diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 000000000..ef079c9fc --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,8 @@ +[advisories] +ignore = [ + "RUSTSEC-2023-0071", # RSA Marvin Attack: potential key recovery through timing sidechannels (not exploitable in our usage) +] + +[target] +arch = "x86_64" +os = "linux" diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index f64d1cbd5..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,398 +0,0 @@ -version: 2.1 - -orbs: - trivy: fifteen5/trivy-orb@1.0.0 - docker: circleci/docker@2.2.0 - terraform: circleci/terraform@3.2.1 - codecov: codecov/codecov@3.2.5 - -commands: - setup-docker: - steps: - - setup_remote_docker - - - run: - name: Setup docker buildx - command: | - set -eux - - docker context create buildx-build - docker buildx create --use buildx-build - - build-scan-docker: - steps: - - run: - name: Build API Image - command: docker build -f ./docker/api.Dockerfile -t ghcr.io/scuffletv/api:$(git rev-parse HEAD) . - - - run: - name: Install Dependencies - command: apk add --no-cache curl jq wget tar gzip ca-certificates - - - trivy/scan: - args: image ghcr.io/scuffletv/api:$(git rev-parse HEAD) - - - run: - name: Build Edge Image - command: docker build -f ./docker/edge.Dockerfile -t ghcr.io/scuffletv/edge:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/edge:$(git rev-parse HEAD) - - - run: - name: Build Ingest Image - command: docker build -f ./docker/ingest.Dockerfile -t ghcr.io/scuffletv/ingest:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/ingest:$(git rev-parse HEAD) - - - run: - name: Build Transcoder Image - command: docker build -f ./docker/transcoder.Dockerfile -t ghcr.io/scuffletv/transcoder:$(git rev-parse HEAD) . - - - trivy/scan: - args: image --severity CRITICAL,HIGH ghcr.io/scuffletv/transcoder:$(git rev-parse HEAD) - - - run: - name: Build Website Image - command: docker build -f ./docker/website.Dockerfile -t ghcr.io/scuffletv/website:$(git rev-parse HEAD) . - - - trivy/scan: - args: image ghcr.io/scuffletv/website:$(git rev-parse HEAD) - -jobs: - lint-test: - resource_class: large - - docker: - - image: ghcr.io/scuffletv/build:c8a76333b9cd45d77aceff358322a0e6a5e4023f - environment: - DATABASE_URL: postgres://root@localhost:26257/scuffle - RMQ_URL: amqp://rabbitmq:rabbitmq@localhost:5672/scuffle - REDIS_URL: redis://localhost:6379 - CARGO_INCREMENTAL: 1 - - - image: ghcr.io/scuffletv/cockroach:latest - - - image: bitnami/rabbitmq:latest - environment: - RABBITMQ_USERNAME: rabbitmq - RABBITMQ_PASSWORD: rabbitmq - RABBITMQ_VHOSTS: scuffle - RABBITMQ_DISK_FREE_ABSOLUTE_LIMIT: 50MB - - - image: redis:latest - - steps: - - docker/install-dockerize - - - terraform/install - - - checkout - - - run: - name: Combine Cargo.lock Files - command: | - sha256sum Cargo.lock >> locksum - sha256sum frontend/player/Cargo.lock >> locksum - - - restore_cache: - name: Restore Rust Lint/Test Cache - keys: - - &lint-test-cache lint-test-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "locksum" }} - - lint-test-cache-{{ .Environment.CACHE_VERSION }}- - - - run: - name: Install dependencies - command: mask bootstrap --no-db --no-docker --no-env --no-rust - - - run: - name: Wait for services to start - command: | - dockerize -wait tcp://localhost:26257 -timeout 15s - dockerize -wait tcp://localhost:5672 -timeout 15s - dockerize -wait tcp://localhost:6379 -timeout 15s - - - run: - name: Migrate Database - command: mask db migrate - - - run: - name: Cargo Sweep Start - command: | - cargo sweep -s - cargo sweep -s frontend/player - - - run: - name: Run Lint - command: mask lint - - - run: - name: Run Rust Tests - command: | - cargo llvm-cov nextest \ - --workspace \ - --lcov \ - --output-path lcov.info \ - --ignore-filename-regex "(main\.rs|tests|.*\.nocov\.rs)" \ - --no-fail-fast \ - -E "not test(_v6)" \ - --status-level all \ - --profile ci \ - --tests \ - --config "profile.dev.debug = 0" - - - codecov/upload: - file: lcov.info - - - run: - name: Install Playwright Dependencies - command: pnpm --filter website exec playwright install-deps - - - run: - name: Run JavaScript Tests - command: mask test --no-rust - - - run: - name: Cargo Sweep Finish - command: | - cargo sweep -f - cargo sweep -f frontend/player - - - save_cache: - name: Save Test Cache - key: *lint-test-cache - paths: - - target - - frontend/player/target - - build: - resource_class: large - - docker: - - image: ghcr.io/scuffletv/build:c8a76333b9cd45d77aceff358322a0e6a5e4023f - environment: - CARGO_INCREMENTAL: 1 - - steps: - - checkout - - - run: - name: Install dependencies - command: mask bootstrap --no-db --no-docker --no-env --no-js-tests --no-rust - - - run: - name: Combine Cargo.lock Files - command: | - sha256sum Cargo.lock >> locksum - sha256sum frontend/player/Cargo.lock >> locksum - - - restore_cache: - name: Restore Rust Build Cache - keys: - - &build-cache build-cache-{{ .Environment.CACHE_VERSION }}-{{ checksum "locksum" }} - - build-cache-{{ .Environment.CACHE_VERSION }}- - - - run: - name: Cargo Sweep Start - command: | - cargo sweep -s - cargo sweep -s frontend/player - - - run: - name: Build Rust - command: mask build rust - - - run: - name: Build JavaScript - command: mask build website --no-gql-prepare - - - run: - name: Cargo Sweep Finish - command: | - cargo sweep -f - cargo sweep -f frontend/player - - - save_cache: - name: Save Build Cache - key: *build-cache - paths: - - target - - frontend/player/target - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/api - destination: api - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/edge - destination: edge - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/ingest - destination: ingest - - - store_artifacts: - path: target/x86_64-unknown-linux-gnu/release/transcoder - destination: transcoder - - - run: - name: Compress Website Build - command: tar -czf website.tar.gz -C frontend/website build --transform s/build/website/ - - - store_artifacts: - path: website.tar.gz - - - run: - name: Compress Player Build - command: tar -czf player.tar.gz -C frontend/player dist --transform s/dist/player/ - - - store_artifacts: - path: player.tar.gz - - - run: - name: Compress Player Package - command: tar -czf player-pkg.tar.gz -C frontend/player pkg --transform s/pkg/player-pkg/ - - - store_artifacts: - path: player-pkg.tar.gz - - - run: - name: Compress Player Demo - command: tar -czf player-demo.tar.gz -C frontend/player demo-dist --transform s/demo-dist/player-demo/ - - - store_artifacts: - path: player-demo.tar.gz - - - persist_to_workspace: - root: . - paths: - - target/x86_64-unknown-linux-gnu/release/api - - target/x86_64-unknown-linux-gnu/release/edge - - target/x86_64-unknown-linux-gnu/release/ingest - - target/x86_64-unknown-linux-gnu/release/transcoder - - frontend/website/build - - docker: - resource_class: medium - - docker: - - image: docker:24.0.4-cli-alpine3.18 - - steps: - - run: - name: Install Git - command: apk add --no-cache git - - - checkout - - - setup-docker - - - attach_workspace: - at: . - - - build-scan-docker - - docker-push: - resource_class: medium - - docker: - - image: docker:24.0.4-cli-alpine3.18 - - steps: - - run: - name: Install Git - command: apk add --no-cache git bash - - - checkout - - - setup-docker - - - attach_workspace: - at: . - - - build-scan-docker - - - run: - name: Login to GitHub Container Registry - command: echo $SCUFFLE_GHCR_TOKEN | docker login ghcr.io -u scuffletv --password-stdin - - - run: - name: Push Images - shell: bash -eo pipefail - command: | - if [[ -n $CIRCLE_PR_USERNAME ]]; then - TAG="$CIRCLE_PR_USERNAME-$CIRCLE_PR_NUMBER" - elif [[ $CIRCLE_BRANCH == "main" ]]; then - TAG="latest" - else - TAG="branch-$CIRCLE_BRANCH" - fi - - # Convert to lowercase & replace all invalid characters - TAG=$(echo $TAG | tr '[:upper:]' '[:lower:]') - TAG=$(echo $TAG | tr '/' '-') - TAG=$(echo $TAG | tr -cd '[[:alnum:]]._-') - - echo "Tagging images with $TAG" - - docker tag ghcr.io/scuffletv/api:$(git rev-parse HEAD) ghcr.io/scuffletv/api:$TAG - docker tag ghcr.io/scuffletv/edge:$(git rev-parse HEAD) ghcr.io/scuffletv/edge:$TAG - docker tag ghcr.io/scuffletv/ingest:$(git rev-parse HEAD) ghcr.io/scuffletv/ingest:$TAG - docker tag ghcr.io/scuffletv/transcoder:$(git rev-parse HEAD) ghcr.io/scuffletv/transcoder:$TAG - docker tag ghcr.io/scuffletv/website:$(git rev-parse HEAD) ghcr.io/scuffletv/website:$TAG - - docker push ghcr.io/scuffletv/api:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/api:$TAG - - docker push ghcr.io/scuffletv/edge:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/edge:$TAG - - docker push ghcr.io/scuffletv/ingest:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/ingest:$TAG - - docker push ghcr.io/scuffletv/transcoder:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/transcoder:$TAG - - docker push ghcr.io/scuffletv/website:$(git rev-parse HEAD) - docker push ghcr.io/scuffletv/website:$TAG - -workflows: - lint-test-build: - jobs: - - hold: - type: approval - - - lint-test: - requires: - - hold - - - build: - requires: - - lint-test - - - docker: - requires: - - build - filters: - branches: - ignore: - - main - - /^feature\/.*$/ - - - docker-push-approve: - type: approval - requires: - - build - filters: - branches: - only: - - main - - /^feature\/.*$/ - - - docker-push: - requires: - - docker-push-approve - - context: - - ghcr diff --git a/.config/nextest.toml b/.config/nextest.toml index f110fed12..4b0fc7ca0 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,6 +1,7 @@ [profile.default] test-threads = "num-cpus" slow-timeout = { period = "10s", terminate-after = 3 } +retries = { backoff = "fixed", count = 2, delay = "1s" } [profile.ci] test-threads = "num-cpus" diff --git a/.dockerignore b/.dockerignore index 5f1fddb5a..8b5a6f9c9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,5 +2,5 @@ node_modules/ .env* Dockerfile -dev-stack/ +dev/ .dockerignore diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..e078f9bcb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# https://editorconfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = tab +trim_trailing_whitespace = true diff --git a/.github/ISSUE_TEMPLATE/bugreport.yml b/.github/ISSUE_TEMPLATE/bugreport.yml index 024253ec2..c57430593 100644 --- a/.github/ISSUE_TEMPLATE/bugreport.yml +++ b/.github/ISSUE_TEMPLATE/bugreport.yml @@ -1,6 +1,6 @@ name: Bug Report description: Found bugs? Share them here -title: "[Bug]: Scuffle has a bug here [...]" +title: "A brief description of the bug" labels: ["bug"] assignees: - ScuffleTV/reviewers @@ -10,6 +10,7 @@ body: value: | ### **Thank you for taking the time to fill out this Bug Report!** Please provide as much information as possible. This will help us resolve the Bug quickly and accurately. + If you know which application this bug is for, please add the appropriate label to this issue. - type: markdown attributes: value: --- @@ -64,11 +65,3 @@ body: label: Relevant log output description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. render: shell - - type: checkboxes - id: disclaimer - attributes: - label: Disclaimer - description: I feel confident that this is a bug and not a browser or PC issue. - options: - - label: I agree - required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index c70f4fc9a..79e561729 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -1,6 +1,6 @@ name: Feature Request description: Have an idea for an improvement to Scuffle? Your ideas are always welcome here -title: "[Feature]: Scuffle needs [...]" +title: "A brief description of the feature request" labels: ["feature request"] assignees: - ScuffleTV/reviewers @@ -10,6 +10,7 @@ body: value: | ### **Thank you for taking the time to fill out this Feature Request!** Please provide as much information as possible. To make sure we have all the details we need to get started, please fill out the form below. + If you know which application this feature request is for, please add the appropriate label to this issue. - type: markdown attributes: value: --- @@ -29,12 +30,6 @@ body: Refer to a an existing bug, you can use `#bugid` validations: required: false - - type: textarea - id: alternatives - attributes: - label: Alternatives - description: | - Have you thought about alternative solutions or ways of implementation? Do you have an idea for how this could be a reality? Let us know! - type: textarea id: info attributes: @@ -50,11 +45,3 @@ body: options: - label: I am willing to submit a pull request to implement this required: false - - type: checkboxes - id: disclaimer - attributes: - label: Disclaimer - description: I have checked that this has not been suggested before - options: - - label: I agree - required: true diff --git a/.github/labels.yml b/.github/labels.yml index 579500cf8..5b876e9c6 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -1,50 +1,61 @@ -- name: "backlog" - color: "7ef7ef" -- name: "bug" - color: "d73a4a" -- name: "chore" - color: "ffffff" -- name: "ci" - color: "0075ca" -- name: "dependencies" - color: "276bd1" -- name: "documentation" - color: "0075ca" -- name: "duplicate" - color: "cfd3d7" -- name: "feature request" - color: "fcf95a" -- name: "good first issue" - color: "7057ff" -- name: "help wanted" - color: "008672" -- name: "invalid" - color: "e4e669" -- name: "need repro" - color: "c66037" -- name: "performance" - color: "80c042" -- name: "priority:critical" - color: "b60205" -- name: "priority:high" - color: "d93f0b" -- name: "priority:medium" - color: "fbca04" -- name: "priority:low" - color: "0e8a16" -- name: "refactor" - color: "1d637f" -- name: "regression" - color: "ea8785" -- name: "tests" - color: "f06dff" -- name: "wontfix" - color: "ffffff" -- name: "frontend" - color: "ff0000" -- name: "backend" - color: "00ff00" -- name: "infrastructure" - color: "0000ff" -- name: "video" - color: "ff00ff" +# Generic labels +- name: bug + color: C90C5B + description: Something isn't working +- name: documentation + color: 58E0CC + description: Improvements or additions to documentation +- name: feature request + color: 75E058 + description: New feature or request +- name: good first issue + color: 26EA63 + description: Good for newcomers +- name: help wanted + color: 42F58A + description: Extra attention is needed +- name: question + color: D5E058 + description: Further information is requested +- name: tests + color: 10CB8B + description: Issues related to tests + +# Video labels +- name: video/api + color: 15E2D8 + description: Issues related to the Video API +- name: video/cli + color: 710D62 + description: Issues related to the Video CLI +- name: video/edge + color: 648E4A + description: Issues related to the Video Edge +- name: video/ingest + color: A74F7B + description: Issues related to the Video Ingest +- name: video/player + color: 9455D9 + description: Issues related to the Video Player +- name: video/transcoder + color: 6061E8 + description: Issues related to the Video Transcoder +- name: video/utils + color: EF3E85 + description: Issues related to other Video Utils + +# Platform labels +- name: platform/api + color: 9BEE11 + description: Issues related to the Platform API +- name: platform/image-processor + color: 7910AD + description: Issues related to the Platform Image Processor +- name: platform/website + color: EB4A2A + description: Issues related to the Platform Website + +# Utilities labels +- name: utils + color: A93146 + description: Issues related to other helper utilities diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index c44edac9c..538f41d94 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -25,7 +25,7 @@ jobs: checks: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Mask run: | diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml index 8fc88fffb..549b3c21e 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/commitlint.yml @@ -8,7 +8,7 @@ jobs: name: Run Commitlint on PR runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/lint-test-build.yml b/.github/workflows/lint-test-build.yml new file mode 100644 index 000000000..24c89a3a7 --- /dev/null +++ b/.github/workflows/lint-test-build.yml @@ -0,0 +1,224 @@ +name: Lint Test Build + +on: + pull_request: + branches: + - main + - feature/** + push: + branches: + - main + - feature/** + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }} + cancel-in-progress: true + +jobs: + pre_job: + name: Pre Job + runs-on: ubuntu-latest + steps: + - id: skip_check + uses: fkirc/skip-duplicate-actions@v3.4.0 + with: + skip_after_successful_duplicate: "true" + do_not_skip: '["workflow_dispatch", "schedule"]' + + - name: Set output + run: echo "should_skip=${{ steps.skip_check.outputs.should_skip }}" >> $GITHUB_OUTPUT + + lint: + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: ubuntu-latest + container: ghcr.io/scuffletv/ci/build:latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: pnpm install -r + + - name: Fix git safe directory + run: git config --global --add safe.directory '*' + + - name: Lint + run: mask lint + + test: + needs: pre_job + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: ubuntu-latest + container: ghcr.io/scuffletv/ci/build:latest + services: + cockroach: + image: ghcr.io/scuffletv/ci/cockroach:latest + env: + ARGS: start-single-node --insecure --advertise-addr=0.0.0.0 + ports: + - 26257:26257 + - 8080:8080 + + nats: + image: ghcr.io/scuffletv/ci/nats:latest + env: + ARGS: -js + ports: + - 4222:4222 + - 8222:8222 + - 6222:6222 + + redis: + image: ghcr.io/scuffletv/ci/redis:latest + ports: + - 6379:6379 + + minio: + image: ghcr.io/scuffletv/ci/minio:latest + env: + ARGS: server /export --console-address ":9001" + MINIO_ACCESS_KEY: root + MINIO_SECRET_KEY: scuffle123 + ports: + - 9000:9000 + - 9001:9001 + + env: + PLATFORM_DATABASE_URL: postgres://root@cockroach:26257/scuffle_platform + VIDEO_DATABASE_URL: postgres://root@cockroach:26257/scuffle_video + PLATFORM_DATABASE_URL_TEST: postgres://root@cockroach:26257/scuffle_platform_test + VIDEO_DATABASE_URL_TEST: postgres://root@cockroach:26257/scuffle_video_test + NATS_ADDR: nats:4222 + REDIS_ADDR: redis:6379 + + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: pnpm install -r + + - name: Migrate database + run: mask dev migrate + + - name: Install MinIO client + run: curl -sL https://dl.min.io/client/mc/release/linux-amd64/mc > /usr/local/bin/mc && chmod +x /usr/local/bin/mc + + - name: Setup Minio + run: | + mc config host add myminio http://minio:9000 root scuffle123; + mc rb --force myminio/scuffle-video || true; + mc rb --force myminio/scuffle-image-processor || true; + mc rb --force myminio/scuffle-image-processor-public || true; + mc mb myminio/scuffle-video; + mc mb myminio/scuffle-image-processor; + mc mb myminio/scuffle-image-processor-public; + mc anonymous set download myminio/scuffle-video; + mc anonymous set download myminio/scuffle-image-processor-public; + + - name: Test + run: mask test --ci + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: true + + build: + needs: [pre_job, lint, test] + if: needs.pre_job.outputs.should_skip != 'true' + runs-on: ubuntu-latest + container: ghcr.io/scuffletv/ci/build:latest + steps: + - uses: actions/checkout@v4 + + # This is needed to prevent git from complaining about the + # `fatal: detected dubious ownership in repository at` error + # when trying to build the website. + - name: Configure git + run: | + git config --global --add safe.directory '*' + + - name: Install dependencies + run: pnpm install -r + + - name: Build Player Demo + run: pnpm -F="@scuffle/player" build + + - name: Upload Artifact (Player Demo) + uses: actions/upload-artifact@v4 + with: + name: video-player-demo + if-no-files-found: error + path: video/player/dist/* + + - name: Build Website + run: pnpm -F="website" build + + - name: Upload Artifact (Website) + uses: actions/upload-artifact@v4 + with: + name: platform-website + if-no-files-found: error + path: platform/website/dist/* + + - name: Build (release fat-lto) + if: github.ref == 'refs/heads/main' + run: cargo build --profile release-fat + + - name: Build (release thin-lto) + if: github.ref != 'refs/heads/main' + run: cargo build --profile release + + - name: Upload Artifact (platform-api) + uses: actions/upload-artifact@v4 + with: + name: platform-api + if-no-files-found: error + path: target/release/platform-api + + - name: Upload Artifact (platform-image-processor) + uses: actions/upload-artifact@v4 + with: + name: image-processor + if-no-files-found: error + path: target/release/platform-image-processor + + - name: Upload Artifact (video-api) + uses: actions/upload-artifact@v4 + with: + name: video-api + if-no-files-found: error + path: target/release/video-api + + - name: Upload Artifact (video-ingest) + uses: actions/upload-artifact@v4 + with: + name: video-ingest + if-no-files-found: error + path: target/release/video-ingest + + - name: Upload Artifact (video-transcoder) + uses: actions/upload-artifact@v4 + with: + name: video-transcoder + if-no-files-found: error + path: target/release/video-transcoder + + - name: Upload Artifact (video-edge) + uses: actions/upload-artifact@v4 + with: + name: video-edge + if-no-files-found: error + path: target/release/video-edge + + - name: Upload Artifact (video-cli) + uses: actions/upload-artifact@v4 + with: + name: video-cli + if-no-files-found: error + path: target/release/video-cli diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 048a1959b..ea999028c 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -8,20 +8,21 @@ on: branches: - main paths: - - ".github/labels.yml" + - .github/labels.yml + - .github/workflows/sync-labels.yml jobs: - synclabels: + sync-labels: name: Sync Labels runs-on: ubuntu-latest - permissions: write-all - steps: - name: Checkout repository - uses: actions/checkout@v3 - - - name: Sync labels - uses: crazy-max/ghaction-github-labeler@v3 + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Syncing labels + uses: crazy-max/ghaction-github-labeler@v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} + yaml-file: .github/labels.yml diff --git a/.gitignore b/.gitignore index 4efdc4e3d..c8c6b5e7a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules/ *.lcov lcov.info local/ +coverage/ diff --git a/.prettierignore b/.prettierignore index b74331c0d..73ed414ab 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,6 @@ -frontend/website/**/* -**/target/**/* -**/pkg/**/* -terraform/.terraform/**/* +/video/ +/platform/ +**/target +**/pkg +terraform/.terraform **/pnpm-lock.yaml diff --git a/.sqlx/query-035868368a1a31c2ebbe29cf6f8838c53fe59545aeb1addd2c55628db7c882de.json b/.sqlx/query-035868368a1a31c2ebbe29cf6f8838c53fe59545aeb1addd2c55628db7c882de.json deleted file mode 100644 index 32b30a0e7..000000000 --- a/.sqlx/query-035868368a1a31c2ebbe29cf6f8838c53fe59545aeb1addd2c55628db7c882de.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "invalidated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 3, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "expires_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "last_used_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid", "Timestamptz"] - }, - "nullable": [false, false, true, false, false, false] - }, - "hash": "035868368a1a31c2ebbe29cf6f8838c53fe59545aeb1addd2c55628db7c882de" -} diff --git a/.sqlx/query-05099b839bff31a75798c381868260aab2157b684575f49c861c0c3700b61d38.json b/.sqlx/query-05099b839bff31a75798c381868260aab2157b684575f49c861c0c3700b61d38.json deleted file mode 100644 index b9830cd00..000000000 --- a/.sqlx/query-05099b839bff31a75798c381868260aab2157b684575f49c861c0c3700b61d38.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM sessions WHERE id = ANY($1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "invalidated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 3, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "expires_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "last_used_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["UuidArray"] - }, - "nullable": [false, false, true, false, false, false] - }, - "hash": "05099b839bff31a75798c381868260aab2157b684575f49c861c0c3700b61d38" -} diff --git a/.sqlx/query-0c1620e99580f2f2903044790957d30670e4171385faa0010c3cf3efbcfb0c07.json b/.sqlx/query-0c1620e99580f2f2903044790957d30670e4171385faa0010c3cf3efbcfb0c07.json deleted file mode 100644 index 1e8397e8c..000000000 --- a/.sqlx/query-0c1620e99580f2f2903044790957d30670e4171385faa0010c3cf3efbcfb0c07.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM global_role_grants", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "0c1620e99580f2f2903044790957d30670e4171385faa0010c3cf3efbcfb0c07" -} diff --git a/.sqlx/query-108b50ed6503dfdf87eb8cc1e3621ec1a3cfe52b2feb730443aeb5901b3cf8fa.json b/.sqlx/query-108b50ed6503dfdf87eb8cc1e3621ec1a3cfe52b2feb730443aeb5901b3cf8fa.json deleted file mode 100644 index e22d4b664..000000000 --- a/.sqlx/query-108b50ed6503dfdf87eb8cc1e3621ec1a3cfe52b2feb730443aeb5901b3cf8fa.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM stream_events WHERE stream_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "stream_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "message", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "level", - "type_info": "Int8" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid"] - }, - "nullable": [false, false, false, false, false, false] - }, - "hash": "108b50ed6503dfdf87eb8cc1e3621ec1a3cfe52b2feb730443aeb5901b3cf8fa" -} diff --git a/.sqlx/query-11e96cfd8c2736f13ce55975ea910dd68640f6f14e38a4b3342d514804e3de27.json b/.sqlx/query-11e96cfd8c2736f13ce55975ea910dd68640f6f14e38a4b3342d514804e3de27.json deleted file mode 100644 index 69b8f1d06..000000000 --- a/.sqlx/query-11e96cfd8c2736f13ce55975ea910dd68640f6f14e38a4b3342d514804e3de27.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM sessions WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid"] - }, - "nullable": [] - }, - "hash": "11e96cfd8c2736f13ce55975ea910dd68640f6f14e38a4b3342d514804e3de27" -} diff --git a/.sqlx/query-1873d5fa1601ed463b0bedc9cdee858e1ddcd09ed8a52253d753174e3d9cc7ce.json b/.sqlx/query-1873d5fa1601ed463b0bedc9cdee858e1ddcd09ed8a52253d753174e3d9cc7ce.json deleted file mode 100644 index 4b6acfef8..000000000 --- a/.sqlx/query-1873d5fa1601ed463b0bedc9cdee858e1ddcd09ed8a52253d753174e3d9cc7ce.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE streams SET ready_state = $2, updated_at = $3, ended_at = $4 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Int8", "Timestamptz", "Timestamptz"] - }, - "nullable": [] - }, - "hash": "1873d5fa1601ed463b0bedc9cdee858e1ddcd09ed8a52253d753174e3d9cc7ce" -} diff --git a/.sqlx/query-2391864f0848a226481224ba6c5173cedd2c1ebd38297e93ff7afa3a78c7fdc1.json b/.sqlx/query-2391864f0848a226481224ba6c5173cedd2c1ebd38297e93ff7afa3a78c7fdc1.json deleted file mode 100644 index 0d965f3c2..000000000 --- a/.sqlx/query-2391864f0848a226481224ba6c5173cedd2c1ebd38297e93ff7afa3a78c7fdc1.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM chat_messages WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "channel_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "author_id", - "type_info": "Uuid" - }, - { - "ordinal": 3, - "name": "content", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid"] - }, - "nullable": [false, false, false, false, false] - }, - "hash": "2391864f0848a226481224ba6c5173cedd2c1ebd38297e93ff7afa3a78c7fdc1" -} diff --git a/.sqlx/query-26e7e05427bc7dabcd7815d27764fda2baf4cfe60a2d2d6ee2a1f773dccbbce2.json b/.sqlx/query-26e7e05427bc7dabcd7815d27764fda2baf4cfe60a2d2d6ee2a1f773dccbbce2.json deleted file mode 100644 index 592bf6265..000000000 --- a/.sqlx/query-26e7e05427bc7dabcd7815d27764fda2baf4cfe60a2d2d6ee2a1f773dccbbce2.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM users", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "stream_key", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "stream_title", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "stream_description", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "stream_transcoding_enabled", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "stream_recording_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "26e7e05427bc7dabcd7815d27764fda2baf4cfe60a2d2d6ee2a1f773dccbbce2" -} diff --git a/.sqlx/query-2c74978cd2c9e2fd4aee55e5b6e7383db42079d2d9e2ca49d5f5c61223d91fc4.json b/.sqlx/query-2c74978cd2c9e2fd4aee55e5b6e7383db42079d2d9e2ca49d5f5c61223d91fc4.json deleted file mode 100644 index e7d13f888..000000000 --- a/.sqlx/query-2c74978cd2c9e2fd4aee55e5b6e7383db42079d2d9e2ca49d5f5c61223d91fc4.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM users WHERE username = ANY($1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "stream_key", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "stream_title", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "stream_description", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "stream_transcoding_enabled", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "stream_recording_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["VarcharArray"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "2c74978cd2c9e2fd4aee55e5b6e7383db42079d2d9e2ca49d5f5c61223d91fc4" -} diff --git a/.sqlx/query-368cd60692561bd636df964fa09d77d7daac755a0b151756789618176c4aeb46.json b/.sqlx/query-368cd60692561bd636df964fa09d77d7daac755a0b151756789618176c4aeb46.json deleted file mode 100644 index 61ec7ff7e..000000000 --- a/.sqlx/query-368cd60692561bd636df964fa09d77d7daac755a0b151756789618176c4aeb46.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": ["Varchar", "Varchar", "Varchar", "Varchar"] - }, - "nullable": [false] - }, - "hash": "368cd60692561bd636df964fa09d77d7daac755a0b151756789618176c4aeb46" -} diff --git a/.sqlx/query-3b7a241164f959d566e9e3944e23f515377ed87813964914f76d7f6c59e831e7.json b/.sqlx/query-3b7a241164f959d566e9e3944e23f515377ed87813964914f76d7f6c59e831e7.json deleted file mode 100644 index f0b52979e..000000000 --- a/.sqlx/query-3b7a241164f959d566e9e3944e23f515377ed87813964914f76d7f6c59e831e7.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE sessions SET last_used_at = NOW() WHERE id = $1 RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "invalidated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 3, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "expires_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "last_used_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid"] - }, - "nullable": [false, false, true, false, false, false] - }, - "hash": "3b7a241164f959d566e9e3944e23f515377ed87813964914f76d7f6c59e831e7" -} diff --git a/.sqlx/query-3e75dfea0d1b0bf39900027cc5811fadcaa9f1ac2bcc7ee734036ca5ae80ba3f.json b/.sqlx/query-3e75dfea0d1b0bf39900027cc5811fadcaa9f1ac2bcc7ee734036ca5ae80ba3f.json deleted file mode 100644 index de3b76ba7..000000000 --- a/.sqlx/query-3e75dfea0d1b0bf39900027cc5811fadcaa9f1ac2bcc7ee734036ca5ae80ba3f.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id, ended_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "channel_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "recorded", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "transcoded", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "deleted", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "ready_state", - "type_info": "Int8" - }, - { - "ordinal": 8, - "name": "ingest_address", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "connection_id", - "type_info": "Uuid" - }, - { - "ordinal": 10, - "name": "state", - "type_info": "Bytea" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 13, - "name": "ended_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid", "Varchar", "Text", "Bool", "Bool", "Varchar", "Uuid", "Timestamptz"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - false - ] - }, - "hash": "3e75dfea0d1b0bf39900027cc5811fadcaa9f1ac2bcc7ee734036ca5ae80ba3f" -} diff --git a/.sqlx/query-4d7199d35d15dd51799ad2f7e568c92b6facabe6dfd9fe64328f426a08471904.json b/.sqlx/query-4d7199d35d15dd51799ad2f7e568c92b6facabe6dfd9fe64328f426a08471904.json deleted file mode 100644 index 8f5166636..000000000 --- a/.sqlx/query-4d7199d35d15dd51799ad2f7e568c92b6facabe6dfd9fe64328f426a08471904.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE streams SET updated_at = $2, ended_at = $3 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Timestamptz", "Timestamptz"] - }, - "nullable": [] - }, - "hash": "4d7199d35d15dd51799ad2f7e568c92b6facabe6dfd9fe64328f426a08471904" -} diff --git a/.sqlx/query-512142ff15edf2c33cc8680612c751438ff65e0a47dd03fe849489f2ad8f7dea.json b/.sqlx/query-512142ff15edf2c33cc8680612c751438ff65e0a47dd03fe849489f2ad8f7dea.json deleted file mode 100644 index 2f9531294..000000000 --- a/.sqlx/query-512142ff15edf2c33cc8680612c751438ff65e0a47dd03fe849489f2ad8f7dea.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO global_role_grants(user_id, global_role_id, created_at) VALUES ($1, $2, $3)", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Uuid", "Timestamptz"] - }, - "nullable": [] - }, - "hash": "512142ff15edf2c33cc8680612c751438ff65e0a47dd03fe849489f2ad8f7dea" -} diff --git a/.sqlx/query-614fafd36514d4d678c746372ff86c839dfb155eadc4c769266ce6fc259aa622.json b/.sqlx/query-614fafd36514d4d678c746372ff86c839dfb155eadc4c769266ce6fc259aa622.json deleted file mode 100644 index 6e00da49a..000000000 --- a/.sqlx/query-614fafd36514d4d678c746372ff86c839dfb155eadc4c769266ce6fc259aa622.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "stream_key", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "stream_title", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "stream_description", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "stream_transcoding_enabled", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "stream_recording_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Varchar", "Varchar", "Varchar", "Varchar"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "614fafd36514d4d678c746372ff86c839dfb155eadc4c769266ce6fc259aa622" -} diff --git a/.sqlx/query-67212733127cc5b54c1f800575c07bee774ac3ccd41001f508235aaf016969ff.json b/.sqlx/query-67212733127cc5b54c1f800575c07bee774ac3ccd41001f508235aaf016969ff.json deleted file mode 100644 index c8470485a..000000000 --- a/.sqlx/query-67212733127cc5b54c1f800575c07bee774ac3ccd41001f508235aaf016969ff.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO stream_events (stream_id, level, title, message, created_at) VALUES ($1, $2, $3, $4, $5)", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Int8", "Varchar", "Text", "Timestamptz"] - }, - "nullable": [] - }, - "hash": "67212733127cc5b54c1f800575c07bee774ac3ccd41001f508235aaf016969ff" -} diff --git a/.sqlx/query-758b4822a1597c4e683b5160015edcca61bdc5e4ed67611ac74a7d241f150615.json b/.sqlx/query-758b4822a1597c4e683b5160015edcca61bdc5e4ed67611ac74a7d241f150615.json deleted file mode 100644 index 1637d65e9..000000000 --- a/.sqlx/query-758b4822a1597c4e683b5160015edcca61bdc5e4ed67611ac74a7d241f150615.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM chat_messages", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "758b4822a1597c4e683b5160015edcca61bdc5e4ed67611ac74a7d241f150615" -} diff --git a/.sqlx/query-7e9ff951aeb95580a0e33bd1b82f3cafb3f1cbee9002c2a6ab4306890c5fd563.json b/.sqlx/query-7e9ff951aeb95580a0e33bd1b82f3cafb3f1cbee9002c2a6ab4306890c5fd563.json deleted file mode 100644 index ad6343c2b..000000000 --- a/.sqlx/query-7e9ff951aeb95580a0e33bd1b82f3cafb3f1cbee9002c2a6ab4306890c5fd563.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT rg.user_id, r.* FROM global_role_grants rg JOIN global_roles r ON rg.global_role_id = r.id WHERE rg.user_id = ANY($1) ORDER BY rg.user_id, r.rank ASC", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "rank", - "type_info": "Int8" - }, - { - "ordinal": 5, - "name": "allowed_permissions", - "type_info": "Int8" - }, - { - "ordinal": 6, - "name": "denied_permissions", - "type_info": "Int8" - }, - { - "ordinal": 7, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["UuidArray"] - }, - "nullable": [false, false, false, false, false, false, false, false] - }, - "hash": "7e9ff951aeb95580a0e33bd1b82f3cafb3f1cbee9002c2a6ab4306890c5fd563" -} diff --git a/.sqlx/query-7f424c4cb2f5ba376b1397fa03f3b8cd6d26fdef16a6a5c385595ecabd6c67f9.json b/.sqlx/query-7f424c4cb2f5ba376b1397fa03f3b8cd6d26fdef16a6a5c385595ecabd6c67f9.json deleted file mode 100644 index f8fb48b0c..000000000 --- a/.sqlx/query-7f424c4cb2f5ba376b1397fa03f3b8cd6d26fdef16a6a5c385595ecabd6c67f9.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE sessions SET invalidated_at = NOW() WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid"] - }, - "nullable": [] - }, - "hash": "7f424c4cb2f5ba376b1397fa03f3b8cd6d26fdef16a6a5c385595ecabd6c67f9" -} diff --git a/.sqlx/query-8ad0d841aef4b91bdc8f7944fdd6681791e233abe990672413f4f985769e9557.json b/.sqlx/query-8ad0d841aef4b91bdc8f7944fdd6681791e233abe990672413f4f985769e9557.json deleted file mode 100644 index e269822bf..000000000 --- a/.sqlx/query-8ad0d841aef4b91bdc8f7944fdd6681791e233abe990672413f4f985769e9557.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM streams WHERE id = ANY($1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "channel_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "recorded", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "transcoded", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "deleted", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "ready_state", - "type_info": "Int8" - }, - { - "ordinal": 8, - "name": "ingest_address", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "connection_id", - "type_info": "Uuid" - }, - { - "ordinal": 10, - "name": "state", - "type_info": "Bytea" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 13, - "name": "ended_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["UuidArray"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - false - ] - }, - "hash": "8ad0d841aef4b91bdc8f7944fdd6681791e233abe990672413f4f985769e9557" -} diff --git a/.sqlx/query-9694b996490453d5084dd3e1a39fd50ee8b118112b76815247a0b88ecc229334.json b/.sqlx/query-9694b996490453d5084dd3e1a39fd50ee8b118112b76815247a0b88ecc229334.json deleted file mode 100644 index 7b10ffac4..000000000 --- a/.sqlx/query-9694b996490453d5084dd3e1a39fd50ee8b118112b76815247a0b88ecc229334.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM global_roles", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "9694b996490453d5084dd3e1a39fd50ee8b118112b76815247a0b88ecc229334" -} diff --git a/.sqlx/query-97f47a6dfe152120cba09261c58609d2649d0d2d13e3c03f022ae5ff75b7b4e4.json b/.sqlx/query-97f47a6dfe152120cba09261c58609d2649d0d2d13e3c03f022ae5ff75b7b4e4.json deleted file mode 100644 index ff04cda12..000000000 --- a/.sqlx/query-97f47a6dfe152120cba09261c58609d2649d0d2d13e3c03f022ae5ff75b7b4e4.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE streams SET updated_at = NOW(), state = $2 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Bytea"] - }, - "nullable": [] - }, - "hash": "97f47a6dfe152120cba09261c58609d2649d0d2d13e3c03f022ae5ff75b7b4e4" -} diff --git a/.sqlx/query-9b57fecce896b1fbef36e9f274f073342913ef8f4c765db9ede719c80d0baba0.json b/.sqlx/query-9b57fecce896b1fbef36e9f274f073342913ef8f4c765db9ede719c80d0baba0.json deleted file mode 100644 index 2fa1e51d0..000000000 --- a/.sqlx/query-9b57fecce896b1fbef36e9f274f073342913ef8f4c765db9ede719c80d0baba0.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM stream_bitrate_updates WHERE stream_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "stream_id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "video_bitrate", - "type_info": "Int8" - }, - { - "ordinal": 2, - "name": "audio_bitrate", - "type_info": "Int8" - }, - { - "ordinal": 3, - "name": "metadata_bitrate", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid"] - }, - "nullable": [false, false, false, false, false] - }, - "hash": "9b57fecce896b1fbef36e9f274f073342913ef8f4c765db9ede719c80d0baba0" -} diff --git a/.sqlx/query-9fef50eb9e29e7c52a1cc6fea6496023f49e91e927eacaa75913d1e8155951e1.json b/.sqlx/query-9fef50eb9e29e7c52a1cc6fea6496023f49e91e927eacaa75913d1e8155951e1.json deleted file mode 100644 index 65756a8c8..000000000 --- a/.sqlx/query-9fef50eb9e29e7c52a1cc6fea6496023f49e91e927eacaa75913d1e8155951e1.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE streams SET ready_state = 0, ended_at = $2 WHERE id = $1;", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Timestamptz"] - }, - "nullable": [] - }, - "hash": "9fef50eb9e29e7c52a1cc6fea6496023f49e91e927eacaa75913d1e8155951e1" -} diff --git a/.sqlx/query-a1e8e41b7c5e2cd81b351bb9bbc5276ab2b660a890a87249a062f44d7515b3bc.json b/.sqlx/query-a1e8e41b7c5e2cd81b351bb9bbc5276ab2b660a890a87249a062f44d7515b3bc.json deleted file mode 100644 index 1df5402b9..000000000 --- a/.sqlx/query-a1e8e41b7c5e2cd81b351bb9bbc5276ab2b660a890a87249a062f44d7515b3bc.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": ["Varchar", "Text", "Int8", "Int8", "Int8", "Timestamptz"] - }, - "nullable": [false] - }, - "hash": "a1e8e41b7c5e2cd81b351bb9bbc5276ab2b660a890a87249a062f44d7515b3bc" -} diff --git a/.sqlx/query-a6953b8d45e8ccf9da305fe0e9e2d7661063317a48cb96448d06da043f39edff.json b/.sqlx/query-a6953b8d45e8ccf9da305fe0e9e2d7661063317a48cb96448d06da043f39edff.json deleted file mode 100644 index 7b7a2ef86..000000000 --- a/.sqlx/query-a6953b8d45e8ccf9da305fe0e9e2d7661063317a48cb96448d06da043f39edff.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM sessions", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "a6953b8d45e8ccf9da305fe0e9e2d7661063317a48cb96448d06da043f39edff" -} diff --git a/.sqlx/query-a8d689f3da99ca8dd832facc7aca3e4a766ccaa66afbddb3bbb4334df2a18b79.json b/.sqlx/query-a8d689f3da99ca8dd832facc7aca3e4a766ccaa66afbddb3bbb4334df2a18b79.json deleted file mode 100644 index 28f9385b6..000000000 --- a/.sqlx/query-a8d689f3da99ca8dd832facc7aca3e4a766ccaa66afbddb3bbb4334df2a18b79.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM global_roles WHERE rank = -1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "rank", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "allowed_permissions", - "type_info": "Int8" - }, - { - "ordinal": 5, - "name": "denied_permissions", - "type_info": "Int8" - }, - { - "ordinal": 6, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [false, false, false, false, false, false, false] - }, - "hash": "a8d689f3da99ca8dd832facc7aca3e4a766ccaa66afbddb3bbb4334df2a18b79" -} diff --git a/.sqlx/query-b0e75a4049dd4ffe01458ac90cba1ea4d89adc76be24f485bfed5b80e49827f4.json b/.sqlx/query-b0e75a4049dd4ffe01458ac90cba1ea4d89adc76be24f485bfed5b80e49827f4.json deleted file mode 100644 index e47103813..000000000 --- a/.sqlx/query-b0e75a4049dd4ffe01458ac90cba1ea4d89adc76be24f485bfed5b80e49827f4.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "stream_key", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "stream_title", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "stream_description", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "stream_transcoding_enabled", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "stream_recording_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Varchar", "Varchar", "Varchar", "Varchar"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "b0e75a4049dd4ffe01458ac90cba1ea4d89adc76be24f485bfed5b80e49827f4" -} diff --git a/.sqlx/query-b21c95c9715a07d176c595b6e288c723185d9c115dccd52820432061c3d726b3.json b/.sqlx/query-b21c95c9715a07d176c595b6e288c723185d9c115dccd52820432061c3d726b3.json deleted file mode 100644 index 54e4d43e1..000000000 --- a/.sqlx/query-b21c95c9715a07d176c595b6e288c723185d9c115dccd52820432061c3d726b3.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4)", - "describe": { - "columns": [], - "parameters": { - "Left": ["Varchar", "Varchar", "Varchar", "Varchar"] - }, - "nullable": [] - }, - "hash": "b21c95c9715a07d176c595b6e288c723185d9c115dccd52820432061c3d726b3" -} diff --git a/.sqlx/query-b2e203f1efe61c600ef99fce94875a138dc2a764d6e89860de0c2be54c8e6273.json b/.sqlx/query-b2e203f1efe61c600ef99fce94875a138dc2a764d6e89860de0c2be54c8e6273.json deleted file mode 100644 index b379afd39..000000000 --- a/.sqlx/query-b2e203f1efe61c600ef99fce94875a138dc2a764d6e89860de0c2be54c8e6273.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "channel_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "recorded", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "transcoded", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "deleted", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "ready_state", - "type_info": "Int8" - }, - { - "ordinal": 8, - "name": "ingest_address", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "connection_id", - "type_info": "Uuid" - }, - { - "ordinal": 10, - "name": "state", - "type_info": "Bytea" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 13, - "name": "ended_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid", "Varchar", "Text", "Bool", "Bool", "Varchar", "Uuid"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - false - ] - }, - "hash": "b2e203f1efe61c600ef99fce94875a138dc2a764d6e89860de0c2be54c8e6273" -} diff --git a/.sqlx/query-b4f47071b16828f14aa4675cd536c7f78a4d44fab3b4d5a3824205f050427487.json b/.sqlx/query-b4f47071b16828f14aa4675cd536c7f78a4d44fab3b4d5a3824205f050427487.json deleted file mode 100644 index f7a57357c..000000000 --- a/.sqlx/query-b4f47071b16828f14aa4675cd536c7f78a4d44fab3b4d5a3824205f050427487.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (username, display_name, password_hash, email, stream_key) VALUES ($1, $2, $3, $4, $5) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "stream_key", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "stream_title", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "stream_description", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "stream_transcoding_enabled", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "stream_recording_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Varchar", "Varchar", "Varchar", "Varchar", "Varchar"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "b4f47071b16828f14aa4675cd536c7f78a4d44fab3b4d5a3824205f050427487" -} diff --git a/.sqlx/query-b70317ca36372ae9803b15c675439062654e708b88c838cef53e642b16963bd3.json b/.sqlx/query-b70317ca36372ae9803b15c675439062654e708b88c838cef53e642b16963bd3.json deleted file mode 100644 index 84928ae8e..000000000 --- a/.sqlx/query-b70317ca36372ae9803b15c675439062654e708b88c838cef53e642b16963bd3.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO sessions (user_id, expires_at) VALUES ($1, $2) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "invalidated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 3, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "expires_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "last_used_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid", "Timestamptz"] - }, - "nullable": [false, false, true, false, false, false] - }, - "hash": "b70317ca36372ae9803b15c675439062654e708b88c838cef53e642b16963bd3" -} diff --git a/.sqlx/query-be069c894da4bd83e723cf42f1e9dd81d7415c2daa11bff6b20d4e80ff5a4655.json b/.sqlx/query-be069c894da4bd83e723cf42f1e9dd81d7415c2daa11bff6b20d4e80ff5a4655.json deleted file mode 100644 index 8cca7e489..000000000 --- a/.sqlx/query-be069c894da4bd83e723cf42f1e9dd81d7415c2daa11bff6b20d4e80ff5a4655.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO stream_bitrate_updates (stream_id, video_bitrate, audio_bitrate, metadata_bitrate, created_at) VALUES ($1, $2, $3, $4, $5)", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Int8", "Int8", "Int8", "Timestamptz"] - }, - "nullable": [] - }, - "hash": "be069c894da4bd83e723cf42f1e9dd81d7415c2daa11bff6b20d4e80ff5a4655" -} diff --git a/.sqlx/query-cb132bd2a36febc699e69c97c7f2a673e7a58cd4764e5f27bea094dcae6068a9.json b/.sqlx/query-cb132bd2a36febc699e69c97c7f2a673e7a58cd4764e5f27bea094dcae6068a9.json deleted file mode 100644 index 08f88eac4..000000000 --- a/.sqlx/query-cb132bd2a36febc699e69c97c7f2a673e7a58cd4764e5f27bea094dcae6068a9.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM streams WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "channel_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "title", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "description", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "recorded", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "transcoded", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "deleted", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "ready_state", - "type_info": "Int8" - }, - { - "ordinal": 8, - "name": "ingest_address", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "connection_id", - "type_info": "Uuid" - }, - { - "ordinal": 10, - "name": "state", - "type_info": "Bytea" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 13, - "name": "ended_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - false, - true, - false - ] - }, - "hash": "cb132bd2a36febc699e69c97c7f2a673e7a58cd4764e5f27bea094dcae6068a9" -} diff --git a/.sqlx/query-ce4e760f056c23b782cc4c7b9a5e2b9bef618d3f0add681bcfbae3913eda68f5.json b/.sqlx/query-ce4e760f056c23b782cc4c7b9a5e2b9bef618d3f0add681bcfbae3913eda68f5.json deleted file mode 100644 index a9b58b763..000000000 --- a/.sqlx/query-ce4e760f056c23b782cc4c7b9a5e2b9bef618d3f0add681bcfbae3913eda68f5.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO streams (id, channel_id, title, description, ready_state, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7)", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Uuid", "Varchar", "Text", "Int8", "Varchar", "Uuid"] - }, - "nullable": [] - }, - "hash": "ce4e760f056c23b782cc4c7b9a5e2b9bef618d3f0add681bcfbae3913eda68f5" -} diff --git a/.sqlx/query-d130c416e56962ab334ee1b4ca77369a4c35dbc1cf31279f7ed4d418ed75aabb.json b/.sqlx/query-d130c416e56962ab334ee1b4ca77369a4c35dbc1cf31279f7ed4d418ed75aabb.json deleted file mode 100644 index 1c2264b16..000000000 --- a/.sqlx/query-d130c416e56962ab334ee1b4ca77369a4c35dbc1cf31279f7ed4d418ed75aabb.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM sessions", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "invalidated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 3, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "expires_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "last_used_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [false, false, true, false, false, false] - }, - "hash": "d130c416e56962ab334ee1b4ca77369a4c35dbc1cf31279f7ed4d418ed75aabb" -} diff --git a/.sqlx/query-d72e3fa41e75cf014f0320b64ee7dc09359df8f1e8c7ddc4ba441c128d9f32bd.json b/.sqlx/query-d72e3fa41e75cf014f0320b64ee7dc09359df8f1e8c7ddc4ba441c128d9f32bd.json deleted file mode 100644 index 2d2f6608e..000000000 --- a/.sqlx/query-d72e3fa41e75cf014f0320b64ee7dc09359df8f1e8c7ddc4ba441c128d9f32bd.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO chat_messages (channel_id, author_id, content) VALUES ($1, $2, $3) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "channel_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "author_id", - "type_info": "Uuid" - }, - { - "ordinal": 3, - "name": "content", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Uuid", "Uuid", "Text"] - }, - "nullable": [false, false, false, false, false] - }, - "hash": "d72e3fa41e75cf014f0320b64ee7dc09359df8f1e8c7ddc4ba441c128d9f32bd" -} diff --git a/.sqlx/query-d922fee7a1f6646268b5a3a6feb3f4574d0617d2366d9ecd9ecbaba259d921f2.json b/.sqlx/query-d922fee7a1f6646268b5a3a6feb3f4574d0617d2366d9ecd9ecbaba259d921f2.json deleted file mode 100644 index c91c98e19..000000000 --- a/.sqlx/query-d922fee7a1f6646268b5a3a6feb3f4574d0617d2366d9ecd9ecbaba259d921f2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": ["Varchar", "Varchar", "Varchar", "Varchar"] - }, - "nullable": [false] - }, - "hash": "d922fee7a1f6646268b5a3a6feb3f4574d0617d2366d9ecd9ecbaba259d921f2" -} diff --git a/.sqlx/query-dbf86803b7b808f9363c880937fa393e0de85cc77a61c328af0e9afc846e393d.json b/.sqlx/query-dbf86803b7b808f9363c880937fa393e0de85cc77a61c328af0e9afc846e393d.json deleted file mode 100644 index 932923e9d..000000000 --- a/.sqlx/query-dbf86803b7b808f9363c880937fa393e0de85cc77a61c328af0e9afc846e393d.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO global_role_grants (user_id, global_role_id) VALUES ($1, $2)", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Uuid"] - }, - "nullable": [] - }, - "hash": "dbf86803b7b808f9363c880937fa393e0de85cc77a61c328af0e9afc846e393d" -} diff --git a/.sqlx/query-e364703ad664b479bed3ae4990cccb7666c16c9b98e0c4c814ece462723b70d2.json b/.sqlx/query-e364703ad664b479bed3ae4990cccb7666c16c9b98e0c4c814ece462723b70d2.json deleted file mode 100644 index 5a7c4849a..000000000 --- a/.sqlx/query-e364703ad664b479bed3ae4990cccb7666c16c9b98e0c4c814ece462723b70d2.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE streams SET ready_state = $2, updated_at = $3, ended_at = $3 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Int8", "Timestamptz"] - }, - "nullable": [] - }, - "hash": "e364703ad664b479bed3ae4990cccb7666c16c9b98e0c4c814ece462723b70d2" -} diff --git a/.sqlx/query-e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b.json b/.sqlx/query-e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b.json deleted file mode 100644 index f7b401dac..000000000 --- a/.sqlx/query-e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM users WHERE id = ANY($1)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "stream_key", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "stream_title", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "stream_description", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "stream_transcoding_enabled", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "stream_recording_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["UuidArray"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "e4568529cfbdc9207c1ba481ae77489e756927d45b7963842215098d51bc3d0b" -} diff --git a/.sqlx/query-f03bc2e6748e50c26096726e04a309e356161fef6c0ba068dcb7e1b5e884423f.json b/.sqlx/query-f03bc2e6748e50c26096726e04a309e356161fef6c0ba068dcb7e1b5e884423f.json deleted file mode 100644 index 81624e021..000000000 --- a/.sqlx/query-f03bc2e6748e50c26096726e04a309e356161fef6c0ba068dcb7e1b5e884423f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM streams", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "f03bc2e6748e50c26096726e04a309e356161fef6c0ba068dcb7e1b5e884423f" -} diff --git a/.sqlx/query-f384b5f03269060341ac3d10061952ab57a30ab6e37111b855d0dea80fcf022a.json b/.sqlx/query-f384b5f03269060341ac3d10061952ab57a30ab6e37111b855d0dea80fcf022a.json deleted file mode 100644 index cc0595bc3..000000000 --- a/.sqlx/query-f384b5f03269060341ac3d10061952ab57a30ab6e37111b855d0dea80fcf022a.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Varchar" - }, - { - "ordinal": 4, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "email_verified", - "type_info": "Bool" - }, - { - "ordinal": 6, - "name": "stream_key", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "stream_title", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "stream_description", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "stream_transcoding_enabled", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "stream_recording_enabled", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "last_login_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": ["Varchar", "Varchar", "Varchar", "Varchar"] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "f384b5f03269060341ac3d10061952ab57a30ab6e37111b855d0dea80fcf022a" -} diff --git a/.sqlx/query-f4f8f8c2668ec23ba1f4a315d74087521496603e8b1bc10475a864001e795593.json b/.sqlx/query-f4f8f8c2668ec23ba1f4a315d74087521496603e8b1bc10475a864001e795593.json deleted file mode 100644 index d34b73e8e..000000000 --- a/.sqlx/query-f4f8f8c2668ec23ba1f4a315d74087521496603e8b1bc10475a864001e795593.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM users", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "f4f8f8c2668ec23ba1f4a315d74087521496603e8b1bc10475a864001e795593" -} diff --git a/.sqlx/query-f604fe60c8dcdbfd2be470ae557d04b723e9a5b47ce0b5e42e156811d17e1758.json b/.sqlx/query-f604fe60c8dcdbfd2be470ae557d04b723e9a5b47ce0b5e42e156811d17e1758.json deleted file mode 100644 index b2eac41d5..000000000 --- a/.sqlx/query-f604fe60c8dcdbfd2be470ae557d04b723e9a5b47ce0b5e42e156811d17e1758.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE streams SET ready_state = $2, ended_at = NOW(), updated_at = NOW() WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": ["Uuid", "Int8"] - }, - "nullable": [] - }, - "hash": "f604fe60c8dcdbfd2be470ae557d04b723e9a5b47ce0b5e42e156811d17e1758" -} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 90f131a74..280a1857c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,7 @@ { "recommendations": [ - "kokakiwi.vscode-just", "rust-lang.rust-analyzer", "serayuzgur.crates", - "bungcip.better-toml", "swellaby.rust-pack", "svelte.svelte-vscode", "zxh404.vscode-proto3", diff --git a/CODEOWNERS b/CODEOWNERS index f145310a6..60b53fe23 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,7 +1,9 @@ * @ScuffleTV/maintainers -frontend/ @ScuffleTV/frontend @ScuffleTV/maintainers +platform/website @ScuffleTV/frontend @ScuffleTV/maintainers video/ @ScuffleTV/video @ScuffleTV/maintainers -backend/ @ScuffleTV/backend @ScuffleTV/maintainers +video/player @ScuffleTV/video @ScuffleTV/frontend @ScuffleTV/maintainers + +platform/backend @ScuffleTV/backend @ScuffleTV/maintainers diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c01974af9..354d8c5ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,274 +2,296 @@ ## Code of Conduct -We have a [Code of Conduct](./CODE_OF_CONDUCT.md) that we expect all contributors to follow. Please read it before contributing. +Before diving in, please familiarize yourself with our [Code of Conduct](./CODE_OF_CONDUCT.md). Adherence to it ensures a harmonious community. -## CLA - -We require all contributors to sign a [Contributor License Agreement](./CLA.md) before we can accept any contributions. +## Design Documents -To sign the CLA, please head over to [cla.scuffle.tv](https://cla.scuffle.tv) and sign the CLA. +To understand the project's structure and functionality, refer to our [design document](./design/README.md). -## Getting Started +## CLA -In order to get started, you will need to have the following installed on your machine: +Before accepting contributions, we require all contributors to sign a [Contributor License Agreement](./CLA.md). To sign the CLA, visit [cla.scuffle.tv](https://cla.scuffle.tv). -For this project we recommend using [VSCode](https://code.visualstudio.com/) as your IDE. +## Monorepo -We also advise you to use a linux based operating system, however, if you are on windows you can use [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) to run Linux commands. +This project is structured as a [monorepo](https://semaphoreci.com/blog/what-is-monorepo), meaning all code for every project resides here. We chose a monorepo for several reasons: -### WSL2 +- Code sharing across services and products is streamlined. +- It simplifies testing and integration across the platform. +- Multiple projects can be maintained and contributed to within a single PR or ticket. -If you are using WSL2, we recommend you also setup systemd so you can run services like docker inside of WSL2 rather than using Docker Desktop. +However, this approach necessitates a more intricate build system. -You can find instructions on how to do that [here](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/). +## Commit Messages -- [Git](https://git-scm.com/) -- [NodeJS](https://nodejs.org/en/) -- [PNPM](https://pnpm.io/) -- [Docker](https://www.docker.com/) -- [Docker Compose V2](https://docs.docker.com/compose/install) -- [Rust](https://www.rust-lang.org/tools/install) -- [Mask](https://github.com/jacobdeichert/mask) -- [Terraform](https://developer.hashicorp.com/terraform) +When committing to our `main` branch, please adhere to our conventions: -### For Ubuntu +- Ensure every commit can be compiled successfully and is formatted. +- Follow the format detailed [here](https://karma-runner.github.io/6.4/dev/git-commit-msg.html). -If you are using Ubuntu you can install everything with the following commands: +Example: ```bash -# Installing Rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +doc(api): Added documentation for xyz +``` -# Configuring apt to find nodejs -curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash - +The general format is: -# install pnpm -curl -fsSL https://get.pnpm.io/install.sh | sh - +```bash +type(scope): +``` -# Running the install for nodejs, make, docker and git -sudo apt-get update -sudo apt-get install build-essential pkg-config libssl-dev nodejs docker.io git gnupg software-properties-common +In the commit message body, provide a detailed description and link to the ticket addressed by the commit: -# Add Hashicorp's GPG key -wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null +```bash +Closes #1, #2 +``` -# Add Hashicorp's repository -echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \ -https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \ -sudo tee /etc/apt/sources.list.d/hashicorp.list > /dev/null +If there are breaking changes, mention them: -# Install Terraform -sudo apt-get update && sudo apt-get install terraform +```bash +Breaking changes: -# Installing docker compose v2 -DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} -mkdir -p $DOCKER_CONFIG/cli-plugins -curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose -chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose +`abc` is no longer supported and has been replaced with `xyz` ``` -You should also make it so you can run docker without sudo. +For any queries about commit message formatting, feel free to ask. -```bash -sudo groupadd docker -sudo usermod -aG docker $USER -``` +## Pull Requests -Now you need to setup your environment variables. +Each commit in a pull request should address one or more tickets. Aim for a `many to one` relationship: multiple tickets can be addressed in a single commit, but each ticket should have only one associated commit. While developing, you can commit freely. However, before merging, you'll be asked to squash your commits and adjust their names. After ensuring CI passes, we can merge your contributions. -You should add the following to your `~/.bashrc` or `~/.zshrc` file. +To squash commits: ```bash -source $HOME/.cargo/env -export PATH="$HOME/.cargo/bin:$PATH" +git rebase -i HEAD~ ``` -Installing Mask +or -``` -cargo install mask +```bash +git rebase -i ``` -## Setting up the project - -Once you have everything installed, you can clone the project and install the dependencies. +## Code Formatting -```bash -git clone --recurse-submodules https://github.com/ScuffleTV/scuffle.git scuffle -cd scuffle -mask bootstrap -``` +While formatting isn't mandatory during development, it's encouraged for easier PR reviews. Before merging, ensure your PR is formatted. -The boostrap command will setup the project for you. +## Testing -This includes: +Each subproject has specific testing requirements. Refer to the README of each subproject for details. Integration tests will be run on your PR to ensure overall system integrity. -- Installing all the dependencies -- Setting up the database -- Setting up .env files +## Documentation -## Development Database +Update the documentation of the subproject you're working on when making a PR. This isn't mandatory during development but is encouraged. Before merging, documentation updates are required. -We use Postgres for our database. +## Getting Started -You can run a local instance of Postgres with the following command: +To begin, ensure the following are installed: -```bash -mask db up -``` +- Recommended IDE: [VSCode](https://code.visualstudio.com/) +- For Windows users, use [WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10) for Linux commands. -To shut down the local instance of Postgres you can run the following command: +### WSL2 -```bash -mask db down -``` +For WSL2 users, setting up systemd is recommended to run services like Docker inside WSL2, bypassing Docker Desktop. Instructions are available [here](https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/). -### Database Migrations +### Install Components -We use sqlx-cli to manage our database migrations. +A guide for installing everyone on Ubuntu: -You can run the migrations with the following command: +#### Rust ```bash -mask db migrate +sudo apt-get update +sudo apt-get install -y curl gnupg ca-certificates git +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` -You can create a new migration with the following command: +Update your environment variables by adding the following to `~/.bashrc` or `~/.zshrc`: ```bash -mask db migrate add "migration name" +source $HOME/.cargo/env +export PATH="$HOME/.cargo/bin:$PATH" ``` -Then you can find the SQL for the migration in the [migrations](./backend/migrations) folder. -You can then edit the up migration file to add your SQL. -You must also provide a down migration file so we can rollback the migration. - -You will then be prompted to rerun the prepare command +To install Mask: ```bash -mask db prepare +cargo install mask ``` -This will run the migrations and generate the SQLx code for the database. So that compile time querying can be used. +#### NodeJS -### Turnstile +```bash +NODE_MAJOR=20 -We use [Turnstile](https://www.cloudflare.com/products/turnstile/) as our captcha service. +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg +echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list +sudo apt-get update +sudo apt-get install -y nodejs +``` -In order to validate local login requests, you will need to setup a local instance of Turnstile. +#### Pnpm -You can go to cloudflare's website and register for a free account and then create a new application. +```bash +curl -fsSL https://get.pnpm.io/install.sh | bash - +``` -Set the domain to `localhost` and the widget type to managed. +#### Docker -Then create a `.env.local` file in the `frontend/website` folder and add the following: +```bash +sudo apt-get install -y docker.io -``` -VITE_CF_TURNSTILE_KEY= -VITE_GQL_ENDPOINT=http://localhost:8080/v1/gql -VITE_GQL_WS_ENDPOINT=ws://localhost:8080/v1/gql -VITE_GQL_VERSION=1.0 +DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} +mkdir -p $DOCKER_CONFIG/cli-plugins +curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose +chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose ``` -Then you can export the following environment variables: +To run Docker without sudo: ```bash -export SCUF_TURNSTILE_SECRET_KEY= +sudo groupadd docker +sudo usermod -aG docker $(whoami) ``` -Then when you start the API server it will use the local instance of Turnstile. +#### C External Libraries -## Monorepo +```bash +sudo apt-get update +sudo apt-get install pkg-config software-properties-common meson ninja-build nasm clang cmake make build-essential yasm autoconf automake libtool -For starters, you will notice that this project is a [monorepo](https://semaphoreci.com/blog/what-is-monorepo). +git clone https://github.com/ScuffleTV/external.git --depth 1 --recurse-submodule /tmp/scuffle-external +sudo /tmp/scuffle-external/build.sh --prefix /usr/local +sudo rm -rf /tmp/scuffle-external +``` -This means that all our code for every project is stored here in this repo. +## Setting up the project -We opted to use a monorepo for a few reasons: +After installation, clone the project and set up dependencies: -- We can minimize code duplication since we can share everything between services and products. -- We can easily test and integrate the entire platform. -- We can easily maintain and contribute to multiple projects within a single PR or ticket. +```bash +git clone --recurse-submodules https://github.com/ScuffleTV/scuffle.git scuffle +cd scuffle +mask bootstrap +``` -Monorepos come at a cost of a more complex build system. +The bootstrap command will handle: -## Commit Messages +- Dependency installation +- Development environment setup +- .env file setup -When we make commits to our `main` branch we have a few conventions we would like you to follow. +## Development Environment -Every commit you commit must be able to be compiled successfully, and also must be formatted. +We utilize: -Commits must be in the format specified here https://karma-runner.github.io/6.4/dev/git-commit-msg.html +- [CockroachDB](https://www.cockroachlabs.com/) as our database. +- [NATs](https://nats.io/) +- [S3](https://aws.amazon.com/s3/) (or any S3-compatible service; we use [MiniIO](https://min.io/) for development) -`doc(api): Added documentation for xyz` +To run local third-party services: -where the format is basically +```bash +mask dev up +``` -`type(scope): ` +To shut them down: -Then in the commit message body, you can give a more detailed description and link to the ticket that this commit aims to resolve. +```bash +mask dev down +``` -`Closes #1, #2` +### Database Migrations -We then would like you to mention breaking changes if any. +We employ sqlx-cli for database migrations. To run migrations: -``` -Breaking changes: - -`abc` is no longer supported and has been replaced with `xyz` +```bash +mask dev migrate ``` -If you have any questions regarding how commit messages should be formatted please ask. +### Turnstile -## Pull Requests +We use [Turnstile](https://www.cloudflare.com/products/turnstile/) for captcha services. For local login request validation, set up a local Turnstile instance. Instructions are provided in the guide. -Each commit in a pull request should resolve one or more tickets. +## Dev Env Setup (NOTE: this will change) -There should be one commit per ticket. If we need more tickets then we can create sub-issues and tasks around those. +
-So the relationship between tickets to commits is `many to one` where we can have many tickets in a single commit but only one commit per ticket. +Expand -You should try and break up the commits as one ticket per commit but sometimes the trivial tickets might be small enough that we can just combine them into a single commit. +There are currently three major components to get the dev environment up and running -A maintainer and or reviewer will advise you on what you should do to make your PR mergeable. +### Video API -However, you do not need to do this for the development stage of your PR. While developing you can commit as many times as you want with any names you like, however, once it is ready for merge someone will ask you to squash your commits into tickets and fix up the naming on them. Once that is done and CI passes we can then merge your contributions! +Create a local/video-api-config.toml -Squashing commits can be done with the following command: +```toml +[grpc] +bind_address = "127.0.0.1:0" +[database] +uri = "postgres://root@localhost:5432/scuffle_video" ``` -git rebase -i HEAD~ + +```bash +cargo run --bin video-api -- --config-file local/video-api-config.toml ``` -or +You will need to generate organization id and access token via the video-cli + +Create an organization +```bash +cargo run --bin video-cli -- --config-file local/video-api-config.toml organization create --name ``` -git rebase -i + +Create an access token using the organization id just generated + +```bash +cargo run --bin video-cli -- --config-file local/video-api-config.toml --organization-id XXXXXXXXXXXXXXXXXXXXXXXXXX access-token create --scopes all:admin ``` -Then you can change the `pick` to `squash` for all the commits you want to squash into the first commit. Then you can change the commit message to the ticket number and description. +### Platform API -## Code Formatting +Create a local/platform-api-config.toml -Formatting is not required during the development stage but it is encouraged since it makes it easier to review your PR. Once we get to the merging phase we would require you to format the PR so it is ready to be merged. +Add the ids you generated in previous step -## Testing +```toml +[grpc] +bind_address = "127.0.0.1:0" -Each subproject will have its testing requirements. Please read the README for each subproject to see what is required. +[database] +uri = "postgres://root@localhost:5432/scuffle_platform" -Integration tests will run on your PR to ensure nothing else breaks during your implementation. +[video_api] +organization_id = "XXXXXXXXXXXXXXXXXXXXXXXXXX" +access_key = "XXXXXXXXXXXXXXXXXXXXXXXXXX" +secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXX" +``` -## Documentation +```bash +cargo run --bin platform-api -- --config-file local/platform-api-config.toml +``` -When you make a PR, you should also update the documentation for the subproject you are working on. This is not required during the development stage, but it is encouraged since it makes it easier to review your PR. Once we get to the merging phase, we will require you to update the documentation, so it is ready to be merged. +### Website -## Questions +The website uses vite + svelte and uses pnpm to run in dev mode -If you have any questions, please ask in the [discord server](https://scuffle.tv/discord) or create an issue on the repo or in the discussion section +```bash +cd platform/website +pnpm run dev +``` + +
+ +## Questions -Please do not hesitate to ask questions; we are here to help you and make sure you are comfortable contributing to this project. If you need help following the design documents or need clarification about the codebase, please ask us, and we will help you. +For any questions, join our [discord server](https://discord.gg/scuffle), create an issue on the repo, or engage in the discussion section. We're here to assist and ensure a smooth contribution process. -## Thank you +## Thank You -Thank you for taking the time to read this document and for contributing to this project. We are very excited to have you on board, and we hope you enjoy your time here. +Your interest and contributions are invaluable. We're thrilled to have you on board and hope you find the experience rewarding. diff --git a/Cargo.lock b/Cargo.lock index c22dd9346..b529cdc9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ dependencies = [ [[package]] name = "aac" -version = "0.1.0" +version = "0.0.1" dependencies = [ "byteorder", "bytes", @@ -25,9 +25,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -40,9 +40,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -51,25 +51,32 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "aligned-vec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" + [[package]] name = "allocator-api2" version = "0.2.16" @@ -78,7 +85,7 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "amf0" -version = "0.1.0" +version = "0.0.1" dependencies = [ "byteorder", "bytes", @@ -87,54 +94,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "amq-protocol" -version = "7.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d40d8b2465c7959dd40cee32ba6ac334b5de57e9fca0cc756759894a4152a5d" -dependencies = [ - "amq-protocol-tcp", - "amq-protocol-types", - "amq-protocol-uri", - "cookie-factory", - "nom", - "serde", -] - -[[package]] -name = "amq-protocol-tcp" -version = "7.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cb2100adae7da61953a2c3a01935d86caae13329fadce3333f524d6d6ce12e2" -dependencies = [ - "amq-protocol-uri", - "tcp-stream", - "tracing 0.1.37", -] - -[[package]] -name = "amq-protocol-types" -version = "7.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "156ff13c8a3ced600b4e54ed826a2ae6242b6069d00dd98466827cef07d3daff" -dependencies = [ - "cookie-factory", - "nom", - "serde", - "serde_json", -] - -[[package]] -name = "amq-protocol-uri" -version = "7.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "751bbd7d440576066233e740576f1b31fdc6ab86cfabfbd48c548de77eca73e4" -dependencies = [ - "amq-protocol-types", - "percent-encoding", - "url", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -152,122 +111,89 @@ dependencies = [ [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "1.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" - -[[package]] -name = "api" -version = "0.1.0" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" dependencies = [ - "anyhow", - "arc-swap", - "argon2", - "async-graphql", - "async-stream", - "bitmask-enum", - "chrono", - "common", - "config", - "dotenvy", - "email_address", - "fred", - "futures", - "futures-util", - "hmac", - "http", - "hyper", - "hyper-tungstenite", - "jwt", - "lapin", - "negative-impl", - "portpicker", - "prost", - "prost-build", - "rand", - "reqwest", - "routerify", - "serde", - "serde_json", - "serial_test", - "sha2", - "sqlx", - "tempfile", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tonic", - "tonic-build", - "tracing 0.1.37", - "uuid", + "backtrace", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arc-swap" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] -name = "arcstr" -version = "1.1.5" +name = "arg_enum_proc_macro" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f907281554a3d0312bb7aab855a8e0ef6cbf1614d06de54105039ca8b34460e" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] [[package]] name = "argon2" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e554a8638bdc1e4eae9984845306cc95f8a9208ba8d49c3859fd958b46774d" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", @@ -276,109 +202,60 @@ dependencies = [ ] [[package]] -name = "ascii_utils" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" -dependencies = [ - "async-lock", - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.3.1" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] -name = "async-global-executor-trait" -version = "2.1.0" +name = "ascii_utils" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33dd14c5a15affd2abcff50d84efd4009ada28a860f01c14f9d654f3e81b3f75" -dependencies = [ - "async-global-executor", - "async-trait", - "executor-trait", -] +checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" [[package]] name = "async-graphql" -version = "5.0.10" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35ef8f9be23ee30fe1eb1cf175c689bc33517c6c6d0fd0669dade611e5ced7f" +checksum = "261fa27d5bff5afdf7beff291b3bc73f99d1529804c70e51b0fbc51e70b1c6a9" dependencies = [ "async-graphql-derive", "async-graphql-parser", "async-graphql-value", "async-stream", "async-trait", - "base64 0.13.1", + "base64 0.21.7", "bytes", "chrono", "fast_chemail", "fnv", - "futures-channel", - "futures-timer", "futures-util", "handlebars", - "http", - "indexmap 1.9.3", - "lru", + "http 1.1.0", + "indexmap 2.2.6", + "lru 0.7.8", "mime", "multer", "num-traits", "once_cell", - "opentelemetry", "pin-project-lite", "regex", "serde", "serde_json", "serde_urlencoded", "sha2", - "static_assertions", + "static_assertions_next", "tempfile", "thiserror", - "tracing 0.1.37", + "tracing", "tracing-futures", - "uuid", ] [[package]] name = "async-graphql-derive" -version = "5.0.10" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0f6ceed3640b4825424da70a5107e79d48d9b2bc6318dfc666b2fc4777f8c4" +checksum = "3188809947798ea6db736715a60cf645ba3b87ea031c710130e1476b48e45967" dependencies = [ "Inflector", "async-graphql-parser", @@ -386,15 +263,16 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "strum", + "syn 2.0.58", "thiserror", ] [[package]] name = "async-graphql-parser" -version = "5.0.10" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc308cd3bc611ee86c9cf19182d2b5ee583da40761970e41207f088be3db18f" +checksum = "d4e65a0b83027f35b2a5d9728a098bc66ac394caa8191d2c65ed9eb2985cf3d8" dependencies = [ "async-graphql-value", "pest", @@ -404,55 +282,48 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "5.0.10" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d461325bfb04058070712296601dfe5e5bd6cdff84780a0a8c569ffb15c87eb3" +checksum = "68e40849c29a39012d38bff87bfed431f1ed6c53fbec493294c1045d61a7ae75" dependencies = [ "bytes", - "indexmap 1.9.3", + "indexmap 2.2.6", "serde", "serde_json", ] [[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix 0.37.23", - "slab", - "socket2 0.4.9", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-reactor-trait" -version = "1.1.0" +name = "async-nats" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6012d170ad00de56c9ee354aef2e358359deb1ec504254e0e5a3774771de0e" +checksum = "dbc1f1a75fd07f0f517322d103211f12d757658e91676def9a2e688774656c60" dependencies = [ - "async-io", - "async-trait", - "futures-core", - "reactor-trait", + "base64 0.21.7", + "bytes", + "futures", + "http 0.2.12", + "memchr", + "nkeys", + "nuid", + "once_cell", + "rand", + "regex", + "ring", + "rustls 0.21.10", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.4", + "rustls-webpki 0.101.7", + "serde", + "serde_json", + "serde_nanos", + "serde_repr", + "thiserror", + "time", + "tokio", + "tokio-retry", + "tokio-rustls 0.24.1", + "tracing", + "url", ] [[package]] @@ -474,133 +345,528 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - [[package]] name = "async-trait" -version = "0.1.71" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] [[package]] -name = "atoi" -version = "2.0.0" +name = "autocfg" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "av1" +version = "0.0.1" dependencies = [ - "num-traits", + "byteorder", + "bytes", + "bytesio", ] [[package]] -name = "atomic-waker" -version = "1.1.1" +name = "av1-grain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom", + "num-rational", + "v_frame", +] [[package]] -name = "autocfg" -version = "1.1.0" +name = "aws-config" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "48730d0b4c3d91c43d0d37168831d9fd0e065ad4a889a2ee9faf8d34c3d2804d" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "hyper 0.14.28", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] [[package]] -name = "av1" -version = "0.1.0" +name = "aws-credential-types" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8587ae17c8e967e4b05a62d495be2fb7701bec52a97f7acfe8a29f938384c8" dependencies = [ - "byteorder", - "bytes", - "bytesio", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", ] [[package]] -name = "axum" -version = "0.6.18" +name = "aws-runtime" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +checksum = "c4ee6903f9d0197510eb6b44c4d86b493011d08b4992938f7b9be0333b6685aa" dependencies = [ - "async-trait", - "axum-core", - "bitflags 1.3.2", + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper", - "tower", - "tower-layer", - "tower-service", + "tracing", + "uuid", ] [[package]] -name = "axum-core" -version = "0.3.4" +name = "aws-sdk-s3" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "644c5939c1b78097d37f3341708978d68490070d4b0f8fa91f0878678c06a7ef" dependencies = [ - "async-trait", + "ahash 0.8.11", + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", "bytes", - "futures-util", - "http", - "http-body", - "mime", - "rustversion", - "tower-layer", - "tower-service", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http-body 0.4.6", + "lru 0.12.3", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", ] [[package]] -name = "az" -version = "1.2.1" +name = "aws-sdk-sso" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "b2be5ba83b077b67a6f7a1927eb6b212bf556e33bd74b5eaa5aa6e421910803a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] [[package]] -name = "backtrace" -version = "0.3.68" +name = "aws-sdk-ssooidc" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "022ca669825f841aef17b12d4354ef2b8651e4664be49f2d9ea13e4062a80c9f" dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", ] [[package]] -name = "base64" -version = "0.13.1" +name = "aws-sdk-sts" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "8e4a5f5cb007347c1ab34a6d56456301dfada921fc9e57d687ecb08baddd11ff" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] [[package]] -name = "base64" -version = "0.21.2" +name = "aws-sigv4" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d6f29688a4be9895c0ba8bef861ad0c0dac5c15e9618b9b7a6c233990fc263" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.1.0", + "once_cell", + "p256 0.11.1", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "83fa43bc04a6b2441968faeab56e68da3812f978a670a5db32accbdcafddd12f" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6363078f927f612b970edf9d1903ef5cef9a64d1e8423525ebb1f0a1633c858" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f10fa66956f01540051b0aa7ad54574640f748f9839e843442d99b970d3aff9" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de34bcfa1fb3c82a80e252a753db34a6658e07f23d3a5b3fc96919518fa7a3f5" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.0", + "hyper 0.14.28", + "hyper-rustls", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.10", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc56a5c96ec741de6c5e6bf1ce6948be969d6506dfa9c39cffc284e31e4979b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe14dceea1e70101d38fbf2a99e6a34159477c0fb95e68e05c66bd7ae4c3729" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872c68cf019c0e4afc5de7753c4f7288ce4b71663212771bf5e4542eb9346ca9" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb278e322f16f59630a83b6b2dc992a0b48aa74ed47b4130f193fae0053d713" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 0.2.12", + "rustc_version", + "tracing", +] + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] [[package]] name = "base64ct" @@ -608,6 +874,82 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bcder" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" +dependencies = [ + "bytes", + "smallvec", +] + +[[package]] +name = "binary-helper" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-nats", + "async-stream", + "async-trait", + "aws-config", + "aws-credential-types", + "aws-sdk-s3", + "aws-smithy-types", + "bytes", + "deadpool-postgres", + "fred", + "futures-util", + "http-body 1.0.0", + "hyper 1.2.0", + "once_cell", + "pb", + "pin-project", + "postgres-from-row", + "postgres-types", + "prost", + "rustls 0.22.3", + "rustls-pemfile 2.1.2", + "scuffle-config", + "scuffle-utils", + "serde", + "thiserror", + "tokio", + "tokio-postgres", + "tokio-postgres-rustls", + "tonic", + "tower-layer", + "tracing", + "tracing-subscriber", + "ulid", +] + +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -616,23 +958,26 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" -dependencies = [ - "serde", -] +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bitmask-enum" -version = "2.2.1" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78d456f91b4c1fdebf2698214e599fec3d7f8b46e3140fb254a9ea88c970ab0a" +checksum = "9990737a6d5740ff51cdbbc0f0503015cb30c390f6623968281eb214a520cfc0" dependencies = [ "quote", - "syn 2.0.26", + "syn 2.0.58", ] +[[package]] +name = "bitstream-io" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c9989a51171e2e81038ab168b6ae22886fe9ded214430dbb4f41c28cf176da" + [[package]] name = "blake2" version = "0.10.6" @@ -652,627 +997,835 @@ dependencies = [ ] [[package]] -name = "block-padding" +name = "built" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41bfbdb21256b87a8b5e80fab81a8eed158178e812fd7ba451907518b2742f16" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "bytesio" +version = "0.0.1" +dependencies = [ + "byteorder", + "bytes", + "futures", + "scuffle-utils", + "tokio", + "tokio-stream", + "tokio-util", +] + +[[package]] +name = "cc" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.4", +] + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie-factory" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" dependencies = [ - "generic-array", + "futures", ] [[package]] -name = "blocking" -version = "1.3.1" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", + "core-foundation-sys", + "libc", ] [[package]] -name = "bumpalo" -version = "3.13.0" +name = "core-foundation-sys" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] [[package]] -name = "bytemuck" -version = "1.13.1" +name = "crc16" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" [[package]] -name = "byteorder" -version = "1.4.3" +name = "crc32c" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2" +dependencies = [ + "rustc_version", +] [[package]] -name = "bytes" +name = "crc32fast" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ - "serde", + "cfg-if", ] [[package]] -name = "bytes-utils" -version = "0.1.3" +name = "crossbeam-channel" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ - "bytes", - "either", + "crossbeam-utils", ] [[package]] -name = "bytesio" -version = "0.0.1" +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "byteorder", - "bytes", - "common", - "futures", - "tokio", - "tokio-stream", - "tokio-util", + "crossbeam-epoch", + "crossbeam-utils", ] [[package]] -name = "casey" -version = "0.4.0" +name = "crossbeam-epoch" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614586263949597dcc18675da12ef9b429135e13628d92eb8b8c6fa50ca5656b" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "syn 1.0.109", + "crossbeam-utils", ] [[package]] -name = "cbc" -version = "0.1.2" +name = "crossbeam-queue" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cipher", + "crossbeam-utils", ] [[package]] -name = "cc" -version = "1.0.79" +name = "crossbeam-utils" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] -name = "cfg-if" -version = "1.0.0" +name = "crunchy" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] -name = "chrono" -version = "0.4.26" +name = "crypto-bigint" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "winapi", + "generic-array", + "rand_core", + "subtle", + "zeroize", ] [[package]] -name = "cipher" -version = "0.4.4" +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "crypto-common", - "inout", + "generic-array", + "rand_core", + "subtle", + "zeroize", ] [[package]] -name = "clap" -version = "4.3.12" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eab9e8ceb9afdade1ab3f0fd8dbce5b1b2f468ad653baf10e771781b2b67b73" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "clap_builder", + "generic-array", + "typenum", ] [[package]] -name = "clap_builder" -version = "4.3.12" +name = "curve25519-dalek" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2763db829349bf00cfc06251268865ed4363b93a943174f638daf3ecdba2cd" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "once_cell", - "strsim", + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", ] [[package]] -name = "clap_lex" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" - -[[package]] -name = "colorchoice" -version = "1.0.0" +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - -[[package]] -name = "common" -version = "0.1.0" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "anyhow", - "arc-swap", - "async-stream", - "async-trait", - "config", - "futures", - "http", - "lapin", - "log", - "once_cell", - "portpicker", - "prost", - "serde", - "tempfile", - "thiserror", - "tokio", - "tokio-util", - "tonic", - "tonic-build", - "tower", - "tracing 0.1.37", - "tracing-log", - "tracing-subscriber", - "trust-dns-resolver", + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] -name = "concurrent-queue" -version = "2.2.0" +name = "darling" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "config" -version = "0.1.0" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ - "clap", - "config_derive", - "convert_case", - "humantime", - "num-order", - "serde", - "serde-value", - "serde_ignored", - "serde_json", - "serde_path_to_error", - "serde_yaml", - "thiserror", - "toml", - "tracing 0.1.37", + "darling_core", + "darling_macro", ] [[package]] -name = "config_derive" -version = "0.1.0" +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", - "syn 2.0.26", + "strsim 0.10.0", + "syn 2.0.58", ] [[package]] -name = "config_test" -version = "0.1.0" +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ - "config", - "serde", + "darling_core", + "quote", + "syn 2.0.58", ] [[package]] -name = "const-oid" -version = "0.9.4" +name = "data-encoding" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "795bc6e66a8e340f075fcf6227e417a2dc976b92b91f3cdc778bb858778b6747" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] -name = "convert_case" -version = "0.6.0" +name = "deadpool" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490" dependencies = [ - "unicode-segmentation", + "async-trait", + "deadpool-runtime", + "num_cpus", + "tokio", ] [[package]] -name = "cookie-factory" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" - -[[package]] -name = "core-foundation" -version = "0.9.3" +name = "deadpool-postgres" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "bda39fa1cfff190d8924d447ad04fd22772c250438ca5ce1dfb3c80621c05aaa" dependencies = [ - "core-foundation-sys", - "libc", + "deadpool", + "tokio", + "tokio-postgres", + "tracing", ] [[package]] -name = "core-foundation-sys" -version = "0.8.4" +name = "deadpool-runtime" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" +dependencies = [ + "tokio", +] [[package]] -name = "cpufeatures" -version = "0.2.9" +name = "default-net" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "0c5a6569a908354d49b10db3c516d69aca1eccd97562fd31c98b13f00b73ca66" dependencies = [ + "dlopen2", "libc", + "memalloc", + "netlink-packet-core", + "netlink-packet-route", + "netlink-sys", + "once_cell", + "system-configuration", + "windows", ] [[package]] -name = "crc" -version = "3.0.1" +name = "der" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ - "crc-catalog", + "const-oid", + "zeroize", ] [[package]] -name = "crc-catalog" -version = "2.2.0" +name = "der" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] [[package]] -name = "crc16" -version = "0.4.0" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] [[package]] -name = "crossbeam-channel" -version = "0.5.8" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "cfg-if", - "crossbeam-utils", + "block-buffer", + "const-oid", + "crypto-common", + "subtle", ] [[package]] -name = "crossbeam-queue" -version = "0.3.8" +name = "dlopen2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" dependencies = [ - "cfg-if", - "crossbeam-utils", + "libc", + "once_cell", + "winapi", ] [[package]] -name = "crossbeam-utils" -version = "0.8.16" +name = "dotenvy" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "crunchy" -version = "0.2.2" +name = "dunce" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] -name = "crypto-common" -version = "0.1.6" +name = "ecdsa" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "generic-array", - "typenum", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", ] [[package]] -name = "darling" -version = "0.14.4" +name = "ecdsa" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "darling_core", - "darling_macro", + "der 0.7.9", + "digest", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", ] [[package]] -name = "darling_core" -version = "0.14.4" +name = "ed25519" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", + "signature 2.2.0", ] [[package]] -name = "darling_macro" -version = "0.14.4" +name = "ed25519-dalek" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", + "curve25519-dalek", + "ed25519", + "sha2", + "signature 2.2.0", + "subtle", ] [[package]] -name = "dashmap" -version = "5.5.0" +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "cfg-if", - "hashbrown 0.14.0", - "lock_api", - "once_cell", - "parking_lot_core", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "pkcs8 0.9.0", + "rand_core", + "sec1 0.3.0", + "subtle", + "zeroize", ] [[package]] -name = "data-encoding" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" - -[[package]] -name = "der" -version = "0.7.7" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ed52955ce76b1554f509074bb357d3fb8ac9b51288a65a3fd480d1dfba946" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "const-oid", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest", + "ff 0.13.0", + "generic-array", + "group 0.13.0", + "hkdf", "pem-rfc7468", + "pkcs8 0.10.2", + "rand_core", + "sec1 0.7.3", + "subtle", "zeroize", ] [[package]] -name = "des" -version = "0.8.1" +name = "encoding_rs" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ - "cipher", + "cfg-if", ] [[package]] -name = "digest" -version = "0.10.7" +name = "enum-as-inner" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] -name = "doc-comment" -version = "0.3.3" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "dotenvy" -version = "0.15.7" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] -name = "edge" -version = "0.1.0" +name = "exp_golomb" +version = "0.0.1" dependencies = [ - "anyhow", - "async-stream", - "async-trait", "bytes", - "chrono", - "common", - "config", - "dotenvy", - "fred", - "futures", - "futures-util", - "hyper", - "native-tls", - "nix", - "portpicker", - "prost", - "prost-build", - "routerify", - "serde", - "serde_json", - "serial_test", - "sha2", - "tempfile", - "tokio", - "tokio-native-tls", - "tokio-stream", - "tokio-util", - "tonic", - "tonic-build", - "tracing 0.1.37", - "url", - "url-parse", - "uuid", + "bytesio", ] [[package]] -name = "either" -version = "1.8.1" +name = "exr" +version = "1.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" dependencies = [ - "serde", + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", ] [[package]] -name = "email_address" -version = "0.2.4" +name = "fallible-iterator" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" -dependencies = [ - "serde", -] +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] -name = "encoding_rs" -version = "0.8.32" +name = "fallible_collections" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "a88c69768c0a15262df21899142bc6df9b9b823546d4b4b9a7bc2d6c448ec6fd" dependencies = [ - "cfg-if", + "hashbrown 0.13.2", ] [[package]] -name = "enum-as-inner" -version = "0.5.1" +name = "fast_chemail" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" +checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 1.0.109", + "ascii_utils", ] [[package]] -name = "env_logger" -version = "0.10.0" +name = "fast_image_resize" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "c9d450fac8a334ad72825596173f0f7767ff04dd6e3d59c49c894c4bc2957e8b" dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", + "cfg-if", + "num-traits", + "thiserror", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "fastrand" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] -name = "errno" -version = "0.3.1" +name = "fdeflate" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", + "simd-adler32", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "ff" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ - "cc", - "libc", + "rand_core", + "subtle", ] [[package]] -name = "etcetera" -version = "0.8.0" +name = "ff" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "cfg-if", - "home", - "windows-sys", + "rand_core", + "subtle", ] [[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +name = "ffmpeg" +version = "0.1.0" +dependencies = [ + "bytes", + "crossbeam-channel", + "ffmpeg-sys-next", + "libc", + "scuffle-utils", + "tokio", + "tracing", +] [[package]] -name = "executor-trait" -version = "2.1.0" +name = "ffmpeg-sys-next" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a1052dd43212a7777ec6a69b117da52f5e52f07aec47d00c1a2b33b85d06b08" +checksum = "c2529ad916d08c3562c754c21bc9b17a26c7882c0f5706cc2cd69472175f1620" dependencies = [ - "async-trait", + "bindgen", + "cc", + "libc", + "num_cpus", + "pkg-config", + "vcpkg", ] [[package]] -name = "exp_golomb" -version = "0.1.0" -dependencies = [ - "bytes", - "bytesio", -] +name = "fiat-crypto" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" [[package]] -name = "fast_chemail" -version = "0.9.6" +name = "file-format" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" -dependencies = [ - "ascii_utils", -] +checksum = "4ba1b81b3c213cf1c071f8bf3b83531f310df99642e58c48247272eef006cae5" [[package]] -name = "fastrand" -version = "1.9.0" +name = "finl_unicode" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "fixed" -version = "1.23.1" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79386fdcec5e0fde91b1a6a5bcd89677d1f9304f7f986b154a1b9109038854d9" +checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048" dependencies = [ "az", "bytemuck", @@ -1286,6 +1839,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -1297,13 +1860,10 @@ dependencies = [ [[package]] name = "flume" -version = "0.10.14" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ - "futures-core", - "futures-sink", - "pin-project", "spin 0.9.8", ] @@ -1329,63 +1889,51 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fred" -version = "6.3.0" -source = "git+https://github.com/ScuffleTV/fred.rs#bd8d218c789b6160b7e590af108fe7f1e36faaf8" +version = "8.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8e3a1339ed45ad8fde94530c4bdcbd5f371a3c6bd3bf57682923792830aa37" dependencies = [ "arc-swap", - "arcstr", "async-trait", "bytes", "bytes-utils", - "cfg-if", + "crossbeam-queue", "float-cmp", "futures", - "lazy_static", "log", - "native-tls", "parking_lot", "rand", "redis-protocol", + "rustls 0.22.3", + "rustls-native-certs 0.7.0", + "rustls-webpki 0.102.2", "semver", - "sha-1", + "socket2", "tokio", - "tokio-native-tls", + "tokio-rustls 0.25.0", "tokio-stream", "tokio-util", + "trust-dns-resolver", "url", + "urlencoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1398,9 +1946,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1408,87 +1956,55 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -1510,53 +2026,169 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif-dispose" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "781005a5985b4c723fd3e6586df79d823151846ebcbcf2fcc7e3d3fba18c2d51" +dependencies = [ + "gif", + "imgref", + "rgb", +] + +[[package]] +name = "gifski" +version = "1.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6a6c5eab296009821c25867a4eaa9fca77df08bade4eed27bc5c211b3e6466f" +dependencies = [ + "clap", + "crossbeam-channel", + "crossbeam-utils", + "dunce", + "gif", + "gif-dispose", + "imagequant", + "imgref", + "lodepng", + "loop9", + "natord", + "num-traits", + "ordered-channel", + "pbr", + "quick-error 2.0.1", + "resize", + "rgb", + "wild", ] [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core", + "subtle", +] [[package]] -name = "glob" -version = "0.3.1" +name = "h2" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] [[package]] name = "h2" -version = "0.3.20" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 1.9.3", + "http 1.1.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", - "tracing 0.1.37", + "tracing", ] [[package]] name = "h264" -version = "0.1.0" +version = "0.0.1" dependencies = [ "byteorder", "bytes", @@ -1566,7 +2198,7 @@ dependencies = [ [[package]] name = "h265" -version = "0.1.0" +version = "0.0.1" dependencies = [ "byteorder", "bytes", @@ -1576,9 +2208,9 @@ dependencies = [ [[package]] name = "half" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -1586,9 +2218,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "4.3.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" dependencies = [ "log", "pest", @@ -1604,26 +2236,26 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.8", ] [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.3", - "allocator-api2", + "ahash 0.8.11", ] [[package]] -name = "hashlink" -version = "0.8.3" +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "hashbrown 0.14.0", + "ahash 0.8.11", + "allocator-api2", ] [[package]] @@ -1631,15 +2263,18 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -1649,9 +2284,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -1665,15 +2300,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" -dependencies = [ - "windows-sys", -] - [[package]] name = "hostname" version = "0.3.1" @@ -1687,9 +2313,20 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1698,12 +2335,35 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", - "http", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -1715,9 +2375,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" @@ -1727,78 +2387,120 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", - "tracing 0.1.37", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.4", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.28", + "log", + "rustls 0.21.10", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.28", "pin-project-lite", "tokio", "tokio-io-timeout", ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-tungstenite" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad" dependencies = [ - "bytes", - "hyper", - "native-tls", + "http-body-util", + "hyper 1.2.0", + "hyper-util", + "pin-project-lite", "tokio", - "tokio-native-tls", + "tokio-tungstenite", + "tungstenite", ] [[package]] -name = "hyper-tungstenite" -version = "0.10.0" +name = "hyper-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226df6fd0aece319a325419d770aa9d947defa60463f142cd82b329121f906a3" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ - "hyper", - "pin-project", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.2.0", + "pin-project-lite", + "socket2", "tokio", - "tokio-tungstenite", - "tungstenite", ] [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -1818,115 +2520,91 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ - "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] -name = "indexmap" -version = "1.9.3" +name = "image" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-traits", + "png", + "qoi", + "tiff", ] [[package]] -name = "indexmap" -version = "2.0.0" +name = "imagequant" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "85a7f142d232ccbdc00cbef49d17f45639aeb07d9bfe28e17c21dea3efac64e5" dependencies = [ - "equivalent", - "hashbrown 0.14.0", + "arrayvec", + "once_cell", + "rayon", + "rgb", + "thread_local", ] [[package]] -name = "ingest" -version = "0.1.0" -dependencies = [ - "aac", - "anyhow", - "async-stream", - "async-trait", - "bytes", - "bytesio", - "chrono", - "common", - "config", - "dotenvy", - "flv", - "futures", - "futures-util", - "hyper", - "lapin", - "mp4", - "native-tls", - "pnet", - "portpicker", - "prost", - "prost-build", - "rtmp", - "serde", - "serde_json", - "serial_test", - "tempfile", - "tokio", - "tokio-executor-trait", - "tokio-native-tls", - "tokio-reactor-trait", - "tonic", - "tonic-build", - "tracing 0.1.37", - "transmuxer", - "uuid", -] +name = "imgref" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] -name = "inout" -version = "0.1.3" +name = "indexmap" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ - "block-padding", - "generic-array", + "autocfg", + "hashbrown 0.12.3", ] [[package]] -name = "instant" -version = "0.1.12" +name = "indexmap" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ - "cfg-if", + "equivalent", + "hashbrown 0.14.3", + "serde", ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "interpolate_name" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ - "hermit-abi", - "libc", - "windows-sys", + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] @@ -1935,164 +2613,218 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.3", + "socket2", "widestring", - "windows-sys", - "winreg 0.50.0", + "windows-sys 0.48.0", + "winreg", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "ipnetwork" -version = "0.20.0" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ - "serde", + "either", ] [[package]] -name = "is-terminal" -version = "0.4.9" +name = "itoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix 0.38.4", - "windows-sys", -] +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "itertools" -version = "0.10.5" +name = "jobserver" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "f08474e32172238f2827bd160c67871cdb2801430f65c3979184dc362e3ca118" dependencies = [ - "either", + "libc", ] [[package]] -name = "itoa" -version = "1.0.9" +name = "jpeg-decoder" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] -name = "jwt" -version = "0.16.0" +name = "jwt-next" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +checksum = "89327f6d992ab1b3f6c908ee32cc0bb66068e2696da2cfe21a8764e400fe9c3b" dependencies = [ - "base64 0.13.1", + "base64 0.21.7", "crypto-common", "digest", + "ecdsa 0.16.9", "hmac", + "p256 0.13.2", + "p384", + "pem", + "rsa", "serde", "serde_json", "sha2", + "signature 2.2.0", ] [[package]] -name = "lapin" -version = "2.3.1" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f3067a1fcfbc3fc46455809c023e69b8f6602463201010f4ae5a3b572adb9dc" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "amq-protocol", - "async-global-executor-trait", - "async-reactor-trait", - "async-trait", - "executor-trait", - "flume", - "futures-core", - "futures-io", - "parking_lot", - "pinky-swear", - "reactor-trait", - "serde", - "tracing 0.1.37", - "waker-fn", + "spin 0.5.2", ] [[package]] -name = "lazy_static" -version = "1.4.0" +name = "lazycell" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libavif-sys" +version = "0.16.0+libavif.1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb05b7f9437ad312ad3dec250781fc95df54d4a1ec69d8b6cf5d503c5f5a51d" dependencies = [ - "spin 0.5.2", + "cmake", + "libc", + "libdav1d-sys", + "rav1e", ] [[package]] name = "libc" -version = "0.2.147" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libdav1d-sys" +version = "0.7.0+libdav1d.1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0de922cdce0f7aca62567c9c454cbf49e5b1a7a79a10f97a3130547692aca71" +dependencies = [ + "libc", +] + +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "libloading" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.4", +] [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] -name = "libsqlite3-sys" -version = "0.26.0" +name = "libwebp-sys2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "0e2ae528b6c8f543825990b24c00cfd8fe64dde126c8288f4972b18e3d558072" dependencies = [ "cc", + "cfg-if", + "libc", "pkg-config", "vcpkg", ] [[package]] name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", ] +[[package]] +name = "lodepng" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a42d298694b14401847de29abd44adf278b42e989e516deac7b72018400002d8" +dependencies = [ + "crc32fast", + "fallible_collections", + "flate2", + "libc", + "rgb", +] + [[package]] name = "log" -version = "0.4.19" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "loop9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] [[package]] name = "lru" @@ -2103,6 +2835,15 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "lru" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +dependencies = [ + "hashbrown 0.14.3", +] + [[package]] name = "lru-cache" version = "0.1.2" @@ -2128,40 +2869,41 @@ dependencies = [ ] [[package]] -name = "matches" -version = "0.1.10" +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] -name = "matchit" -version = "0.7.0" +name = "maybe-rayon" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", +] [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest", ] [[package]] -name = "memchr" -version = "2.5.0" +name = "memalloc" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" [[package]] -name = "memoffset" -version = "0.7.1" +name = "memchr" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" @@ -2177,22 +2919,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", + "simd-adler32", ] [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2204,7 +2947,6 @@ dependencies = [ "byteorder", "bytes", "bytesio", - "casey", "fixed", "h264", "h265", @@ -2215,14 +2957,14 @@ dependencies = [ [[package]] name = "multer" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219" dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.1.0", "httparse", "log", "memchr", @@ -2233,58 +2975,94 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] -name = "native-tls" -version = "0.2.11" +name = "nasm-rs" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "fe4d98d0065f4b1daf164b3eafb11974c94662e5e2396cf03f32d0bb5c17da51" dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "rayon", ] [[package]] -name = "negative-impl" -version = "0.1.4" +name = "natord" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681fa6e0925389292df5b310dc2b8f9407748194cedb9d6fb678adec155fb73c" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.26", + "anyhow", + "byteorder", + "netlink-packet-utils", ] [[package]] -name = "nix" -version = "0.26.2" +name = "netlink-packet-route" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" dependencies = [ + "anyhow", "bitflags 1.3.2", - "cfg-if", + "byteorder", "libc", - "memoffset", - "pin-utils", - "static_assertions", + "netlink-packet-core", + "netlink-packet-utils", ] [[package]] -name = "no-std-net" -version = "0.6.0" +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" +dependencies = [ + "bytes", + "libc", + "log", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nkeys" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" +checksum = "aad178aad32087b19042ee36dfd450b73f5f934fbfb058b59b198684dfec4c47" +dependencies = [ + "byteorder", + "data-encoding", + "ed25519", + "ed25519-dalek", + "getrandom", + "log", + "rand", + "signatory", +] [[package]] name = "nom" @@ -2296,6 +3074,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2306,6 +3090,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "nuid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc895af95856f929163a0aa20c26a78d26bfdc839f51b9d5aa7a5b79e52b7e83" +dependencies = [ + "rand", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -2323,32 +3127,37 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6a0fd4f737c707bd9086cc16c925f294943eb62eb71499e9fd4cf71f8b9f4e" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -2357,30 +3166,36 @@ dependencies = [ [[package]] name = "num-modular" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a5fe11d4135c3bcdf3a95b18b194afa9608a5f6ff034f5d857bc9a27fb0119" -dependencies = [ - "num-integer", - "num-traits", -] +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" [[package]] name = "num-order" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e81e321057a0370997b13e6638bba6bd7f6f426e1f8e9a2562490a28eb23e1bc" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" dependencies = [ - "libm", "num-modular", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", "libm", @@ -2398,183 +3213,182 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl-probe" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] -name = "openssl" -version = "0.10.55" +name = "ordered-channel" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "4f0bc569ca0974cb90125f52cf76f7b6ad3a072301beba78eb0aa4174c4964ed" dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "crossbeam-channel", ] [[package]] -name = "openssl-macros" -version = "0.1.1" +name = "ordered-float" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.26", + "num-traits", ] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "outref" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" [[package]] -name = "openssl-sys" -version = "0.9.90" +name = "overload" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] -name = "opentelemetry" -version = "0.19.0" +name = "p256" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4b8347cc26099d3aeee044065ecc3ae11469796b4d65d065a23a584ed92a6f" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ - "opentelemetry_api", - "opentelemetry_sdk", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2", ] [[package]] -name = "opentelemetry_api" -version = "0.19.0" +name = "p256" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "futures-channel", - "futures-util", - "indexmap 1.9.3", - "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", ] [[package]] -name = "opentelemetry_sdk" -version = "0.19.0" +name = "p384" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" dependencies = [ - "async-trait", - "crossbeam-channel", - "futures-channel", - "futures-executor", - "futures-util", - "once_cell", - "opentelemetry_api", - "percent-encoding", - "rand", - "thiserror", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder", + "sha2", ] [[package]] -name = "ordered-float" -version = "2.10.0" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "num-traits", + "lock_api", + "parking_lot_core", ] [[package]] -name = "overload" -version = "0.1.1" +name = "parking_lot_core" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] [[package]] -name = "p12" -version = "0.6.3" +name = "password-hash" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ - "cbc", - "cipher", - "des", - "getrandom", - "hmac", - "lazy_static", - "rc2", - "sha1", - "yasna", + "base64ct", + "rand_core", + "subtle", ] [[package]] -name = "parking" -version = "2.1.0" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] -name = "parking_lot" -version = "0.12.1" +name = "path-tree" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "8a3a8689ce29b9b1e4d2363bd5b2ee0f6765d6fb6973df48f24145c9325d4f6e" dependencies = [ - "lock_api", - "parking_lot_core", + "smallvec", ] [[package]] -name = "parking_lot_core" -version = "0.9.8" +name = "pb" +version = "0.0.1" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost", + "prost-build", + "quote", + "syn 2.0.58", + "tonic", + "tonic-build", + "ulid", + "uuid", + "walkdir", +] + +[[package]] +name = "pbr" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "ed5827dfa0d69b6c92493d6c38e633bbaa5937c153d0d7c28bf12313f8c6d514" dependencies = [ - "cfg-if", + "crossbeam-channel", "libc", - "redox_syscall", - "smallvec", - "windows-targets", + "winapi", ] [[package]] -name = "password-hash" -version = "0.5.0" +name = "peeking_take_while" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] -name = "paste" -version = "1.0.14" +name = "pem" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.0", + "serde", +] [[package]] name = "pem-rfc7468" @@ -2587,25 +3401,26 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.0" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" +checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" dependencies = [ + "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.0" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b" +checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" dependencies = [ "pest", "pest_generator", @@ -2613,22 +3428,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.0" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" +checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] [[package]] name = "pest_meta" -version = "2.7.0" +version = "2.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0" +checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" dependencies = [ "once_cell", "pest", @@ -2637,39 +3452,57 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 1.9.3", + "indexmap 2.2.6", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", ] [[package]] name = "pin-project" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2678,26 +3511,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pinky-swear" -version = "6.1.0" +name = "pkcs1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d894b67aa7a4bf295db5e85349078c604edaa6fa5c8721e8eca3c7729a27f2ac" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "doc-comment", - "flume", - "parking_lot", - "tracing 0.1.37", + "der 0.7.9", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] -name = "pkcs1" -version = "0.7.5" +name = "pkcs8" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.6.1", + "spki 0.6.0", ] [[package]] @@ -2706,132 +3537,212 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.9", + "spki 0.7.3", ] [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] -name = "pnet" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd959a8268165518e2bf5546ba84c7b3222744435616381df3c456fe8d983576" +name = "platform-api" +version = "0.0.1" dependencies = [ - "ipnetwork", - "pnet_base", - "pnet_datalink", - "pnet_packet", - "pnet_sys", - "pnet_transport", + "anyhow", + "arc-swap", + "argon2", + "async-graphql", + "async-nats", + "async-stream", + "async-trait", + "aws-config", + "aws-sdk-s3", + "base64 0.21.7", + "binary-helper", + "bitmask-enum", + "bytes", + "chrono", + "fred", + "futures", + "futures-util", + "hmac", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-tungstenite", + "hyper-util", + "jwt-next", + "multer", + "path-tree", + "pb", + "pin-project", + "postgres-from-row", + "postgres-types", + "prost", + "rand", + "reqwest", + "rustls 0.22.3", + "rustls-pemfile 2.1.2", + "scuffle-config", + "scuffle-utils", + "serde", + "serde_json", + "sha2", + "tempfile", + "thiserror", + "tokio", + "tokio-rustls 0.25.0", + "tokio-stream", + "tonic", + "totp-rs", + "tracing", + "ulid", + "uuid", ] [[package]] -name = "pnet_base" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872e46346144ebf35219ccaa64b1dffacd9c6f188cd7d012bd6977a2a838f42e" +name = "platform-image-processor" +version = "0.0.1" dependencies = [ - "no-std-net", + "anyhow", + "async-nats", + "async-trait", + "aws-config", + "aws-sdk-s3", + "binary-helper", + "byteorder", + "bytes", + "fast_image_resize", + "ffmpeg", + "file-format", + "futures", + "gifski", + "imgref", + "libavif-sys", + "libwebp-sys2", + "num_cpus", + "pb", + "png", + "postgres-from-row", + "prost", + "reqwest", + "rgb", + "scopeguard", + "scuffle-config", + "scuffle-utils", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tonic", + "tracing", + "ulid", ] [[package]] -name = "pnet_datalink" -version = "0.33.0" +name = "platforms" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c302da22118d2793c312a35fb3da6846cb0fab6c3ad53fd67e37809b06cdafce" -dependencies = [ - "ipnetwork", - "libc", - "pnet_base", - "pnet_sys", - "winapi", -] +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] -name = "pnet_macros" -version = "0.33.0" +name = "png" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a780e80005c2e463ec25a6e9f928630049a10b43945fea83207207d4a7606f4" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", ] [[package]] -name = "pnet_macros_support" -version = "0.33.0" +name = "portpicker" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d932134f32efd7834eb8b16d42418dac87086347d1bc7d142370ef078582bc" +checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" dependencies = [ - "pnet_base", + "rand", ] [[package]] -name = "pnet_packet" -version = "0.33.0" +name = "postgres-derive" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bde678bbd85cb1c2d99dc9fc596e57f03aa725f84f3168b0eaf33eeccb41706" +checksum = "83145eba741b050ef981a9a1838c843fa7665e154383325aa8b440ae703180a2" dependencies = [ - "glob", - "pnet_base", - "pnet_macros", - "pnet_macros_support", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] -name = "pnet_sys" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf7a58b2803d818a374be9278a1fe8f88fce14b936afbe225000cfcd9c73f16" +name = "postgres-from-row" +version = "0.5.2" +source = "git+https://github.com/ScuffleTV/postgres-from-row.git?branch=troy/from_fn#3a775f225aae7c0f54e404f3f07aa13fcec2cc9b" dependencies = [ - "libc", - "winapi", + "postgres-from-row-derive", + "tokio-postgres", ] [[package]] -name = "pnet_transport" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "813d1c0e4defbe7ee22f6fe1755f122b77bfb5abe77145b1b5baaf463cab9249" +name = "postgres-from-row-derive" +version = "0.5.2" +source = "git+https://github.com/ScuffleTV/postgres-from-row.git?branch=troy/from_fn#3a775f225aae7c0f54e404f3f07aa13fcec2cc9b" dependencies = [ - "libc", - "pnet_base", - "pnet_packet", - "pnet_sys", + "darling", + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] -name = "polling" -version = "2.8.0" +name = "postgres-protocol" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys", + "base64 0.21.7", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", ] [[package]] -name = "portpicker" -version = "0.1.1" +name = "postgres-types" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" +checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" dependencies = [ - "rand", + "bytes", + "chrono", + "fallible-iterator", + "postgres-derive", + "postgres-protocol", + "serde", + "serde_json", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2840,12 +3751,21 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.1.25" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", - "syn 1.0.109", + "syn 2.0.58", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", ] [[package]] @@ -2855,23 +3775,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" +dependencies = [ + "quote", + "syn 2.0.58", +] + [[package]] name = "prost" -version = "0.11.9" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" dependencies = [ "bytes", "prost-derive", @@ -2879,59 +3818,90 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.9" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +checksum = "80b776a1b2dc779f5ee0641f8ade0125bc1298dd41a9a0c16d8bd57b42d222b1" dependencies = [ "bytes", - "heck", + "heck 0.5.0", "itertools", - "lazy_static", "log", "multimap", + "once_cell", "petgraph", "prettyplease", "prost", "prost-types", "regex", - "syn 1.0.109", + "syn 2.0.58", "tempfile", - "which", ] [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" dependencies = [ "prost", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "qrcodegen" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" + +[[package]] +name = "qrcodegen-image" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59a7c7ddb94c99fa3942dd761dbb305bca462b71d7bd9bcb3f9ff4d454d5736" +dependencies = [ + "base64 0.22.0", + "image", + "qrcodegen", +] + [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" -version = "1.0.31" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -2967,23 +3937,60 @@ dependencies = [ ] [[package]] -name = "rc2" -version = "0.8.1" +name = "rav1e" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" +dependencies = [ + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av1-grain", + "bitstream-io", + "built", + "cc", + "cfg-if", + "interpolate_name", + "itertools", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "nasm-rs", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "once_cell", + "paste", + "profiling", + "rand", + "rand_chacha", + "scan_fmt", + "simd_helpers", + "system-deps", + "thiserror", + "v_frame", +] + +[[package]] +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "cipher", + "either", + "rayon-core", ] [[package]] -name = "reactor-trait" -version = "1.1.0" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "438a4293e4d097556730f4711998189416232f009c137389e0f961d2bc0ddc58" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "async-trait", - "futures-core", - "futures-io", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] @@ -3002,23 +4009,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.1" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.3", - "regex-syntax 0.7.4", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -3032,15 +4039,21 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.3", ] +[[package]] +name = "regex-lite" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -3049,103 +4062,133 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.21.2", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio", - "tokio-native-tls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.10.1", + "webpki-roots", + "winreg", +] + +[[package]] +name = "resize" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e29f584c07a8396c5e2eee0bd8d7aec5c8d9e0a3c2333806fd2ec1d2a5b080" +dependencies = [ + "rayon", + "rgb", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", ] [[package]] -name = "resolv-conf" -version = "0.7.0" +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hostname", - "quick-error", + "hmac", + "subtle", ] [[package]] -name = "ring" -version = "0.16.20" +name = "rgb" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted", - "web-sys", - "winapi", + "bytemuck", ] [[package]] -name = "routerify" -version = "3.0.0" +name = "ring" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496c1d3718081c45ba9c31fbfc07417900aa96f4070ff90dc29961836b7a9945" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ - "http", - "hyper", - "lazy_static", - "percent-encoding", - "regex", + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", ] [[package]] name = "rsa" -version = "0.9.2" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ - "byteorder", "const-oid", "digest", "num-bigint-dig", "num-integer", - "num-iter", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core", - "signature", - "spki", + "sha2", + "signature 2.2.0", + "spki 0.7.3", "subtle", "zeroize", ] @@ -3160,18 +4203,16 @@ dependencies = [ "bytes", "bytesio", "chrono", - "common", - "flv", "futures", - "h264", "hmac", "num-derive", "num-traits", "rand", + "scuffle-utils", "serde_json", "sha2", "tokio", - "tracing 0.1.37", + "tracing", "uuid", ] @@ -3182,54 +4223,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rustix" -version = "0.37.23" +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys", + "semver", ] [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.5.0", "errno", "libc", - "linux-raw-sys 0.4.3", - "windows-sys", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.5" +version = "0.21.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] [[package]] -name = "rustls-connector" -version = "0.18.3" +name = "rustls" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "060bcc1795b840d0e56d78f3293be5f652aa1611d249b0e63ffe19f4a8c9ae23" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", - "rustls", - "rustls-native-certs", - "rustls-webpki", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", ] [[package]] @@ -3239,72 +4283,223 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.0", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "base64 0.21.2", + "ring", + "untrusted", ] [[package]] name = "rustls-webpki" -version = "0.101.1" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scan_fmt" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", ] +[[package]] +name = "scuffle-config" +version = "0.0.1" +dependencies = [ + "clap", + "convert_case", + "humantime", + "num-order", + "scuffle_config_derive", + "serde", + "serde-value", + "serde_ignored", + "serde_json", + "serde_path_to_error", + "serde_yaml", + "thiserror", + "toml", + "tracing", + "ulid", + "uuid", +] + +[[package]] +name = "scuffle-utils" +version = "0.1.0" +dependencies = [ + "async-trait", + "bytes", + "const_format", + "deadpool-postgres", + "dotenvy", + "fnv", + "fred", + "futures", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body-util", + "hyper 1.2.0", + "path-tree", + "pin-project", + "portpicker", + "postgres-from-row", + "postgres-types", + "prost", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "tokio-postgres", + "tokio-util", + "tonic", + "tonic-build", + "tower", + "tracing", + "trust-dns-resolver", + "ulid", +] + +[[package]] +name = "scuffle_config_derive" +version = "0.0.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.9", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3315,9 +4510,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -3325,15 +4520,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.171" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -3348,52 +4543,94 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" -version = "1.0.171" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", +] + +[[package]] +name = "serde_derive_internals" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", ] [[package]] name = "serde_ignored" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c31d5c53fd39f208e770f5a20a0bb214dee2a8d0d8adba18e19ad95a482ca5" +checksum = "a8e319a36d1b52126a0d608f24e93b2d81297091818cd70625fcf50a15d84ddf" dependencies = [ "serde", ] [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_nanos" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93142f0367a4cc53ae0fead1bcda39e85beccfad3dcd717656cacab94b12985" +dependencies = [ + "serde", +] + [[package]] name = "serde_path_to_error" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -3412,58 +4649,22 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.23" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6075b41c7e3b079e5f246eb6094a44850d3a4c25a67c581c80796c80134012" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "itoa", "ryu", "serde", "unsafe-libyaml", ] -[[package]] -name = "serial_test" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" -dependencies = [ - "dashmap", - "futures", - "lazy_static", - "log", - "parking_lot", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.26", -] - -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -3472,9 +4673,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -3483,13 +4684,19 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3499,49 +4706,82 @@ dependencies = [ "libc", ] +[[package]] +name = "signatory" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e303f8205714074f6068773f0e29527e0453937fe837c9717d066635b65f31" +dependencies = [ + "pkcs8 0.10.2", + "rand_core", + "signature 2.2.0", + "zeroize", +] + [[package]] name = "signature" -version = "2.1.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ "digest", "rand_core", ] [[package]] -name = "slab" -version = "0.4.8" +name = "signature" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "autocfg", + "digest", + "rand_core", ] [[package]] -name = "smallvec" -version = "1.11.0" +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "siphasher" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] -name = "socket2" +name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "libc", - "winapi", + "autocfg", ] +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -3561,243 +4801,74 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", ] [[package]] -name = "sqlformat" -version = "0.2.1" +name = "spki" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" -dependencies = [ - "itertools", - "nom", - "unicode_categories", -] - -[[package]] -name = "sqlx" -version = "0.7.1" -source = "git+https://github.com/launchbadge/sqlx?branch=main#c70cfaf035b3ffcb1d6f244f6070e92646e83515" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.7.1" -source = "git+https://github.com/launchbadge/sqlx?branch=main#c70cfaf035b3ffcb1d6f244f6070e92646e83515" -dependencies = [ - "ahash 0.8.3", - "atoi", - "byteorder", - "bytes", - "chrono", - "crc", - "crossbeam-queue", - "dotenvy", - "either", - "event-listener", - "futures-channel", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashlink", - "hex", - "indexmap 2.0.0", - "log", - "memchr", - "native-tls", - "once_cell", - "paste", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlformat", - "thiserror", - "tokio", - "tokio-stream", - "tracing 0.1.37", - "url", - "uuid", -] - -[[package]] -name = "sqlx-macros" -version = "0.7.1" -source = "git+https://github.com/launchbadge/sqlx?branch=main#c70cfaf035b3ffcb1d6f244f6070e92646e83515" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 1.0.109", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.7.1" -source = "git+https://github.com/launchbadge/sqlx?branch=main#c70cfaf035b3ffcb1d6f244f6070e92646e83515" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 1.0.109", - "tempfile", - "tokio", - "url", + "base64ct", + "der 0.7.9", ] [[package]] -name = "sqlx-mysql" -version = "0.7.1" -source = "git+https://github.com/launchbadge/sqlx?branch=main#c70cfaf035b3ffcb1d6f244f6070e92646e83515" -dependencies = [ - "atoi", - "base64 0.21.2", - "bitflags 2.3.3", - "byteorder", - "bytes", - "chrono", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing 0.1.37", - "uuid", - "whoami", -] +name = "static_assertions_next" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" [[package]] -name = "sqlx-postgres" -version = "0.7.1" -source = "git+https://github.com/launchbadge/sqlx?branch=main#c70cfaf035b3ffcb1d6f244f6070e92646e83515" +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ - "atoi", - "base64 0.21.2", - "bitflags 2.3.3", - "byteorder", - "chrono", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand", - "serde", - "serde_json", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing 0.1.37", - "uuid", - "whoami", + "finl_unicode", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "sqlx-sqlite" -version = "0.7.1" -source = "git+https://github.com/launchbadge/sqlx?branch=main#c70cfaf035b3ffcb1d6f244f6070e92646e83515" -dependencies = [ - "atoi", - "chrono", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "sqlx-core", - "tracing 0.1.37", - "url", - "uuid", -] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "stringprep" -version = "0.1.3" +name = "strum" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "strum_macros", ] [[package]] -name = "strsim" -version = "0.10.0" +name = "strum_macros" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] [[package]] name = "subtle" @@ -3818,9 +4889,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -3834,71 +4905,129 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "tcp-stream" -version = "0.26.1" +name = "system-configuration" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da30af7998f51ee1aa48ab24276fe303a697b004e31ff542b192c088d5630a5" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "cfg-if", - "native-tls", - "p12", - "rustls-connector", - "rustls-pemfile", + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", ] [[package]] -name = "tempfile" -version = "3.6.0" +name = "system-configuration-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ - "autocfg", - "cfg-if", - "fastrand", - "redox_syscall", - "rustix 0.37.23", - "windows-sys", + "core-foundation-sys", + "libc", ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "system-deps" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ - "winapi-util", + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.43" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.43" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -3916,11 +5045,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.29.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", @@ -3929,20 +5057,9 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.4.9", + "socket2", "tokio-macros", - "windows-sys", -] - -[[package]] -name = "tokio-executor-trait" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "802ccf58e108fe16561f35348fabe15ff38218968f033d587e399a84937533cc" -dependencies = [ - "async-trait", - "executor-trait", - "tokio", + "windows-sys 0.48.0", ] [[package]] @@ -3957,37 +5074,65 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2", + "tokio", + "tokio-util", + "whoami", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-postgres-rustls" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "0ea13f22eda7127c827983bdaf0d7fff9df21c8817bab02815ac277a21143677" dependencies = [ - "native-tls", + "futures", + "ring", + "rustls 0.22.3", "tokio", + "tokio-postgres", + "tokio-rustls 0.25.0", + "x509-certificate", ] [[package]] -name = "tokio-reactor-trait" -version = "1.1.0" +name = "tokio-retry" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9481a72f36bd9cbb8d6dd349227c4783e234e4332cfe806225bc929c4b92486" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" dependencies = [ - "async-trait", - "futures-core", - "futures-io", - "reactor-trait", + "pin-project", + "rand", "tokio", - "tokio-stream", ] [[package]] @@ -3996,15 +5141,26 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.3", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -4014,9 +5170,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", @@ -4026,94 +5182,121 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", - "tracing 0.1.37", + "tracing", ] [[package]] name = "toml" -version = "0.7.6" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.9", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.6", ] [[package]] name = "tonic" -version = "0.9.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" dependencies = [ "async-stream", "async-trait", "axum", - "base64 0.21.2", + "base64 0.21.7", "bytes", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-timeout", "percent-encoding", "pin-project", "prost", - "rustls-pemfile", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", "tokio-stream", "tower", "tower-layer", "tower-service", - "tracing 0.1.37", + "tracing", ] [[package]] name = "tonic-build" -version = "0.9.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" dependencies = [ "prettyplease", "proc-macro2", "prost-build", "quote", - "syn 1.0.109", + "syn 2.0.58", +] + +[[package]] +name = "totp-rs" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c4ae9724c5888c0417d2396037ed3b60665925624766416e3e342b6ba5dbd3f" +dependencies = [ + "base32", + "constant_time_eq", + "hmac", + "qrcodegen-image", + "sha1", + "sha2", + "url", + "urlencoding", ] [[package]] @@ -4133,7 +5316,7 @@ dependencies = [ "tokio-util", "tower-layer", "tower-service", - "tracing 0.1.37", + "tracing", ] [[package]] @@ -4150,52 +5333,35 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", - "tracing-core 0.1.31", -] - -[[package]] -name = "tracing" -version = "0.2.0" -source = "git+https://github.com/ScuffleTV/tracing#e2dd0f6e0e31896f41607717159c71fd24f26b4f" -dependencies = [ - "pin-project-lite", - "tracing-core 0.2.0", + "tracing-core", ] [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tracing-core" -version = "0.2.0" -source = "git+https://github.com/ScuffleTV/tracing#e2dd0f6e0e31896f41607717159c71fd24f26b4f" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", ] [[package]] @@ -4207,33 +5373,35 @@ dependencies = [ "futures", "futures-task", "pin-project", - "tracing 0.1.37", + "tracing", ] [[package]] name = "tracing-log" version = "0.2.0" -source = "git+https://github.com/ScuffleTV/tracing#e2dd0f6e0e31896f41607717159c71fd24f26b4f" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "env_logger", "log", "once_cell", - "tracing-core 0.2.0", + "tracing-core", ] [[package]] name = "tracing-serde" -version = "0.2.0" -source = "git+https://github.com/ScuffleTV/tracing#e2dd0f6e0e31896f41607717159c71fd24f26b4f" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ "serde", - "tracing-core 0.2.0", + "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.0" -source = "git+https://github.com/ScuffleTV/tracing#e2dd0f6e0e31896f41607717159c71fd24f26b4f" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -4244,54 +5412,12 @@ dependencies = [ "sharded-slab", "smallvec", "thread_local", - "tracing 0.2.0", - "tracing-core 0.2.0", + "tracing", + "tracing-core", "tracing-log", "tracing-serde", ] -[[package]] -name = "transcoder" -version = "0.1.0" -dependencies = [ - "aac", - "anyhow", - "async-stream", - "async-trait", - "bytes", - "bytesio", - "chrono", - "common", - "config", - "dotenvy", - "flv", - "fred", - "futures", - "futures-util", - "hyper", - "lapin", - "mp4", - "native-tls", - "nix", - "portpicker", - "prost", - "prost-build", - "serde", - "serial_test", - "sha2", - "tempfile", - "tokio", - "tokio-native-tls", - "tokio-stream", - "tokio-util", - "tonic", - "tonic-build", - "tracing 0.1.37", - "transmuxer", - "url-parse", - "uuid", -] - [[package]] name = "transmuxer" version = "0.0.1" @@ -4312,9 +5438,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.22.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" +checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" dependencies = [ "async-trait", "cfg-if", @@ -4323,54 +5449,78 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.2.3", + "idna 0.4.0", "ipnet", - "lazy_static", + "once_cell", "rand", "smallvec", "thiserror", "tinyvec", "tokio", - "tracing 0.1.37", + "tracing", "url", ] [[package]] name = "trust-dns-resolver" -version = "0.22.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe" +checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" dependencies = [ "cfg-if", "futures-util", "ipconfig", - "lazy_static", "lru-cache", + "once_cell", "parking_lot", + "rand", "resolv-conf", "smallvec", "thiserror", "tokio", - "tracing 0.1.37", + "tracing", "trust-dns-proto", ] [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tsify" +version = "0.4.5" +source = "git+https://github.com/ScuffleTV/tsify.git?branch=sisou/comments#e36e55bcf3c9ac7c1d8185e5ad994885f4a2eb46" +dependencies = [ + "gloo-utils", + "serde", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.4.5" +source = "git+https://github.com/ScuffleTV/tsify.git?branch=sisou/comments#e36e55bcf3c9ac7c1d8185e5ad994885f4a2eb46" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.58", +] [[package]] name = "tungstenite" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 1.1.0", "httparse", "log", "rand", @@ -4382,9 +5532,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" @@ -4392,116 +5542,416 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "ulid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34778c17965aa2a08913b57e1f34db9b4a63f5de31768b55bf20d2795f921259" +dependencies = [ + "bytes", + "getrandom", + "postgres-types", + "rand", + "serde", + "uuid", + "web-time", +] + [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] -name = "unicode_categories" -version = "0.1.1" +name = "unicode-xid" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unsafe-libyaml" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", - "idna 0.4.0", + "idna 0.5.0", "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "v_frame" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "video-api" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-nats", + "async-stream", + "async-trait", + "base64 0.21.7", + "binary-helper", + "bytes", + "chrono", + "dotenvy", + "fred", + "futures", + "futures-util", + "hex", + "hmac", + "http 0.2.12", + "hyper 0.14.28", + "itertools", + "jwt-next", + "pb", + "postgres-from-row", + "prost", + "rand", + "rand_chacha", + "scuffle-config", + "scuffle-utils", + "serde", + "serde_json", + "sha2", + "tokio", + "tokio-stream", + "tonic", + "tower", + "tracing", + "ulid", + "url", + "uuid", + "video-common", +] + +[[package]] +name = "video-cli" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-nats", + "async-trait", + "base64 0.21.7", + "binary-helper", + "chrono", + "clap", + "fred", + "futures", + "futures-util", + "pb", + "scuffle-config", + "scuffle-utils", + "serde", + "serde_json", + "serde_yaml", + "tokio", + "tonic", + "ulid", + "video-api", + "video-common", +] + +[[package]] +name = "video-common" +version = "0.0.1" +dependencies = [ + "async-nats", + "async-trait", + "bytes", + "chrono", + "futures", + "futures-util", + "pb", + "postgres-from-row", + "postgres-types", + "prost", + "scuffle-utils", + "serde", + "tokio", + "tokio-postgres", + "tracing", + "ulid", + "uuid", ] [[package]] -name = "url-parse" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b316bdc240d73d26f84d3b0779b89a8fddc9f0b3aa54cf4979b74dda4e08b2" +name = "video-edge" +version = "0.0.1" dependencies = [ - "regex", + "anyhow", + "async-nats", + "async-stream", + "async-trait", + "binary-helper", + "bytes", + "chrono", + "futures", + "futures-util", + "hmac", + "http-body-util", + "hyper 1.2.0", + "hyper-util", + "itertools", + "jwt-next", + "pb", + "postgres-from-row", + "prost", + "rustls 0.22.3", + "rustls-pemfile 2.1.2", + "scuffle-config", + "scuffle-utils", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tokio-rustls 0.25.0", + "tokio-stream", + "tokio-util", + "tonic", + "tracing", + "ulid", + "url", + "uuid", + "video-common", + "video-player-types", ] [[package]] -name = "urlencoding" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +name = "video-ingest" +version = "0.0.1" +dependencies = [ + "aac", + "anyhow", + "async-nats", + "async-stream", + "async-trait", + "base64 0.21.7", + "binary-helper", + "bytes", + "bytesio", + "chrono", + "default-net", + "dotenvy", + "flv", + "futures", + "futures-util", + "hyper 1.2.0", + "mp4", + "pb", + "portpicker", + "postgres-from-row", + "prost", + "rtmp", + "rustls 0.22.3", + "rustls-pemfile 2.1.2", + "scuffle-config", + "scuffle-utils", + "serde", + "serde_json", + "tokio", + "tokio-rustls 0.25.0", + "tokio-stream", + "tonic", + "tracing", + "transmuxer", + "ulid", + "uuid", + "video-common", +] [[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +name = "video-player" +version = "0.0.1" +dependencies = [ + "bytes", + "bytesio", + "console_error_panic_hook", + "gloo-timers", + "h264", + "js-sys", + "mp4", + "serde", + "serde-wasm-bindgen", + "serde_json", + "serde_path_to_error", + "tokio", + "tracing", + "tracing-core", + "tracing-subscriber", + "tsify", + "ulid", + "url", + "video-player-types", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] -name = "uuid" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" +name = "video-player-types" +version = "0.0.1" dependencies = [ - "getrandom", "serde", + "ulid", + "url", ] [[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +name = "video-transcoder" +version = "0.0.1" +dependencies = [ + "aac", + "anyhow", + "async-nats", + "async-stream", + "async-trait", + "aws-config", + "aws-sdk-s3", + "binary-helper", + "bytes", + "bytesio", + "chrono", + "dotenvy", + "ffmpeg", + "flv", + "futures", + "futures-util", + "hyper 1.2.0", + "image", + "mp4", + "pb", + "portpicker", + "prost", + "scuffle-config", + "scuffle-utils", + "serde", + "serde_json", + "sha2", + "tempfile", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tracing", + "transmuxer", + "ulid", + "uuid", + "video-common", +] [[package]] -name = "version_check" -version = "0.9.4" +name = "vsimd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] -name = "waker-fn" -version = "1.1.0" +name = "walkdir" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "want" @@ -4518,11 +5968,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4530,24 +5986,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -4557,9 +6013,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4567,55 +6023,80 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "which" -version = "4.4.0" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "either", - "libc", - "once_cell", + "js-sys", + "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "wild" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1" +dependencies = [ + "glob", +] [[package]] name = "winapi" @@ -4635,9 +6116,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -4654,7 +6135,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] @@ -4663,82 +6153,148 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" -version = "0.5.0" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.10.1" +name = "winnow" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ - "winapi", + "memchr", ] [[package]] @@ -4748,17 +6304,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] -name = "yasna" -version = "0.5.2" +name = "x509-certificate" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der 0.7.9", + "hex", + "pem", + "ring", + "signature 2.2.0", + "spki 0.7.3", + "thiserror", + "zeroize", +] + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "zerocopy" +version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "zune-inflate" +version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 4e8d1b137..970a30716 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,32 +1,67 @@ [workspace] members = [ - "backend/api", + "platform/api", + "platform/image_processor", "video/edge", "video/ingest", "video/transcoder", - "video/bytesio", - "video/container/flv", - "video/container/mp4", - "video/codec/h264", - "video/codec/h265", - "video/codec/av1", - "video/codec/aac", - "video/protocol/rtmp", - "video/transmuxer", - "video/utils/amf0", - "video/utils/exp_golomb", - "common", - "config/config", - "config/config_derive", - "config/config_test" + "video/lib/*", + "video/api", + "video/player", + "video/player_types", + "video/common", + "video/cli", + "binary-helper", + "utils", + "proto", + "config", + "config/derive", + "ffmpeg", ] -exclude = [ - "frontend/player", -] +resolver = "2" + +[profile.release-fat] +inherits = "release" +lto = 'fat' + +[profile.wasm] +lto = 'fat' +panic = 'abort' +opt-level = "z" +codegen-units = 1 +strip = true +inherits = "release" + +[workspace.dependencies] +aac = { path = "video/lib/aac" } +amf0 = { path = "video/lib/amf0" } +av1 = { path = "video/lib/av1" } +bytesio = { path = "video/lib/bytesio", default-features = false } +exp_golomb = { path = "video/lib/exp_golomb" } +flv = { path = "video/lib/flv" } +h264 = { path = "video/lib/h264" } +h265 = { path = "video/lib/h265" } +mp4 = { path = "video/lib/mp4" } +rtmp = { path = "video/lib/rtmp" } +transmuxer = { path = "video/lib/transmuxer" } +utils = { path = "utils", default-features = false, package = "scuffle-utils" } +config = { path = "config", package = "scuffle-config" } +pb = { path = "proto" } +video-common = { path = "video/common" } +video-player-types = { path = "video/player_types" } +video-api = { path = "video/api" } +video-edge = { path = "video/edge" } +video-ingest = { path = "video/ingest" } +video-transcoder = { path = "video/transcoder" } +binary-helper = { path = "binary-helper" } +ffmpeg = { path = "ffmpeg" } +# These patches are pending PRs to the upstream crates +# TODO: Remove these once the PRs are merged [patch.crates-io] -fred = { git = "https://github.com/ScuffleTV/fred.rs" } -tracing-log = { git = "https://github.com/ScuffleTV/tracing" } -tracing-subscriber = { git = "https://github.com/ScuffleTV/tracing" } +# https://github.com/remkop22/postgres-from-row/pull/9 +postgres-from-row = { git = "https://github.com/ScuffleTV/postgres-from-row.git", branch = "troy/from_fn" } +# https://github.com/madonoharu/tsify/pull/32 +tsify = { git = "https://github.com/ScuffleTV/tsify.git", branch = "sisou/comments" } diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 37130dd1b..000000000 --- a/LICENSE.md +++ /dev/null @@ -1,11 +0,0 @@ -Copyright (c) 2023 Scuffle. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -3. All advertising materials mentioning features or use of this software must display the following acknowledgement: - This product includes software developed by the organization. -4. Neither the name of the copyright holder nor the names the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 4606daffd..aa7e0b90c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![codecov](https://codecov.io/gh/ScuffleTV/scuffle/branch/main/graph/badge.svg?token=LJCYSZR4IV)](https://codecov.io/gh/ScuffleTV/scuffle) [![CircleCI](https://dl.circleci.com/status-badge/img/gh/ScuffleTV/scuffle/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/ScuffleTV/scuffle/tree/main) +[![dependency status](https://deps.rs/repo/github/ScuffleTV/scuffle/status.svg)](https://deps.rs/repo/github/ScuffleTV/scuffle) ## Welcome to Scuffle! @@ -12,3 +13,13 @@ This project is very much a WIP and has no functional component right now! For more information on what this is going to be, check out [our conference](https://www.youtube.com/watch?v=QvQpMRkzoVM&t=593s) You can also look at our [design documents](./design/README.md). + +## License + +This project is licensed under multiple licenses. + +For all libraries written by Scuffle, the [MIT](./licenses/MIT_LICENSE) or [Apache 2.0](./licenses/APACHE2_LICENSE) license is used. + +For non-libary code such as applications, the [BSL 1.0](./licenses/BSL_LICENSE) license is used. + +For more information, see the [licenses](./licenses/README.md) readme. diff --git a/backend/api/Cargo.toml b/backend/api/Cargo.toml deleted file mode 100644 index 98f22d5c1..000000000 --- a/backend/api/Cargo.toml +++ /dev/null @@ -1,65 +0,0 @@ -[package] -name = "api" -version = "0.1.0" -edition = "2021" -authors = ["Scuffle "] -description = "Scuffle API server" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[[bin]] -name = "api" -path = "src/main.rs" - -[[bin]] -# This is a dummy binary to export the GraphQL schema for frontend type generation. -name = "api-gql-generator" -path = "src/gql.nocov.rs" -test = false -bench = false - -[dependencies] -anyhow = "1" -tracing = "0" -tokio = { version = "1", features = ["full"] } -serde = { version = "1", features = ["derive"] } -hyper = { version = "0", features = ["full"] } -common = { path = "../../common" } -sqlx = { git="https://github.com/launchbadge/sqlx", branch="main", features = ["postgres", "runtime-tokio-native-tls", "json", "chrono", "uuid"] } -routerify = "3" -serde_json = "1" -reqwest = { version = "0", features = ["json"] } -chrono = { version = "0", default-features = false, features = ["serde", "clock"] } -async-graphql = { version = "5", features = ["apollo_tracing", "apollo_persisted_queries", "tracing", "opentelemetry", "dataloader", "string_number", "uuid"] } -hyper-tungstenite = "0" -async-stream = "0" -futures = "0" -futures-util = "0" -arc-swap = "1" -jwt = "0" -hmac = "0" -sha2 = "0" -negative-impl = "0" -tonic = { version = "0", features = ["tls"] } -prost = "0" -uuid = "1" -bitmask-enum = "2" -argon2 = "0" -email_address = "0" -rand = "0" -lapin = { version = "2", features = ["native-tls"] } -tokio-stream = { version = "0", features = ["sync"] } -fred = { version = "6", features = ["enable-native-tls", "sentinel-client", "sentinel-auth", "subscriber-client"] } -config = { path = "../../config/config" } - -[dev-dependencies] -tempfile = "3" -dotenvy = "0" -http = "0" -tokio-tungstenite = "0" -portpicker = "0" -serial_test = "2" - -[build-dependencies] -tonic-build = "0" -prost-build = "0" diff --git a/backend/api/LICENSE.md b/backend/api/LICENSE.md deleted file mode 120000 index f0608a63a..000000000 --- a/backend/api/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE.md \ No newline at end of file diff --git a/backend/api/build.rs b/backend/api/build.rs deleted file mode 100644 index 346ada42c..000000000 --- a/backend/api/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -const PROTO_DIR: &str = "../../proto"; - -fn main() { - let mut config = prost_build::Config::new(); - - config.protoc_arg("--experimental_allow_proto3_optional"); - config.bytes(["."]); - - tonic_build::configure() - .compile_with_config( - config, - &[ - format!("{}/scuffle/events/ingest.proto", PROTO_DIR), - format!("{}/scuffle/events/api.proto", PROTO_DIR), - format!("{}/scuffle/backend/api.proto", PROTO_DIR), - format!("{}/scuffle/utils/health.proto", PROTO_DIR), - ], - &[PROTO_DIR], - ) - .unwrap(); -} diff --git a/backend/api/src/api/error.rs b/backend/api/src/api/error.rs deleted file mode 100644 index 8dd8613c5..000000000 --- a/backend/api/src/api/error.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::{ - fmt::{Debug, Display}, - panic::Location, -}; - -use hyper::{Body, StatusCode}; -use serde_json::json; - -use super::macros::make_response; - -pub type Result = std::result::Result; - -pub struct RouteError { - source: Option, - location: &'static Location<'static>, - span: tracing::Span, - response: hyper::Response, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ShouldLog { - Yes, - Debug, - No, -} - -impl RouteError { - pub fn span(&self) -> &tracing::Span { - &self.span - } - - pub fn location(&self) -> &'static Location<'static> { - self.location - } - - pub fn response(self) -> hyper::Response { - self.response - } - - pub fn should_log(&self) -> ShouldLog { - match self.response.status().is_server_error() { - true => ShouldLog::Yes, - false => match self.source.is_some() { - true => ShouldLog::Debug, - false => ShouldLog::No, - }, - } - } - - fn with_source(self, source: Option) -> Self { - Self { source, ..self } - } - - fn with_location(self, location: &'static Location<'static>) -> Self { - Self { location, ..self } - } -} - -impl From> for RouteError { - #[track_caller] - fn from(res: hyper::Response) -> Self { - Self { - source: None, - span: tracing::Span::current(), - location: Location::caller(), - response: res, - } - } -} - -impl From<(StatusCode, &'_ str)> for RouteError { - #[track_caller] - fn from(status: (StatusCode, &'_ str)) -> Self { - Self { - source: None, - span: tracing::Span::current(), - location: Location::caller(), - response: make_response!(status.0, json!({ "message": status.1, "success": false })), - } - } -} - -impl From<(StatusCode, &'_ str, T)> for RouteError -where - T: Into + Debug + Display, -{ - #[track_caller] - fn from(status: (StatusCode, &'_ str, T)) -> Self { - Self { - source: Some(status.2.into()), - span: tracing::Span::current(), - location: Location::caller(), - response: make_response!(status.0, json!({ "message": status.1, "success": false })), - } - } -} - -impl From<&'_ str> for RouteError { - #[track_caller] - fn from(message: &'_ str) -> Self { - Self { - source: None, - span: tracing::Span::current(), - location: Location::caller(), - response: make_response!( - StatusCode::INTERNAL_SERVER_ERROR, - json!({ "message": message, "success": false }) - ), - } - } -} - -impl Debug for RouteError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.source { - Some(err) => write!(f, "RouteError: {:?}", err), - None => write!(f, "RouteError: Unknown Source"), - } - } -} - -impl Display for RouteError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.source { - Some(err) => write!(f, "RouteError: {}", err), - None => write!(f, "RouteError: Unknown Source"), - } - } -} - -impl std::error::Error for RouteError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match &self.source { - Some(err) => Some(err.as_ref()), - None => None, - } - } -} - -pub trait ResultExt: Sized { - fn map_err_route(self, ctx: C) -> Result - where - RouteError: From; -} - -impl ResultExt for std::result::Result -where - anyhow::Error: From, -{ - #[track_caller] - fn map_err_route(self, ctx: C) -> Result - where - RouteError: From, - { - match self { - Ok(val) => Ok(val), - Err(err) => Err(RouteError::from(ctx) - .with_source(Some(err.into())) - .with_location(Location::caller())), - } - } -} diff --git a/backend/api/src/api/ext.rs b/backend/api/src/api/ext.rs deleted file mode 100644 index 3cb375c32..000000000 --- a/backend/api/src/api/ext.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::sync::{Arc, Weak}; - -use hyper::{Body, Request, StatusCode}; -use routerify::prelude::RequestExt as _; - -use crate::global::GlobalState; - -use super::error::Result; - -pub trait RequestExt { - fn get_global(&self) -> Result>; -} - -impl RequestExt for Request { - fn get_global(&self) -> Result> { - Ok(self - .data::>() - .expect("global state not set") - .upgrade() - .ok_or(( - StatusCode::INTERNAL_SERVER_ERROR, - "failed to upgrade global state", - ))?) - } -} diff --git a/backend/api/src/api/macros.rs b/backend/api/src/api/macros.rs deleted file mode 100644 index 08e2299f6..000000000 --- a/backend/api/src/api/macros.rs +++ /dev/null @@ -1,11 +0,0 @@ -macro_rules! make_response { - ($status:expr, $body:expr) => { - hyper::Response::builder() - .status($status) - .header("Content-Type", "application/json") - .body(Body::from($body.to_string())) - .expect("failed to build response") - }; -} - -pub(super) use make_response; diff --git a/backend/api/src/api/middleware/auth.rs b/backend/api/src/api/middleware/auth.rs deleted file mode 100644 index 8f08985c0..000000000 --- a/backend/api/src/api/middleware/auth.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::sync::Arc; - -use hyper::http::header; -use hyper::{header::HeaderValue, Body, StatusCode}; -use routerify::{prelude::RequestExt, Middleware}; - -use crate::api::error::{ResultExt, RouteError}; -use crate::api::ext::RequestExt as _; -use crate::api::middleware::response_headers::RequestExt as _; -use crate::api::v1::jwt::JwtState; -use crate::global::GlobalState; - -const X_AUTH_TOKEN_CHECK: &str = "X-Auth-Token-Check"; -const X_AUTH_TOKEN_CHECK_STATUS: &str = "X-Auth-Token-Check-Status"; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum AuthTokenCheck { - Always, - WhenRequired, -} - -impl From for HeaderValue { - fn from(mode: AuthTokenCheck) -> Self { - match mode { - AuthTokenCheck::Always => HeaderValue::from_static("always"), - AuthTokenCheck::WhenRequired => HeaderValue::from_static("when-required"), - } - } -} - -impl From<&HeaderValue> for AuthTokenCheck { - fn from(value: &HeaderValue) -> Self { - match value.to_str().unwrap_or_default() { - "always" => Self::Always, - "when-required" => Self::WhenRequired, - _ => Self::WhenRequired, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum AuthTokenCheckStatus { - Success, - Failed, -} - -impl From for HeaderValue { - fn from(mode: AuthTokenCheckStatus) -> Self { - match mode { - AuthTokenCheckStatus::Success => HeaderValue::from_static("success"), - AuthTokenCheckStatus::Failed => HeaderValue::from_static("failed"), - } - } -} - -macro_rules! fail_fast { - ($mode:ident, $req:ident) => { - $req.set_response_header(X_AUTH_TOKEN_CHECK, $mode); - $req.set_response_header(X_AUTH_TOKEN_CHECK_STATUS, AuthTokenCheckStatus::Failed); - if $mode == AuthTokenCheck::Always { - return Err(RouteError::from((StatusCode::UNAUTHORIZED, "unauthorized"))); - } - return Ok($req); - }; -} - -impl Default for AuthTokenCheck { - fn default() -> Self { - Self::WhenRequired - } -} - -pub fn auth_middleware(_: &Arc) -> Middleware { - Middleware::pre(|req| async move { - let mode = req - .headers() - .get(X_AUTH_TOKEN_CHECK) - .map(AuthTokenCheck::from) - .unwrap_or_default(); - - let Some(token) = req.headers().get(header::AUTHORIZATION) else { - fail_fast!(mode, req); - }; - - let global = req.get_global()?; - let Ok(token) = token.to_str() else { - fail_fast!(mode, req); - }; - - // Token's will start with "Bearer " so we need to remove that - let Some(token) = token.strip_prefix("Bearer ") else { - fail_fast!(mode, req); - }; - - let Some(jwt) = JwtState::verify(&global, token) else { - fail_fast!(mode, req); - }; - - let Some(session) = global - .session_by_id_loader - .load_one(jwt.session_id) - .await - .map_err_route("failed to fetch session")? - else { - fail_fast!(mode, req); - }; - - if !session.is_valid() { - fail_fast!(mode, req); - } - - let permissions = global - .user_permisions_by_id_loader - .load_one(session.user_id) - .await - .map_err_route("failed to fetch user permissions")? - .unwrap_or_default(); - - req.set_response_header(X_AUTH_TOKEN_CHECK_STATUS, AuthTokenCheckStatus::Success); - req.set_context((session, permissions)); - - Ok(req) - }) -} diff --git a/backend/api/src/api/middleware/cors.rs b/backend/api/src/api/middleware/cors.rs deleted file mode 100644 index 460122b69..000000000 --- a/backend/api/src/api/middleware/cors.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::sync::Arc; - -use hyper::http::header; -use hyper::Body; -use routerify::Middleware; - -use crate::api::error::RouteError; -use crate::global::GlobalState; - -pub fn cors_middleware(_: &Arc) -> Middleware { - Middleware::post(|mut resp| async move { - resp.headers_mut() - .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap()); - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_METHODS, - "GET, POST, OPTIONS".parse().unwrap(), - ); - resp.headers_mut().insert( - header::ACCESS_CONTROL_ALLOW_HEADERS, - "Content-Type, Authorization".parse().unwrap(), - ); - - Ok(resp) - }) -} diff --git a/backend/api/src/api/middleware/mod.rs b/backend/api/src/api/middleware/mod.rs deleted file mode 100644 index 8e075bce9..000000000 --- a/backend/api/src/api/middleware/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod auth; -pub mod cors; -pub mod response_headers; diff --git a/backend/api/src/api/middleware/response_headers.rs b/backend/api/src/api/middleware/response_headers.rs deleted file mode 100644 index fa39ba252..000000000 --- a/backend/api/src/api/middleware/response_headers.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use hyper::{header::IntoHeaderName, Body, Request}; -use routerify::{prelude::RequestExt as _, Middleware}; - -use crate::{api::error::RouteError, global::GlobalState}; - -#[derive(Clone)] -pub struct ResponseHeadersMiddleware(pub Arc>); - -impl Default for ResponseHeadersMiddleware { - fn default() -> Self { - Self(Arc::new(Mutex::new(hyper::HeaderMap::new()))) - } -} - -pub fn pre_flight_middleware(_: &Arc) -> Middleware { - Middleware::pre(|req| async move { - req.set_context(ResponseHeadersMiddleware::default()); - - Ok(req) - }) -} - -pub fn post_flight_middleware(_: &Arc) -> Middleware { - Middleware::post_with_info(|mut resp, info| async move { - let headers: Option = info.context(); - - if let Some(headers) = headers { - let headers = headers.0.lock().expect("failed to lock headers"); - headers.iter().for_each(|(k, v)| { - resp.headers_mut().insert(k, v.clone()); - }); - } - - Ok(resp) - }) -} - -pub trait RequestExt { - fn set_response_header(&self, key: K, value: V) - where - K: IntoHeaderName, - V: Into; -} - -impl RequestExt for Request { - fn set_response_header(&self, key: K, value: V) - where - K: IntoHeaderName, - V: Into, - { - let headers: ResponseHeadersMiddleware = self.context().unwrap(); - - let mut headers = headers.0.lock().expect("failed to lock headers"); - key.insert(&mut headers, value.into()); - } -} diff --git a/backend/api/src/api/mod.rs b/backend/api/src/api/mod.rs deleted file mode 100644 index 409fa7ab2..000000000 --- a/backend/api/src/api/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -use anyhow::Result; -use hyper::{server::conn::Http, Body, Response, StatusCode}; -use routerify::{RequestInfo, RequestServiceBuilder, Router}; -use serde_json::json; -use std::sync::Arc; -use tokio::{net::TcpSocket, select}; - -use crate::{api::macros::make_response, global::GlobalState}; - -use self::error::{RouteError, ShouldLog}; - -pub mod error; -pub mod ext; -pub mod macros; -pub mod middleware; -pub mod v1; - -async fn error_handler( - err: Box<(dyn std::error::Error + Send + Sync + 'static)>, - info: RequestInfo, -) -> Response { - match err.downcast::() { - Ok(err) => { - let location = err.location(); - - err.span().in_scope(|| match err.should_log() { - ShouldLog::Yes => { - tracing::error!(location = location.to_string(), error = ?err, "http error") - } - ShouldLog::Debug => { - tracing::debug!(location = location.to_string(), error = ?err, "http error") - } - ShouldLog::No => (), - }); - - err.response() - } - Err(err) => { - tracing::error!(error = ?err, info = ?info, "unhandled http error"); - make_response!( - StatusCode::INTERNAL_SERVER_ERROR, - json!({ "message": "Internal Server Error", "success": false }) - ) - } - } -} - -pub fn routes(global: &Arc) -> Router { - let weak = Arc::downgrade(global); - Router::builder() - .data(weak) - // These response header middlewares lets us add headers to the response from the request handlers - .middleware(middleware::response_headers::pre_flight_middleware(global)) - .middleware(middleware::response_headers::post_flight_middleware(global)) - // Our error handler - .err_handler_with_info(error_handler) - // The CORS middleware adds the CORS headers to the response - .middleware(middleware::cors::cors_middleware(global)) - // The auth middleware checks the Authorization header, and if it's valid, it adds the user to the request extensions - // This way, we can access the user in the handlers, this does not fail the request if the token is invalid or not present. - .middleware(middleware::auth::auth_middleware(global)) - .scope("/v1", v1::routes(global)) - .build() - .expect("failed to build router") -} - -pub async fn run(global: Arc) -> Result<()> { - tracing::info!("Listening on {}", global.config.api.bind_address); - let socket = if global.config.api.bind_address.is_ipv6() { - TcpSocket::new_v6()? - } else { - TcpSocket::new_v4()? - }; - - socket.set_reuseaddr(true)?; - socket.set_reuseport(true)?; - socket.bind(global.config.api.bind_address)?; - let listener = socket.listen(1024)?; - - // The reason we use a Weak reference to the global state is because we don't want to block the shutdown - // When a keep-alive connection is open, the request service will still be alive, and will still be holding a reference to the global state - // If we used an Arc, the global state would never be dropped, and the shutdown would never complete - // By using a Weak reference, we can check if the global state is still alive, and if it isn't, we can stop accepting new connections - let request_service = - RequestServiceBuilder::new(routes(&global)).expect("failed to build request service"); - - loop { - select! { - _ = global.ctx.done() => { - return Ok(()); - }, - r = listener.accept() => { - let (socket, addr) = r?; - tracing::debug!("Accepted connection from {}", addr); - - tokio::spawn(Http::new().serve_connection( - socket, - request_service.build(addr), - ).with_upgrades()); - }, - } - } -} diff --git a/backend/api/src/api/v1/gql/auth.rs b/backend/api/src/api/v1/gql/auth.rs deleted file mode 100644 index 47a668460..000000000 --- a/backend/api/src/api/v1/gql/auth.rs +++ /dev/null @@ -1,340 +0,0 @@ -use super::error::{GqlError, Result, ResultExt}; -use super::ext::ContextExt; -use super::models::session::Session; -use crate::api::v1::jwt::JwtState; -use crate::database::{session, user}; -use async_graphql::{Context, Object}; -use chrono::{Duration, Utc}; - -#[derive(Default, Clone)] -pub struct AuthMutation; - -#[Object] -/// The mutation object for authentication -impl AuthMutation { - /// Login using a username and password. If via websocket this will authenticate the websocket connection. - async fn login<'ctx>( - &self, - ctx: &Context<'_>, - #[graphql(desc = "The username of the user.")] username: String, - #[graphql(desc = "The password of the user.")] password: String, - #[graphql(desc = "The captcha token from cloudflare turnstile.")] captcha_token: String, - #[graphql( - desc = "The duration of the session in seconds. If not specified it will be 7 days." - )] - validity: Option, - #[graphql( - desc = "Setting this to false will make it so logging in does not authenticate the connection." - )] - update_context: Option, - ) -> Result { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - if !global - .validate_turnstile_token(&captcha_token) - .await - .map_err_gql("Failed to validate captcha token")? - { - return Err(GqlError::InvalidInput - .with_message("Captcha token is not valid") - .with_field(vec!["captchaToken"])); - } - - let user = global - .user_by_username_loader - .load_one(username.to_lowercase()) - .await - .map_err_gql("Failed to fetch user")? - .ok_or( - GqlError::InvalidInput - .with_message("Invalid username or password") - .with_field(vec!["username", "password"]), - )?; - - if !user.verify_password(&password) { - return Err(GqlError::InvalidInput - .with_message("Invalid username or password") - .with_field(vec!["username", "password"])); - } - - let login_duration = validity.unwrap_or(60 * 60 * 24 * 7); // 7 days - let expires_at = Utc::now() + Duration::seconds(login_duration as i64); - - // TODO: maybe look to batch this - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions (user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - expires_at, - ) - .fetch_one(&*global.db) - .await - .map_err_gql("Failed to create session")?; - - let jwt = JwtState::from(session.clone()); - - let token = jwt - .serialize(global) - .ok_or((GqlError::InternalServerError, "Failed to serialize JWT"))?; - - let permissions = global - .user_permisions_by_id_loader - .load_one(user.id) - .await - .map_err_gql("Failed to fetch user permissions")? - .unwrap_or_default(); - - // We need to update the request context with the new session - if update_context.unwrap_or(true) { - request_context.set_session(Some((session.clone(), permissions))); - } - - Ok(Session { - id: session.id, - token, - user_id: session.user_id, - expires_at: session.expires_at.into(), - last_used_at: session.last_used_at.into(), - created_at: session.created_at.into(), - _user: Some(user.into()), - }) - } - - /// Login with a session token. If via websocket this will authenticate the websocket connection. - async fn login_with_token<'ctx>( - &self, - ctx: &Context<'_>, - #[graphql(desc = "The JWT Session Token")] session_token: String, - #[graphql( - desc = "Setting this to false will make it so logging in does not authenticate the connection." - )] - update_context: Option, - ) -> Result { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - let jwt = JwtState::verify(global, &session_token).ok_or( - GqlError::InvalidInput - .with_message("Invalid session token") - .with_field(vec!["sessionToken"]), - )?; - - // TODO: maybe look to batch this - let session = sqlx::query_as!( - session::Model, - "UPDATE sessions SET last_used_at = NOW() WHERE id = $1 RETURNING *", - jwt.session_id, - ) - .fetch_optional(&*global.db) - .await - .map_err_gql("failed to fetch session")? - .ok_or( - GqlError::InvalidInput - .with_message("Invalid session token") - .with_field(vec!["sessionToken"]), - )?; - - if !session.is_valid() { - return Err(GqlError::InvalidSession.with_message("Session token is no longer valid")); - } - - let permissions = global - .user_permisions_by_id_loader - .load_one(session.user_id) - .await - .map_err_gql("Failed to fetch user permissions")? - .unwrap_or_default(); - - // We need to update the request context with the new session - if update_context.unwrap_or(true) { - request_context.set_session(Some((session.clone(), permissions))); - } - - Ok(Session { - id: session.id, - token: session_token, - user_id: session.user_id, - expires_at: session.expires_at.into(), - last_used_at: session.last_used_at.into(), - created_at: session.created_at.into(), - _user: None, - }) - } - - /// If successful will return a new session for the account which just got created. - #[allow(clippy::too_many_arguments)] - async fn register<'ctx>( - &self, - ctx: &Context<'_>, - #[graphql(desc = "The username of the user.")] username: String, - #[graphql(desc = "The password of the user.")] password: String, - #[graphql(desc = "The email of the user.")] email: String, - #[graphql(desc = "The captcha token from cloudflare turnstile.")] captcha_token: String, - #[graphql(desc = "The validity of the session in seconds.")] validity: Option, - #[graphql( - desc = "Setting this to false will make it so logging in does not authenticate the connection." - )] - update_context: Option, - ) -> Result { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - if !global - .validate_turnstile_token(&captcha_token) - .await - .map_err_gql("Failed to validate captcha token")? - { - return Err(GqlError::InvalidInput - .with_message("Capcha token is invalid") - .with_field(vec!["captchaToken"])); - } - - let display_name = username.clone(); - let username = username.to_lowercase(); - let email = email.to_lowercase(); - - user::validate_username(&username).map_err(|e| { - GqlError::InvalidInput - .with_message(e) - .with_field(vec!["username"]) - })?; - user::validate_password(&password).map_err(|e| { - GqlError::InvalidInput - .with_message(e) - .with_field(vec!["password"]) - })?; - user::validate_email(&email).map_err(|e| { - GqlError::InvalidInput - .with_message(e) - .with_field(vec!["email"]) - })?; - - if global - .user_by_username_loader - .load_one(username.clone()) - .await - .map_err_gql("failed to fetch user")? - .is_some() - { - return Err(GqlError::InvalidInput - .with_message("Username already taken") - .with_field(vec!["username"])); - } - - let mut tx = global - .db - .begin() - .await - .map_err_gql("Failed to create user")?; - - // TODO: maybe look to batch this - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, password_hash, email, stream_key) VALUES ($1, $2, $3, $4, $5) RETURNING *", - username, - display_name, - user::hash_password(&password), - email, - user::generate_stream_key(), - ) - .fetch_one(&mut *tx) - .await - .map_err_gql("Failed to create user")?; - - let login_duration = validity.unwrap_or(60 * 60 * 24 * 7); // 7 days - let expires_at = Utc::now() + Duration::seconds(login_duration as i64); - - // TODO: maybe look to batch this - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions (user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - expires_at, - ) - .fetch_one(&mut *tx) - .await - .map_err_gql("Failed to create session")?; - - let jwt = JwtState::from(session.clone()); - - let token = jwt - .serialize(global) - .ok_or((GqlError::InternalServerError, "Failed to serialize JWT"))?; - - tx.commit() - .await - .map_err_gql("Failed to commit transaction")?; - - let permissions = global - .user_permisions_by_id_loader - .load_one(user.id) - .await - .map_err_gql("Failed to fetch user permissions")? - .unwrap_or_default(); - - // We need to update the request context with the new session - if update_context.unwrap_or(true) { - request_context.set_session(Some((session.clone(), permissions))); - } - - Ok(Session { - id: session.id, - token, - user_id: session.user_id, - expires_at: session.expires_at.into(), - last_used_at: session.last_used_at.into(), - created_at: session.created_at.into(), - _user: Some(user.into()), - }) - } - - /// Logout the user with the given session token. This will invalidate the session token. - async fn logout<'ctx>( - &self, - ctx: &Context<'_>, - #[graphql( - desc = "You can provide a session token to logout of, if not provided the session will logout of the currently authenticated session." - )] - session_token: Option, - ) -> Result { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - let session = if session_token.is_none() { - request_context.get_session(global).await? - } else { - None - }; - - let jwt = session_token.and_then(|token| JwtState::verify(global, &token)); - - let session_id = match ( - Option::as_ref(&jwt).map(|jwt| jwt.session_id), - Option::as_ref(&session).map(|(s, _)| s.id), - ) { - (Some(id), _) => id, - (None, Some(id)) => id, - (None, None) => { - return Err(GqlError::InvalidInput - .with_message("Not logged in") - .with_field(vec!["sessionToken"])); - } - }; - - // TODO: maybe look to batch this - sqlx::query!( - "UPDATE sessions SET invalidated_at = NOW() WHERE id = $1", - session_id - ) - .execute(&*global.db) - .await - .map_err_gql("Failed to update session")?; - - if jwt.is_none() { - request_context.set_session(None); - } - - Ok(true) - } -} diff --git a/backend/api/src/api/v1/gql/chat.rs b/backend/api/src/api/v1/gql/chat.rs deleted file mode 100644 index 86dbf16b3..000000000 --- a/backend/api/src/api/v1/gql/chat.rs +++ /dev/null @@ -1,80 +0,0 @@ -use crate::api::v1::gql::error::ResultExt; -use crate::database::chat_message; -use crate::pb; -use prost::Message; - -use super::error::{GqlError, Result}; -use super::ext::ContextExt; -use super::models::chat_message::ChatMessage; -use async_graphql::{Context, Object}; -use fred::prelude::PubsubInterface; -use uuid::Uuid; - -const MAX_MESSAGE_LENGTH: usize = 500; - -#[derive(Default)] -pub struct ChatMutation; - -#[Object] -impl ChatMutation { - // Send message in chat. You need to be logged in for that. - async fn send_message<'ctx>( - &self, - ctx: &Context<'_>, - #[graphql(desc = "ID of chat room where the message will be send.")] channel_id: Uuid, - #[graphql(desc = "Message content that will be published.")] content: String, - ) -> Result { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - if content.len() > MAX_MESSAGE_LENGTH { - return Err(GqlError::InvalidInput.with_message("Message too long")); - } - - // TODO: check if user is banned from chat - let (session, _) = request_context - .get_session(global) - .await? - .ok_or_else(|| GqlError::Unauthorized.with_message("You need to be logged in"))?; - - // TODO: Check if the user is allowed to send messages in this chat - let channel = global - .user_by_id_loader - .load_one(channel_id) - .await - .map_err_gql("Failed to fetch channel")? - .ok_or_else(|| GqlError::InvalidInput.with_message("Channel not found"))?; - - let chat_message = sqlx::query_as!( - chat_message::Model, - "INSERT INTO chat_messages (channel_id, author_id, content) VALUES ($1, $2, $3) RETURNING *", - channel.id, - session.user_id, - content, - ).fetch_one(&*global.db).await.map_err_gql("Failed to insert chat message")?; - - match global - .redis - .publish( - format!("user:{}:chat:messages", channel.id), - pb::scuffle::events::ChatMessage { - id: chat_message.id.to_string(), - channel_id: chat_message.channel_id.to_string(), - author_id: chat_message.author_id.to_string(), - content: chat_message.content.clone(), - created_at: chat_message.created_at.timestamp(), - } - .encode_to_vec() - .as_slice(), - ) - .await - { - Ok(()) => {} - Err(_) => { - return Err(GqlError::InternalServerError.with_message("Failed to publish message")); - } - }; - - Ok(chat_message.into()) - } -} diff --git a/backend/api/src/api/v1/gql/error.rs b/backend/api/src/api/v1/gql/error.rs deleted file mode 100644 index 6e7c6224d..000000000 --- a/backend/api/src/api/v1/gql/error.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - panic::Location, - sync::Arc, -}; - -use async_graphql::ErrorExtensions; - -pub type Result = std::result::Result; - -#[derive(Clone)] -pub struct GqlErrorInterface { - kind: GqlError, - message: Option, - fields: Vec, - span: tracing::Span, - source: Option>, - location: &'static Location<'static>, -} - -impl GqlErrorInterface { - fn with_source(self, source: Option) -> Self { - Self { - source: source.map(Arc::new), - ..self - } - } - - fn with_location(self, location: &'static Location<'static>) -> Self { - Self { location, ..self } - } - - pub fn with_field(self, fields: Vec<&str>) -> Self { - let fields = fields.into_iter().map(|f| f.to_string()).collect(); - Self { fields, ..self } - } - - pub fn with_message(self, message: String) -> Self { - Self { - message: Some(message), - ..self - } - } - - fn display(&self) -> String { - match &self.message { - Some(msg) => format!("{}: {}", self.kind, msg), - None => format!("{}", self.kind), - } - } -} - -#[derive(PartialEq, Eq, Clone, Copy, Debug)] - -pub enum GqlError { - /// An internal server error occurred. - InternalServerError, - /// The input was invalid. - InvalidInput, - /// The session is no longer valid. - InvalidSession, - /// Not Implemented - NotImplemented, - /// Unauthorized - Unauthorized, - /// Not Found - NotFound, -} - -impl Display for GqlError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - GqlError::InternalServerError => write!(f, "InternalServerError"), - GqlError::InvalidInput => write!(f, "InvalidInput"), - GqlError::InvalidSession => write!(f, "InvalidSession"), - GqlError::NotImplemented => write!(f, "NotImplemented"), - GqlError::Unauthorized => write!(f, "Unauthorized"), - GqlError::NotFound => write!(f, "NotFound"), - } - } -} - -impl GqlError { - #[track_caller] - pub fn with_message(self, message: &str) -> GqlErrorInterface { - GqlErrorInterface { - kind: self, - message: Some(message.to_string()), - span: tracing::Span::current(), - source: None, - fields: Vec::new(), - location: Location::caller(), - } - } -} - -impl ErrorExtensions for GqlErrorInterface { - fn extend(&self) -> async_graphql::Error { - let err = async_graphql::Error::new(self.display()).extend_with(|_, e| { - e.set("kind", self.kind.to_string()); - if let Some(message) = &self.message { - e.set("reason", message.as_str()); - } - - e.set("fields", self.fields.as_slice()); - }); - - match self.kind { - GqlError::InternalServerError => { - self.span.in_scope(|| { - tracing::error!(error = ?self.source, location = self.location.to_string(), "gql error: {}", self.display()); - }); - } - _ => { - self.span.in_scope(|| { - tracing::debug!(error = ?self.source, location = self.location.to_string(), "gql error: {}", self.display()); - }); - } - } - - err - } -} - -impl From<(GqlError, &'_ str)> for GqlErrorInterface { - #[track_caller] - fn from((kind, message): (GqlError, &'_ str)) -> Self { - Self { - kind, - message: Some(message.to_string()), - span: tracing::Span::current(), - source: None, - fields: Vec::new(), - location: Location::caller(), - } - } -} - -impl From for GqlErrorInterface { - #[track_caller] - fn from(kind: GqlError) -> Self { - Self { - kind, - message: None, - span: tracing::Span::current(), - source: None, - fields: Vec::new(), - location: Location::caller(), - } - } -} - -impl - From<( - GqlError, - &'_ str, - &'static (dyn std::error::Error + Sync + Send), - )> for GqlErrorInterface -{ - #[track_caller] - fn from( - (kind, message, err): ( - GqlError, - &'_ str, - &'static (dyn std::error::Error + Sync + Send), - ), - ) -> Self { - Self { - kind, - fields: Vec::new(), - message: Some(message.to_string()), - span: tracing::Span::current(), - source: Some(Arc::new(err.into())), - location: Location::caller(), - } - } -} - -impl From<&'_ str> for GqlErrorInterface { - #[track_caller] - fn from(msg: &'_ str) -> Self { - Self { - fields: Vec::new(), - kind: GqlError::InternalServerError, - message: Some(msg.to_string()), - span: tracing::Span::current(), - source: None, - location: Location::caller(), - } - } -} - -impl From for async_graphql::Error { - fn from(err: GqlErrorInterface) -> Self { - err.extend() - } -} - -pub trait ResultExt: Sized { - fn map_err_gql(self, ctx: C) -> Result - where - GqlErrorInterface: From; -} - -impl ResultExt for std::result::Result -where - anyhow::Error: From, -{ - #[track_caller] - fn map_err_gql(self, ctx: C) -> Result - where - GqlErrorInterface: From, - { - match self { - Ok(v) => Ok(v), - Err(e) => Err(GqlErrorInterface::from(ctx) - .with_source(Some(e.into())) - .with_location(Location::caller())), - } - } -} - -impl ResultExt for std::option::Option { - #[track_caller] - fn map_err_gql(self, ctx: C) -> Result - where - GqlErrorInterface: From, - { - match self { - Some(v) => Ok(v), - None => Err(GqlErrorInterface::from(ctx) - .with_location(Location::caller()) - .with_source(None)), - } - } -} diff --git a/backend/api/src/api/v1/gql/ext.rs b/backend/api/src/api/v1/gql/ext.rs deleted file mode 100644 index 0e1d9a6d0..000000000 --- a/backend/api/src/api/v1/gql/ext.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::sync::Arc; - -use async_graphql::Context; - -use crate::global::GlobalState; - -use super::request_context::RequestContext; - -pub trait ContextExt { - fn get_global(&self) -> &Arc; - fn get_session(&self) -> &Arc; -} - -impl ContextExt for Context<'_> { - fn get_global(&self) -> &Arc { - self.data_unchecked::>() - } - - fn get_session(&self) -> &Arc { - self.data_unchecked::>() - } -} - -pub trait RequestExt { - fn provide_global(self, global: Arc) -> Self; - fn provide_context(self, ctx: Arc) -> Self; -} - -impl RequestExt for async_graphql::Request { - fn provide_global(self, global: Arc) -> Self { - self.data(global) - } - - fn provide_context(self, ctx: Arc) -> Self { - self.data(ctx) - } -} - -impl RequestExt for async_graphql::Data { - fn provide_global(mut self, global: Arc) -> Self { - self.insert(global); - self - } - - fn provide_context(mut self, ctx: Arc) -> Self { - self.insert(ctx); - self - } -} diff --git a/backend/api/src/api/v1/gql/handlers.rs b/backend/api/src/api/v1/gql/handlers.rs deleted file mode 100644 index 880ed9014..000000000 --- a/backend/api/src/api/v1/gql/handlers.rs +++ /dev/null @@ -1,279 +0,0 @@ -use std::{future, str::FromStr, sync::Arc}; - -use crate::database::session; -use async_graphql::{ - http::{WebSocketProtocols, WsMessage}, - Data, ErrorExtensions, -}; -use futures_util::{SinkExt, StreamExt}; -use hyper::{body::HttpBody, header, Body, Request, Response, StatusCode}; -use hyper_tungstenite::{ - tungstenite::{ - protocol::{frame::coding::CloseCode, CloseFrame}, - Message, - }, - HyperWebsocket, -}; -use routerify::prelude::RequestExt; -use serde_json::json; -use tokio::select; - -use crate::{ - api::{ - error::{Result, ResultExt as _, RouteError}, - ext::RequestExt as _, - v1::jwt::JwtState, - }, - dataloader::user_permissions::UserPermission, - global::GlobalState, -}; - -use super::{ - error::{GqlError, ResultExt as _}, - ext::RequestExt as _, - request_context::RequestContext, - MySchema, -}; - -async fn websocket_handler( - ws: HyperWebsocket, - schema: MySchema, - global: Arc, - protocol: WebSocketProtocols, - request_context: Arc, -) { - let ws = match ws.await { - Ok(ws) => ws, - Err(e) => { - tracing::error!("Failed to upgrade websocket request: {}", e); - return; - } - }; - - let (mut tx, rx) = ws.split(); - - let input = rx - .take_while(|res| future::ready(res.is_ok())) - .map(Result::unwrap) // Safe because we check if its ok above - .filter_map(|msg| { - if let Message::Text(_) | Message::Binary(_) = msg { - future::ready(Some(msg)) - } else { - future::ready(None) - } - }) - .map(Message::into_data); - - let data = Data::default() - .provide_context(request_context.clone()) - .provide_global(global.clone()); - - let stream = { - let global = global.clone(); - - async_graphql::http::WebSocket::new(schema, input, protocol) - .on_connection_init(|params| async move { - // if the token is provided in the connection params we use that - // and we fail if the token is invalid, there doesnt seem to be a way to return an error - // from the connection_init callback that does not close the connection. - // Or a way to tell the client that the token they provided is not valid. - let token = params.get("sessionToken").and_then(|v| v.as_str()); - if let Some(token) = token { - // We silently ignore invalid tokens since we don't want to force the user to login - // if the token is invalid when they make a request which requires authentication, it will fail. - let Some(jwt) = JwtState::verify(&global, token) else { - return Err(GqlError::InvalidSession - .with_message("invalid session token") - .extend()); - }; - - let Some(session) = global - .session_by_id_loader - .load_one(jwt.session_id) - .await - .map_err_gql("failed to fetch session")? - else { - return Err(GqlError::InvalidSession - .with_message("invalid session") - .extend()); - }; - - if !session.is_valid() { - return Err(GqlError::InvalidSession - .with_message("session has invalidated") - .extend()); - } - - let permissions = global - .user_permisions_by_id_loader - .load_one(session.id) - .await - .map_err_gql("failed to fetch permissions")? - .unwrap_or_default(); - - request_context.set_session(Some((session, permissions))); - } - - Ok(data) - }) - .map(|msg| match msg { - WsMessage::Text(text) => Message::Text(text), - WsMessage::Close(code, status) => Message::Close(Some(CloseFrame { - code: code.into(), - reason: status.into(), - })), - }) - .map(Ok) - }; - - // TODO: Gracefully shutdown the stream forward. - // This is interesting since when we shutdown we interrupt the stream forward rather then waiting for the stream to finish. - select! { - _ = stream.forward(&mut tx) => {} - _ = global.ctx.done() => { - tx.send(Message::Close(Some(CloseFrame { code: CloseCode::Restart, reason: "server is restarting".into() }))).await.ok(); - } - } -} - -pub async fn graphql_handler(mut req: Request) -> Result> { - if req.method() == hyper::Method::OPTIONS { - return Ok(hyper::Response::builder() - .status(StatusCode::OK) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - .header( - "Access-Control-Allow-Headers", - "Content-Type, Authorization", - ) - .header("Access-Control-Max-Age", "86400") - .body(Body::empty()) - .expect("failed to build response")); - } - - let schema = req - .data::() - .expect("failed to get schema") - .clone(); - - let global = req.get_global()?; - - let session = req.context::<(session::Model, UserPermission)>(); - - // We need to check if this is a websocket upgrade request. - // If it is, we need to upgrade the request to a websocket request. - if hyper_tungstenite::is_upgrade_request(&req) { - let protocol = req - .headers() - .get(header::SEC_WEBSOCKET_PROTOCOL) - .and_then(|val| val.to_str().ok()) - .and_then(|protocols| { - protocols - .split(',') - .find_map(|p| WebSocketProtocols::from_str(p.trim()).ok()) - }) - .ok_or((StatusCode::BAD_REQUEST, "Invalid websocket protocol"))?; - - let (mut response, websocket) = hyper_tungstenite::upgrade(&mut req, None) - .map_err_route((StatusCode::BAD_REQUEST, "Failed to upgrade to websocket"))?; - - response.headers_mut().insert( - header::SEC_WEBSOCKET_PROTOCOL, - protocol - .sec_websocket_protocol() - .parse() - .expect("failed to set websocket protocol"), - ); - - let request_context = Arc::new(RequestContext::new(true)); - request_context.set_session(session); - - tokio::spawn(websocket_handler( - websocket, - schema, - global, - protocol, - request_context, - )); - - return Ok(response); - } - - let session_state = Arc::new(RequestContext::new(false)); - session_state.set_session(session); - - // We need to parse the request body into a GraphQL request. - // If the request is a post request, we need to parse the body as a GraphQL request. - // If the request is a get request, we need to parse the query string as a GraphQL request. - let request = match *req.method() { - hyper::Method::POST => { - let body = req - .body_mut() - .data() - .await - .and_then(|f| f.ok()) - .ok_or((StatusCode::BAD_REQUEST, "Invalid request body"))?; - - let content_type = req - .headers() - .get("content-type") - .and_then(|val| val.to_str().ok()); - - async_graphql::http::receive_body( - content_type, - body.to_vec().as_slice(), - Default::default(), - ) - .await - .map_err_route((StatusCode::BAD_REQUEST, "Invalid request body"))? - } - hyper::Method::GET => { - let query = req.uri().query().unwrap_or(""); - - async_graphql::http::parse_query_string(query) - .map_err_route((StatusCode::BAD_REQUEST, "Invalid query string"))? - } - _ => { - return Err(RouteError::from(( - StatusCode::METHOD_NOT_ALLOWED, - "Invalid request method", - ))) - } - } - .provide_global(global) - .provide_context(session_state); - - let response = schema.execute(request).await; - - let mut resp = Response::builder() - .status(StatusCode::OK) - .header("Access-Control-Allow-Origin", "*") - .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - .header( - "Access-Control-Allow-Headers", - "Content-Type, Authorization", - ) - .header("Access-Control-Max-Age", "86400") - .header("Content-Type", "application/json") - .body(Body::from( - json!({ - "data": response.data, - "errors": if response.errors.is_empty() { - None - } else { - Some(response.errors) - }, - "extensions": response.extensions, - }) - .to_string(), - )) - .expect("failed to build response"); - - (&response.http_headers) - .into_iter() - .for_each(|(key, value)| { - resp.headers_mut().insert(key, value.clone()); - }); - - Ok(resp) -} diff --git a/backend/api/src/api/v1/gql/mod.rs b/backend/api/src/api/v1/gql/mod.rs deleted file mode 100644 index 79b4a7f9f..000000000 --- a/backend/api/src/api/v1/gql/mod.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::sync::Arc; - -use async_graphql::{extensions, ComplexObject, Context, Schema, SimpleObject}; -use hyper::{Body, Response}; -use routerify::Router; -use uuid::Uuid; - -use crate::{api::error::RouteError, global::GlobalState}; - -use self::{ - error::{Result, ResultExt}, - ext::ContextExt, -}; - -pub mod auth; -pub mod chat; -pub mod error; -pub mod ext; -pub mod handlers; -pub mod models; -pub mod request_context; -pub mod subscription; - -#[derive(Default, SimpleObject)] -#[graphql(complex)] -/// The root query type which contains root level fields. -pub struct Query { - noop: bool, -} - -#[derive(Default, SimpleObject)] -/// The root mutation type which contains root level fields. -pub struct Mutation { - auth: auth::AuthMutation, - chat: chat::ChatMutation, -} - -#[ComplexObject] -impl Query { - async fn user_by_username( - &self, - ctx: &Context<'_>, - #[graphql(desc = "The username of the user.")] username: String, - ) -> Result> { - let global = ctx.get_global(); - - let user = global - .user_by_username_loader - .load_one(username.to_lowercase()) - .await - .map_err_gql("failed to fetch user")?; - - Ok(user.map(models::user::User::from)) - } - - async fn user_by_id( - &self, - ctx: &Context<'_>, - #[graphql(desc = "The id of the user.")] id: Uuid, - ) -> Result> { - let global = ctx.get_global(); - - let user = global - .user_by_id_loader - .load_one(id) - .await - .map_err_gql("failed to fetch user")?; - - Ok(user.map(models::user::User::from)) - } -} - -pub type MySchema = Schema; - -pub const PLAYGROUND_HTML: &str = include_str!("playground.html"); - -pub fn schema() -> MySchema { - Schema::build( - Query::default(), - Mutation::default(), - subscription::Subscription::default(), - ) - .enable_federation() - .enable_subscription_in_federation() - .extension(extensions::Analyzer) - .limit_complexity(100) // We don't want to allow too complex queries to be executed - .finish() -} - -pub fn routes(_global: &Arc) -> Router { - let router = Router::builder() - .data(schema()) - .any_method("/", handlers::graphql_handler) - .get("/playground", move |_| async move { - Ok(Response::builder() - .status(200) - .header("content-type", "text/html") - .body(Body::from(PLAYGROUND_HTML)) - .expect("failed to build response")) - }) - .build() - .expect("failed to build router"); - - router -} diff --git a/backend/api/src/api/v1/gql/models/chat_message.rs b/backend/api/src/api/v1/gql/models/chat_message.rs deleted file mode 100644 index 17e3da265..000000000 --- a/backend/api/src/api/v1/gql/models/chat_message.rs +++ /dev/null @@ -1,75 +0,0 @@ -use async_graphql::{ComplexObject, Context, Enum, SimpleObject}; -use uuid::Uuid; - -use super::{date, user::User}; -use crate::{ - api::v1::gql::{ - error::{GqlError, Result, ResultExt}, - ext::ContextExt, - }, - database::chat_message, -}; - -#[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)] -pub enum MessageType { - User, - Welcome, - System, -} - -#[derive(SimpleObject)] -#[graphql(complex)] -pub struct ChatMessage { - pub id: Uuid, - pub channel_id: Uuid, - pub author_id: Uuid, - pub content: String, - pub created_at: date::DateRFC3339, - pub r#type: MessageType, -} - -#[ComplexObject] -impl ChatMessage { - pub async fn author(&self, ctx: &Context<'_>) -> Result> { - let global = ctx.get_global(); - - if self.author_id.is_nil() { - return Ok(None); - } - - let user = global - .user_by_id_loader - .load_one(self.author_id) - .await - .map_err_gql("failed to fetch user")? - .ok_or(GqlError::NotFound.with_message("user not found"))?; - - Ok(Some(User::from(user))) - } - - pub async fn channel(&self, ctx: &Context<'_>) -> Result { - let global = ctx.get_global(); - - let user = global - .user_by_id_loader - .load_one(self.channel_id) - .await - .map_err_gql("failed to fetch user")? - .ok_or(GqlError::NotFound.with_message("user not found"))?; - - Ok(User::from(user)) - } -} - -impl From for ChatMessage { - fn from(model: chat_message::Model) -> Self { - Self { - id: model.id, - channel_id: model.channel_id, - author_id: model.author_id, - content: model.content, - created_at: model.created_at.into(), - r#type: MessageType::User, - } - } -} diff --git a/backend/api/src/api/v1/gql/models/date.rs b/backend/api/src/api/v1/gql/models/date.rs deleted file mode 100644 index 930bc050c..000000000 --- a/backend/api/src/api/v1/gql/models/date.rs +++ /dev/null @@ -1,41 +0,0 @@ -use async_graphql::{InputValueError, InputValueResult, Scalar, ScalarType, Value}; -use chrono::{TimeZone, Utc}; - -#[derive(Clone, Debug)] -pub struct DateRFC3339(pub chrono::DateTime); - -#[Scalar] -impl ScalarType for DateRFC3339 { - fn parse(value: Value) -> InputValueResult { - match value { - Value::String(s) => match chrono::DateTime::parse_from_rfc3339(&s) { - Ok(dt) => { - let dt = dt.with_timezone(&Utc); - Ok(DateRFC3339(dt)) - } - Err(e) => Err(InputValueError::custom(e.to_string())), - }, - Value::Number(n) => match n.as_i64() { - Some(n) => { - let dt = Utc.timestamp_opt(n, 0); - let dt = dt - .single() - .ok_or(InputValueError::custom("Invalid number"))?; - Ok(DateRFC3339(dt)) - } - None => Err(InputValueError::custom("Invalid number")), - }, - _ => Err(InputValueError::custom("Invalid value")), - } - } - - fn to_value(&self) -> Value { - Value::String(self.0.to_rfc3339()) - } -} - -impl From> for DateRFC3339 { - fn from(dt: chrono::DateTime) -> Self { - DateRFC3339(dt) - } -} diff --git a/backend/api/src/api/v1/gql/models/global_roles.rs b/backend/api/src/api/v1/gql/models/global_roles.rs deleted file mode 100644 index 66b5fec78..000000000 --- a/backend/api/src/api/v1/gql/models/global_roles.rs +++ /dev/null @@ -1,31 +0,0 @@ -use async_graphql::SimpleObject; - -use crate::database::global_role; -use uuid::Uuid; - -use super::date::DateRFC3339; - -#[derive(SimpleObject, Clone)] -pub struct GlobalRole { - pub id: Uuid, - pub name: String, - pub description: String, - pub rank: i32, - pub allowed_permissions: i64, - pub denied_permissions: i64, - pub created_at: DateRFC3339, -} - -impl From for GlobalRole { - fn from(value: global_role::Model) -> Self { - Self { - id: value.id, - created_at: value.created_at.into(), - name: value.name, - description: value.description, - rank: value.rank as i32, - allowed_permissions: value.allowed_permissions.bits(), - denied_permissions: value.denied_permissions.bits(), - } - } -} diff --git a/backend/api/src/api/v1/gql/models/mod.rs b/backend/api/src/api/v1/gql/models/mod.rs deleted file mode 100644 index 77235ddb4..000000000 --- a/backend/api/src/api/v1/gql/models/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod chat_message; -pub mod date; -pub mod global_roles; -pub mod session; -pub mod user; diff --git a/backend/api/src/api/v1/gql/models/session.rs b/backend/api/src/api/v1/gql/models/session.rs deleted file mode 100644 index d87ac5bf1..000000000 --- a/backend/api/src/api/v1/gql/models/session.rs +++ /dev/null @@ -1,48 +0,0 @@ -use async_graphql::{ComplexObject, Context, SimpleObject}; -use uuid::Uuid; - -use super::{date, user::User}; -use crate::api::v1::gql::{ - error::{GqlError, Result, ResultExt}, - ext::ContextExt, -}; - -#[derive(SimpleObject)] -#[graphql(complex)] -pub struct Session { - /// The session's id - pub id: Uuid, - /// The session's token - pub token: String, - /// The user who owns this session - pub user_id: Uuid, - /// Expires at - pub expires_at: date::DateRFC3339, - /// Last used at - pub last_used_at: date::DateRFC3339, - /// Created at - pub created_at: date::DateRFC3339, - - #[graphql(skip)] - pub _user: Option, -} - -#[ComplexObject] -impl Session { - pub async fn user(&self, ctx: &Context<'_>) -> Result { - if let Some(user) = &self._user { - return Ok(user.clone()); - } - - let global = ctx.get_global(); - - let user = global - .user_by_id_loader - .load_one(self.user_id) - .await - .map_err_gql("failed to fetch user")? - .ok_or(GqlError::NotFound.with_message("user not found"))?; - - Ok(User::from(user)) - } -} diff --git a/backend/api/src/api/v1/gql/models/user.rs b/backend/api/src/api/v1/gql/models/user.rs deleted file mode 100644 index 0daa37951..000000000 --- a/backend/api/src/api/v1/gql/models/user.rs +++ /dev/null @@ -1,174 +0,0 @@ -use async_graphql::{ComplexObject, Context, SimpleObject}; -use uuid::Uuid; - -use crate::api::v1::gql::{ - error::{GqlError, Result}, - ext::ContextExt, -}; -use crate::database::{global_role, user}; - -use super::{date::DateRFC3339, global_roles::GlobalRole}; - -#[derive(SimpleObject, Clone)] -#[graphql(complex)] -pub struct User { - pub id: Uuid, - pub display_name: String, - pub username: String, - pub created_at: DateRFC3339, - - // Private fields - #[graphql(skip)] - pub email_: String, - #[graphql(skip)] - pub email_verified_: bool, - #[graphql(skip)] - pub last_login_at_: DateRFC3339, - #[graphql(skip)] - pub stream_key_: String, -} - -/// TODO: find a better way to check if a user is allowed to read a field. - -#[ComplexObject] -impl User { - async fn email(&self, ctx: &Context<'_>) -> Result<&str> { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - let session = request_context.get_session(global).await?; - - if let Some((session, perms)) = session { - if session.user_id == self.id - || perms - .permissions - .has_permission(global_role::Permission::Admin) - { - return Ok(&self.email_); - } - } - - Err(GqlError::Unauthorized - .with_message("you are not allowed to see this field") - .with_field(vec!["email"])) - } - - async fn email_verified(&self, ctx: &Context<'_>) -> Result { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - let session = request_context.get_session(global).await?; - - if let Some((session, perms)) = session { - if session.user_id == self.id - || perms - .permissions - .has_permission(global_role::Permission::Admin) - { - return Ok(self.email_verified_); - } - } - - Err(GqlError::Unauthorized - .with_message("you are not allowed to see this field") - .with_field(vec!["emailVerified"])) - } - - async fn last_login_at(&self, ctx: &Context<'_>) -> Result<&DateRFC3339> { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - let session = request_context.get_session(global).await?; - - if let Some((session, perms)) = session { - if session.user_id == self.id - || perms - .permissions - .has_permission(global_role::Permission::Admin) - { - return Ok(&self.last_login_at_); - } - } - - Err(GqlError::Unauthorized - .with_message("you are not allowed to see this field") - .with_field(vec!["lastLoginAt"])) - } - - async fn stream_key(&self, ctx: &Context<'_>) -> Result<&str> { - let global = ctx.get_global(); - let request_context = ctx.get_session(); - - let session = request_context.get_session(global).await?; - - if let Some((session, perms)) = session { - if session.user_id == self.id - || perms - .permissions - .has_permission(global_role::Permission::Admin) - { - return Ok(&self.stream_key_); - } - } - - Err(GqlError::Unauthorized - .with_message("you are not allowed to see this field") - .with_field(vec!["stream_key"])) - } - - async fn permissions(&self, ctx: &Context<'_>) -> Result { - let global = ctx.get_global(); - - let global_roles = global - .user_permisions_by_id_loader - .load_one(self.id) - .await - .map_err(|e| { - tracing::error!("failed to fetch global roles: {}", e); - - GqlError::InternalServerError - .with_message("failed to fetch global roles") - .with_field(vec!["permissions"]) - })? - .map(|p| p.permissions) - .unwrap_or_default(); - - Ok(global_roles.bits()) - } - - async fn global_roles(&self, ctx: &Context<'_>) -> Result> { - let global = ctx.get_global(); - - let global_roles = global - .user_permisions_by_id_loader - .load_one(self.id) - .await - .map_err(|e| { - tracing::error!("failed to fetch global roles: {}", e); - - GqlError::InternalServerError - .with_message("failed to fetch global roles") - .with_field(vec!["globalRoles"]) - })? - .map(|p| p.roles.into_iter().map(GlobalRole::from).collect()) - .unwrap_or_default(); - - Ok(global_roles) - } -} - -impl From for User { - fn from(value: user::Model) -> Self { - let stream_key = value.get_stream_key(); - Self { - id: value.id, - username: value.username, - display_name: value.display_name, - email_: value.email, - email_verified_: value.email_verified, - created_at: value.created_at.into(), - last_login_at_: value.last_login_at.into(), - stream_key_: stream_key, - } - } -} diff --git a/backend/api/src/api/v1/gql/request_context.rs b/backend/api/src/api/v1/gql/request_context.rs deleted file mode 100644 index 28747e46b..000000000 --- a/backend/api/src/api/v1/gql/request_context.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::sync::Arc; - -use crate::database::session; -use arc_swap::ArcSwap; - -use crate::{ - api::v1::gql::error::Result, dataloader::user_permissions::UserPermission, global::GlobalState, -}; - -use super::error::{GqlError, ResultExt}; - -#[derive(Default)] -pub struct RequestContext { - is_websocket: bool, - session: ArcSwap>, -} - -impl RequestContext { - pub fn new(is_websocket: bool) -> Self { - Self { - is_websocket, - ..Default::default() - } - } - - pub fn set_session(&self, session: Option<(session::Model, UserPermission)>) { - self.session.store(Arc::new(session)); - } - - pub async fn get_session( - &self, - global: &Arc, - ) -> Result> { - let guard = self.session.load(); - let Some(session) = guard.as_ref() else { - return Ok(None); - }; - - if !self.is_websocket { - if !session.0.is_valid() { - return Err(GqlError::InvalidSession.with_message("Session is no longer valid")); - } - - return Ok(Some(session.clone())); - } - - let session = global - .session_by_id_loader - .load_one(session.0.id) - .await - .map_err_gql("failed to fetch session")? - .and_then(|s| if s.is_valid() { Some(s) } else { None }) - .ok_or_else(|| { - self.session.store(Arc::new(None)); - GqlError::InvalidSession.with_message("Session is no longer valid") - })?; - - let user_permissions = global - .user_permisions_by_id_loader - .load_one(session.user_id) - .await - .map_err_gql("failed to fetch user permissions")? - .ok_or_else(|| { - GqlError::InternalServerError.with_message("failed to fetch user permissions") - })?; - - self.session - .store(Arc::new(Some((session.clone(), user_permissions.clone())))); - - Ok(Some((session, user_permissions))) - } -} diff --git a/backend/api/src/api/v1/gql/subscription/chat.rs b/backend/api/src/api/v1/gql/subscription/chat.rs deleted file mode 100644 index 24f0461c3..000000000 --- a/backend/api/src/api/v1/gql/subscription/chat.rs +++ /dev/null @@ -1,78 +0,0 @@ -use async_graphql::{Context, Subscription}; -use async_stream::stream; -use chrono::{TimeZone, Utc}; -use futures_util::Stream; -use prost::Message; -use uuid::Uuid; - -use crate::{ - api::v1::gql::{ - error::{GqlError, Result, ResultExt}, - ext::ContextExt, - models::chat_message::{ChatMessage, MessageType}, - }, - pb, -}; - -#[derive(Default)] -pub struct ChatSubscription; - -#[Subscription] -impl ChatSubscription { - // Listen to new messages in chat. - pub async fn chat_messages<'ctx>( - &self, - ctx: &'ctx Context<'_>, - #[graphql(desc = "Chat to subscribe to.")] channel_id: Uuid, - ) -> Result> + 'ctx> { - let global = ctx.get_global(); - - let welcome_message = ChatMessage { - id: Uuid::nil(), - author_id: Uuid::nil(), - channel_id, - content: "Welcome to the chat!".to_string(), - created_at: chrono::Utc::now().into(), - r#type: MessageType::Welcome, - }; - - // TODO: check if user is allowed to read this chat - let channel = global - .user_by_id_loader - .load_one(channel_id) - .await - .map_err_gql("failed to fetch user")? - .ok_or(GqlError::NotFound.with_message("user not found"))?; - let mut message_stream = global - .subscription_manager - .subscribe(format!("user:{}:chat:messages", channel.id)) - .await - .map_err_gql("failed to subscribe to chat messages")?; - - Ok(stream!({ - yield Ok(welcome_message); - while let Ok(message) = message_stream.recv().await { - let event = pb::scuffle::events::ChatMessage::decode( - message.as_bytes().map_err_gql("invalid redis value type")?, - ) - .map_err_gql("failed to decode chat message")?; - - yield Ok(ChatMessage { - id: Uuid::parse_str(&event.id) - .map_err_gql("failed to parse chat message id")?, - author_id: Uuid::parse_str(&event.author_id) - .map_err_gql("failed to parse chat message author id")?, - channel_id: Uuid::parse_str(&event.channel_id) - .map_err_gql("failed to parse chat message channel id")?, - content: event.content, - created_at: Utc - .timestamp_opt(event.created_at, 0) - .single() - .map_err_gql("failed to parse chat message created at")? - .into(), - r#type: MessageType::User, - }); - } - })) - } -} diff --git a/backend/api/src/api/v1/gql/subscription/mod.rs b/backend/api/src/api/v1/gql/subscription/mod.rs deleted file mode 100644 index ba3fb2b38..000000000 --- a/backend/api/src/api/v1/gql/subscription/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use async_graphql::{MergedSubscription, Subscription}; -use futures_util::Stream; - -use self::{chat::ChatSubscription, user::UserSubscription}; - -pub mod chat; -pub mod user; - -#[derive(MergedSubscription, Default)] -pub struct Subscription(UserSubscription, ChatSubscription, NoopSubscription); - -#[derive(Default)] -struct NoopSubscription; - -#[Subscription] -impl NoopSubscription { - async fn noop(&self) -> impl Stream { - futures_util::stream::empty() - } -} diff --git a/backend/api/src/api/v1/gql/subscription/user.rs b/backend/api/src/api/v1/gql/subscription/user.rs deleted file mode 100644 index 8d91e35e5..000000000 --- a/backend/api/src/api/v1/gql/subscription/user.rs +++ /dev/null @@ -1,76 +0,0 @@ -use async_graphql::{Context, SimpleObject, Subscription}; -use futures_util::Stream; -use prost::Message; -use uuid::Uuid; - -use crate::{ - api::v1::gql::{ - error::{GqlError, Result, ResultExt}, - ext::ContextExt, - }, - pb, -}; - -#[derive(Default)] -pub struct UserSubscription; - -#[derive(SimpleObject)] -struct DisplayNameStream { - pub username: String, - pub display_name: String, -} - -#[Subscription] -impl UserSubscription { - async fn user_display_name<'ctx>( - &self, - ctx: &'ctx Context<'ctx>, - user_id: Uuid, - ) -> Result> + 'ctx> { - let global = ctx.get_global(); - - let Some(mut user) = global - .user_by_id_loader - .load_one(user_id) - .await - .map_err_gql("failed to fetch user")? - else { - return Err(GqlError::NotFound - .with_message("user not found") - .with_field(vec!["user_id"])); - }; - - let mut subscription = global - .subscription_manager - .subscribe(format!("user:{}:display_name", user_id)) - .await - .map_err_gql("failed to subscribe to user display name")?; - - Ok(async_stream::stream!({ - yield Ok(DisplayNameStream { - display_name: user.display_name.clone(), - username: user.username.clone(), - }); - - while let Ok(message) = subscription.recv().await { - let event = pb::scuffle::events::UserDisplayName::decode( - message.as_bytes().map_err_gql("invalid redis value")?, - ) - .map_err_gql("failed to decode user display name")?; - - if let Some(username) = event.username { - user.username = username; - } - - if let Some(display_name) = event.display_name { - user.display_name = display_name; - } - - yield Ok(DisplayNameStream { - display_name: user.display_name.clone(), - username: user.username.clone(), - }); - } - })) - } -} diff --git a/backend/api/src/api/v1/health.rs b/backend/api/src/api/v1/health.rs deleted file mode 100644 index 1f473e0be..000000000 --- a/backend/api/src/api/v1/health.rs +++ /dev/null @@ -1,29 +0,0 @@ -use std::sync::Arc; - -use hyper::{Body, Request, Response, StatusCode}; -use routerify::Router; -use serde_json::json; - -use crate::{ - api::{ - error::{Result, RouteError}, - macros::make_response, - }, - global::GlobalState, -}; - -async fn health(_: Request) -> Result> { - Ok(make_response!( - StatusCode::OK, - json!({ - "status": "ok" - }) - )) -} - -pub fn routes(_global: &Arc) -> Router { - Router::builder() - .get("/", health) - .build() - .expect("failed to build router") -} diff --git a/backend/api/src/api/v1/jwt.rs b/backend/api/src/api/v1/jwt.rs deleted file mode 100644 index 0de251294..000000000 --- a/backend/api/src/api/v1/jwt.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::sync::Arc; - -use crate::database::session; -use chrono::{DateTime, TimeZone, Utc}; -use hmac::{Hmac, Mac}; -use jwt::{Claims, Header, RegisteredClaims, SignWithKey, Token, VerifyWithKey}; -use sha2::Sha256; -use uuid::Uuid; - -use crate::global::GlobalState; - -pub struct JwtState { - pub user_id: Uuid, - pub session_id: Uuid, - pub expiration: Option>, - pub issued_at: DateTime, - pub not_before: Option>, - pub audience: Option, -} - -impl JwtState { - pub fn serialize(&self, global: &Arc) -> Option { - let key = Hmac::::new_from_slice(global.config.jwt.secret.as_bytes()).ok()?; - let claims = Claims::new(RegisteredClaims { - issued_at: Some(self.issued_at.timestamp() as u64), - expiration: self.expiration.map(|x| x.timestamp() as u64), - issuer: Some(global.config.jwt.issuer.to_string()), - json_web_token_id: Some(self.session_id.to_string()), - subject: Some(self.user_id.to_string()), - not_before: self.not_before.map(|x| x.timestamp() as u64), - audience: self.audience.clone(), - }); - - claims.sign_with_key(&key).ok() - } - - pub fn verify(global: &Arc, token: &str) -> Option { - let key = Hmac::::new_from_slice(global.config.jwt.secret.as_bytes()).ok()?; - let token: Token = token.verify_with_key(&key).ok()?; - - let claims = token.claims(); - - if claims.registered.issuer.clone()? != global.config.jwt.issuer { - return None; - } - - let iat = Utc - .timestamp_opt(claims.registered.issued_at? as i64, 0) - .single()?; - if iat > Utc::now() { - return None; - } - - let nbf = claims - .registered - .not_before - .and_then(|x| Utc.timestamp_opt(x as i64, 0).single()); - if let Some(nbf) = nbf { - if nbf > Utc::now() { - return None; - } - } - - let exp = claims - .registered - .expiration - .and_then(|x| Utc.timestamp_opt(x as i64, 0).single()); - if let Some(exp) = exp { - if exp < Utc::now() { - return None; - } - } - - let user_id = claims.registered.subject.clone()?.parse::().ok()?; - - let session_id = claims - .registered - .json_web_token_id - .clone()? - .parse::() - .ok()?; - let audience = claims.registered.audience.clone(); - - Some(JwtState { - user_id, - session_id, - expiration: exp, - issued_at: iat, - not_before: nbf, - audience, - }) - } -} - -impl From for JwtState { - fn from(session: session::Model) -> Self { - JwtState { - user_id: session.user_id, - session_id: session.id, - expiration: Some(session.expires_at), - issued_at: session.created_at, - not_before: None, - audience: None, - } - } -} diff --git a/backend/api/src/api/v1/mod.rs b/backend/api/src/api/v1/mod.rs deleted file mode 100644 index e99afb0e4..000000000 --- a/backend/api/src/api/v1/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::sync::Arc; - -use hyper::Body; -use routerify::Router; - -use crate::global::GlobalState; - -use super::error::RouteError; - -pub mod gql; -pub mod health; -pub mod jwt; - -pub fn routes(global: &Arc) -> Router { - Router::builder() - .scope("/health", health::routes(global)) - .scope("/gql", gql::routes(global)) - .build() - .expect("failed to build router") -} diff --git a/backend/api/src/config.rs b/backend/api/src/config.rs deleted file mode 100644 index 41d4c452a..000000000 --- a/backend/api/src/config.rs +++ /dev/null @@ -1,158 +0,0 @@ -use std::net::SocketAddr; - -use anyhow::Result; -use common::config::{LoggingConfig, RedisConfig, RmqConfig, TlsConfig}; - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -/// The API is the backend for the Scuffle service -pub struct AppConfig { - /// The path to the config file - pub config_file: Option, - - /// Name of this instance - pub name: String, - - /// The logging config - pub logging: LoggingConfig, - - /// API Config - pub api: ApiConfig, - - /// Database Config - pub database: DatabaseConfig, - - /// Turnstile Config - pub turnstile: TurnstileConfig, - - /// JWT Config - pub jwt: JwtConfig, - - /// GRPC Config - pub grpc: GrpcConfig, - - /// RMQ Config - pub rmq: RmqConfig, - - /// Redis configuration - pub redis: RedisConfig, -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct ApiConfig { - /// Bind address for the API - pub bind_address: SocketAddr, - - /// If we should use TLS for the API server - pub tls: Option, -} - -impl Default for ApiConfig { - fn default() -> Self { - Self { - bind_address: "[::]:4000".parse().expect("failed to parse bind address"), - tls: None, - } - } -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct DatabaseConfig { - /// The database URL to use - pub uri: String, -} - -impl Default for DatabaseConfig { - fn default() -> Self { - Self { - uri: "postgres://root@localhost:5432/scuffle_dev".to_string(), - } - } -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct TurnstileConfig { - /// The Cloudflare Turnstile site key to use - pub secret_key: String, - - /// The Cloudflare Turnstile url to use - pub url: String, -} - -impl Default for TurnstileConfig { - fn default() -> Self { - Self { - secret_key: "DUMMY_KEY__SAMPLE_TEXT".to_string(), - url: "https://challenges.cloudflare.com/turnstile/v0/siteverify".to_string(), - } - } -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct JwtConfig { - /// JWT secret - pub secret: String, - - /// JWT issuer - pub issuer: String, -} - -impl Default for JwtConfig { - fn default() -> Self { - Self { - issuer: "scuffle".to_string(), - secret: "scuffle".to_string(), - } - } -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct GrpcConfig { - /// Bind address for the GRPC server - pub bind_address: SocketAddr, - - /// If we should use TLS for the gRPC server - pub tls: Option, -} - -impl Default for GrpcConfig { - fn default() -> Self { - Self { - bind_address: "[::]:50051".parse().expect("failed to parse bind address"), - tls: None, - } - } -} - -impl Default for AppConfig { - fn default() -> Self { - Self { - config_file: Some("config".to_string()), - name: "scuffle-api".to_string(), - logging: LoggingConfig::default(), - api: ApiConfig::default(), - database: DatabaseConfig::default(), - grpc: GrpcConfig::default(), - jwt: JwtConfig::default(), - turnstile: TurnstileConfig::default(), - rmq: RmqConfig::default(), - redis: RedisConfig::default(), - } - } -} - -impl AppConfig { - pub fn parse() -> Result { - let (mut config, config_file) = - common::config::parse::(!cfg!(test), Self::default().config_file)?; - - config.config_file = config_file; - - Ok(config) - } -} diff --git a/backend/api/src/database/channel_role.rs b/backend/api/src/database/channel_role.rs deleted file mode 100644 index fe3416c04..000000000 --- a/backend/api/src/database/channel_role.rs +++ /dev/null @@ -1,36 +0,0 @@ -use bitmask_enum::bitmask; -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default)] -/// A role that can be granted to a user in a channel. -/// Roles can allow or deny permissions to a user. -/// The rank indicates the order in which the role permissions are applied. -/// Roles can have many users granted to them. See the `channel_role_grant` table for more information. -pub struct Model { - /// The unique identifier for the role. - pub id: Uuid, - /// Foreign key to the users table. - pub channel_id: Uuid, - /// The name of the role. - pub name: String, - /// The description of the role. - pub description: String, - /// The rank of the role. (higher rank = priority) unique per channel (-1 is default role) - pub rank: i32, - /// The permissions granted by this role. - pub allowed_permissions: Permission, - /// The permissions denied by this role. - pub denied_permissions: Permission, - /// The time the role was created. - pub created_at: DateTime, -} - -#[bitmask(i64)] -pub enum Permission {} - -impl Default for Permission { - fn default() -> Self { - Self::none() - } -} diff --git a/backend/api/src/database/channel_role_grant.rs b/backend/api/src/database/channel_role_grant.rs deleted file mode 100644 index 1c9a1ddad..000000000 --- a/backend/api/src/database/channel_role_grant.rs +++ /dev/null @@ -1,17 +0,0 @@ -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default)] -/// A grant of a channel role to a user. -/// This allows for channel owners to grant roles to other users in their channel. -/// See the `channel_role` table for more information. -pub struct Model { - /// The unique identifier for the grant. - pub id: Uuid, - /// Foreign key to the user table. - pub user_id: Uuid, - /// Foreign key to the channel_role table. - pub channel_role_id: Uuid, - /// The time the grant was created. - pub created_at: DateTime, -} diff --git a/backend/api/src/database/chat_message.rs b/backend/api/src/database/chat_message.rs deleted file mode 100644 index 4d701e923..000000000 --- a/backend/api/src/database/chat_message.rs +++ /dev/null @@ -1,16 +0,0 @@ -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default)] -pub struct Model { - /// The unique identifier for the chat message. - pub id: Uuid, - /// The unique identifier for the chat room which owns the message. - pub channel_id: Uuid, - /// The unique identifier for the user who sent the message. - pub author_id: Uuid, - /// The content of the message. - pub content: String, - /// The time the message was created. - pub created_at: DateTime, -} diff --git a/backend/api/src/database/global_role.rs b/backend/api/src/database/global_role.rs deleted file mode 100644 index aeec454bd..000000000 --- a/backend/api/src/database/global_role.rs +++ /dev/null @@ -1,51 +0,0 @@ -use bitmask_enum::bitmask; -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default)] -/// A role that can be granted to a user globally. -/// Roles can allow or deny permissions to a user. -/// The rank indicates the order in which the role permissions are applied. -/// Roles can have many users granted to them. See the `global_role_grant` table for more information. -pub struct Model { - /// The unique identifier for the role. - pub id: Uuid, - /// The name of the role. - pub name: String, - /// The description of the role. - pub description: String, - /// The rank of the role. (higher rank = priority) (-1 is default role) - pub rank: i64, - /// The permissions granted by this role. - pub allowed_permissions: Permission, - /// The permissions denied by this role. - pub denied_permissions: Permission, - /// The time the role was created. - pub created_at: DateTime, -} - -#[bitmask(i64)] -pub enum Permission { - /// Can do anything - Admin, - /// Can start streaming - GoLive, - /// Has access to transcoded streams - StreamTranscoding, - /// Has access to recorded streams - StreamRecording, -} - -impl Default for Permission { - fn default() -> Self { - Self::none() - } -} - -impl Permission { - /// Checks if the current permission set has the given permission. - /// Admin permissions always return true. Otherwise, the permission is checked against the current permission set. - pub fn has_permission(&self, other: Self) -> bool { - (*self & Self::Admin == Self::Admin) || (*self & other == other) - } -} diff --git a/backend/api/src/database/global_role_grant.rs b/backend/api/src/database/global_role_grant.rs deleted file mode 100644 index e5df37751..000000000 --- a/backend/api/src/database/global_role_grant.rs +++ /dev/null @@ -1,17 +0,0 @@ -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default)] -/// A grant of a global role to a user. -/// This allows for Admins to grant roles to other users. -/// See the `global_role` table for more information. -pub struct Model { - /// The unique identifier for the grant. - pub id: Uuid, - /// Foreign key to the user table. - pub user_id: Uuid, - /// Foreign key to the global_role table. - pub global_role_id: Uuid, - /// The time the grant was created. - pub created_at: DateTime, -} diff --git a/backend/api/src/database/mod.rs b/backend/api/src/database/mod.rs deleted file mode 100644 index f21466ac9..000000000 --- a/backend/api/src/database/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod channel_role; -pub mod channel_role_grant; -pub mod chat_message; -pub mod global_role; -pub mod global_role_grant; -pub mod protobuf; -pub mod session; -pub mod stream; -pub mod stream_bitrate_update; -pub mod stream_event; -pub mod user; diff --git a/backend/api/src/database/protobuf.rs b/backend/api/src/database/protobuf.rs deleted file mode 100644 index b004118de..000000000 --- a/backend/api/src/database/protobuf.rs +++ /dev/null @@ -1,64 +0,0 @@ -#[derive(Debug, Clone, Default)] -pub enum ProtobufValue { - #[default] - None, - Some(T), - Err(prost::DecodeError), -} - -impl ProtobufValue { - #[allow(dead_code)] - pub fn unwrap(self) -> Option { - match self { - Self::Some(data) => Some(data), - Self::None => None, - Self::Err(err) => panic!( - "called `ProtobufValue::unwrap()` on a `Err` value: {:?}", - err - ), - } - } -} - -impl From> for ProtobufValue -where - ProtobufValue: From, -{ - fn from(data: Option) -> Self { - match data { - Some(data) => Self::from(data), - None => Self::None, - } - } -} - -impl From> for ProtobufValue { - fn from(data: Vec) -> Self { - match T::decode(data.as_slice()) { - Ok(variants) => Self::Some(variants), - Err(e) => Self::Err(e), - } - } -} - -impl PartialEq for ProtobufValue { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::None, Self::None) => true, - (Self::Some(a), Self::Some(b)) => a == b, - _ => false, - } - } -} - -impl PartialEq> - for ProtobufValue -{ - fn eq(&self, other: &Option) -> bool { - match (self, other) { - (Self::None, None) => true, - (Self::Some(a), Some(b)) => a == b, - _ => false, - } - } -} diff --git a/backend/api/src/database/session.rs b/backend/api/src/database/session.rs deleted file mode 100644 index 1f8703e54..000000000 --- a/backend/api/src/database/session.rs +++ /dev/null @@ -1,32 +0,0 @@ -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default)] -pub struct Model { - /// The unique identifier for the session. - pub id: Uuid, - /// Foreign key to the user table. - pub user_id: Uuid, - /// The time the session was invalidated. - pub invalidated_at: Option>, - /// The time the session was created. - pub created_at: DateTime, - /// The time the session expires. - pub expires_at: DateTime, - /// The time the session was last used. - pub last_used_at: DateTime, -} - -impl Model { - pub fn is_valid(&self) -> bool { - if self.invalidated_at.is_some() { - return false; - } - - if self.expires_at < Utc::now() { - return false; - } - - true - } -} diff --git a/backend/api/src/database/stream.rs b/backend/api/src/database/stream.rs deleted file mode 100644 index 5b3798ff9..000000000 --- a/backend/api/src/database/stream.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::pb::scuffle::types::StreamState; -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -use super::protobuf::ProtobufValue; - -#[derive(Debug, Clone, Default, Copy, Eq, PartialEq)] -#[repr(i64)] -pub enum ReadyState { - #[default] - NotReady = 0, - Ready = 1, - Stopped = 2, - StoppedResumable = 3, - Failed = 4, - WasReady = 5, -} - -impl From for i64 { - fn from(state: ReadyState) -> Self { - match state { - ReadyState::NotReady => 0, - ReadyState::Ready => 1, - ReadyState::Stopped => 2, - ReadyState::StoppedResumable => 3, - ReadyState::Failed => 4, - ReadyState::WasReady => 5, - } - } -} - -impl From for ReadyState { - fn from(state: i64) -> Self { - match state { - 0 => ReadyState::NotReady, - 1 => ReadyState::Ready, - 2 => ReadyState::Stopped, - 3 => ReadyState::StoppedResumable, - 4 => ReadyState::Failed, - 5 => ReadyState::WasReady, - _ => ReadyState::NotReady, - } - } -} - -#[derive(Debug, Clone, Default, sqlx::FromRow)] -pub struct Model { - /// The unique identifier for the stream. - pub id: Uuid, - /// The unique identifier for the channel which owns the stream. - pub channel_id: Uuid, - /// The current title of the stream. - pub title: String, - /// The current description of the stream. - pub description: String, - /// Whether or not the stream had recording enabled. - pub recorded: bool, - /// Whether or not the stream had transcoding enabled. - pub transcoded: bool, - /// Whether or not the stream has been deleted. - pub deleted: bool, - /// Whether or not the stream is ready to be viewed. - pub ready_state: ReadyState, - /// Ingest Address address of the ingest server controlling the stream. - pub ingest_address: String, - /// The connection which owns the stream. - pub connection_id: Uuid, - /// The Stream Variants - pub state: ProtobufValue, - /// The time the stream was created. - pub created_at: DateTime, - /// The time the stream was last updated. - /// Used to check if the stream is alive or if its resumable. - pub updated_at: Option>, - /// The time the stream ended. (will be in the future if the stream is live) - pub ended_at: DateTime, -} diff --git a/backend/api/src/database/stream_bitrate_update.rs b/backend/api/src/database/stream_bitrate_update.rs deleted file mode 100644 index 24723210e..000000000 --- a/backend/api/src/database/stream_bitrate_update.rs +++ /dev/null @@ -1,11 +0,0 @@ -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default, sqlx::FromRow)] -pub struct Model { - pub stream_id: Uuid, - pub video_bitrate: i64, - pub audio_bitrate: i64, - pub metadata_bitrate: i64, - pub created_at: DateTime, -} diff --git a/backend/api/src/database/stream_event.rs b/backend/api/src/database/stream_event.rs deleted file mode 100644 index 195e2845e..000000000 --- a/backend/api/src/database/stream_event.rs +++ /dev/null @@ -1,44 +0,0 @@ -use chrono::{DateTime, Utc}; -use uuid::Uuid; - -#[derive(Debug, Clone, Default, Copy, PartialEq, Eq)] -#[repr(i64)] -pub enum Level { - #[default] - Info = 0, - Warning = 1, - Error = 2, -} - -impl From for Level { - fn from(value: i64) -> Self { - match value { - 0 => Self::Info, - 1 => Self::Warning, - 2 => Self::Error, - _ => Self::Info, - } - } -} - -impl From for i64 { - fn from(value: Level) -> Self { - match value { - Level::Info => 0, - Level::Warning => 1, - Level::Error => 2, - } - } -} - -#[derive(Debug, Clone, Default, sqlx::FromRow)] -pub struct Model { - /// The unique identifier for the stream variant. - pub id: Uuid, - /// The unique identifier for the stream. - pub stream_id: Uuid, - pub title: String, - pub message: String, - pub level: Level, - pub created_at: DateTime, -} diff --git a/backend/api/src/database/user.rs b/backend/api/src/database/user.rs deleted file mode 100644 index c51eb0e4c..000000000 --- a/backend/api/src/database/user.rs +++ /dev/null @@ -1,175 +0,0 @@ -use argon2::{ - password_hash::{rand_core::OsRng, SaltString}, - Argon2, PasswordHash, PasswordHasher, PasswordVerifier, -}; -use chrono::{DateTime, Utc}; -use rand::Rng; -use uuid::Uuid; - -#[derive(Debug, Clone, Default)] -#[repr(i32)] -pub enum LiveState { - #[default] - NotLive = 0, - Live = 1, - LiveReady = 2, -} - -impl From for LiveState { - fn from(value: i32) -> Self { - match value { - 0 => Self::NotLive, - 1 => Self::Live, - 2 => Self::LiveReady, - _ => Self::NotLive, - } - } -} - -#[derive(Debug, Clone, Default)] -pub struct Model { - /// The unique identifier for the user. - pub id: Uuid, - /// The username of the user. - pub username: String, - /// The display name of the user. - pub display_name: String, - /// The hashed password of the user. (argon2) - pub password_hash: String, - /// The email of the user. - pub email: String, - /// Whether the user has verified their email. - pub email_verified: bool, - /// The time the user was created. - pub created_at: DateTime, - /// The time the user last logged in. - pub last_login_at: DateTime, - /// The stream key of the user. - pub stream_key: String, - /// The title of the stream - pub stream_title: String, - /// The description of the stream - pub stream_description: String, - /// Whether the stream transcoding is enabled - pub stream_transcoding_enabled: bool, - /// Whether the stream recording is enabled - pub stream_recording_enabled: bool, -} - -impl Model { - /// Uses argon2 to verify the password hash against the provided password. - pub fn verify_password(&self, password: &str) -> bool { - let hash = match PasswordHash::new(&self.password_hash) { - Ok(hash) => hash, - Err(err) => { - tracing::error!("failed to parse password hash: {}", err); - return false; - } - }; - - Argon2::default() - .verify_password(password.as_bytes(), &hash) - .is_ok() - } - - pub fn get_stream_key(&self) -> String { - format!("live_{}_{}", self.id.as_u128(), self.stream_key) - } -} - -/// Generates a new password hash using argon2. -pub fn hash_password(password: &str) -> String { - let salt = SaltString::generate(&mut OsRng); - - let hash = Argon2::default() - .hash_password(password.as_bytes(), &salt) - .expect("failed to hash password"); - - hash.to_string() -} - -/// Validates a username. -pub fn validate_username(username: &str) -> Result<(), &'static str> { - if username.len() < 3 { - return Err("Username must be at least 3 characters long"); - } - - if username.len() > 20 { - return Err("Username must be at most 20 characters long"); - } - - if !username - .chars() - .all(|c| c.is_ascii_alphanumeric() || c == '_') - { - return Err("Username must only contain alphanumeric characters and underscores"); - } - - Ok(()) -} - -/// Validates a password. -pub fn validate_password(password: &str) -> Result<(), &'static str> { - if password.len() < 8 { - return Err("Password must be at least 8 characters long"); - } - - if !password.chars().any(|c| c.is_ascii_lowercase()) { - return Err("Password must contain at least one lowercase character"); - } - - if !password.chars().any(|c| c.is_ascii_uppercase()) { - return Err("Password must contain at least one uppercase character"); - } - - if !password.chars().any(|c| c.is_ascii_digit()) { - return Err("Password must contain at least one digit"); - } - - if !password.chars().any(|c| !c.is_ascii_alphanumeric()) { - return Err("Password must contain at least one special character"); - } - - if password.len() > 100 { - return Err("Password must be at most 100 characters long"); - } - - Ok(()) -} - -/// Validates an email. -pub fn validate_email(email: &str) -> Result<(), &'static str> { - if email.len() < 5 { - return Err("Email must be at least 5 characters long"); - } - - if email.len() > 100 { - return Err("Email must be at most 100 characters long"); - } - - if !email.contains('@') { - return Err("Email must contain an @"); - } - - if !email.contains('.') { - return Err("Email must contain a ."); - } - - if !email_address::EmailAddress::is_valid(email) { - return Err("Email is not a valid email address"); - } - - Ok(()) -} - -/// Generates a new stream key. -pub fn generate_stream_key() -> String { - let mut rng = rand::thread_rng(); - let mut key = String::new(); - - for _ in 0..24 { - key.push(rng.sample(rand::distributions::Alphanumeric).into()); - } - - key -} diff --git a/backend/api/src/dataloader/mod.rs b/backend/api/src/dataloader/mod.rs deleted file mode 100644 index 09eb5b76d..000000000 --- a/backend/api/src/dataloader/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod session; -pub mod stream; -pub mod user; -pub mod user_permissions; diff --git a/backend/api/src/dataloader/session.rs b/backend/api/src/dataloader/session.rs deleted file mode 100644 index ba8e11599..000000000 --- a/backend/api/src/dataloader/session.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::database::session; -use async_graphql::{ - async_trait::async_trait, - dataloader::{DataLoader, Loader}, -}; -use std::{collections::HashMap, sync::Arc}; -use uuid::Uuid; - -pub struct SessionByIdLoader { - db: Arc, -} - -impl SessionByIdLoader { - pub fn new(db: Arc) -> DataLoader { - DataLoader::new(Self { db }, tokio::spawn) - } -} - -#[async_trait] -impl Loader for SessionByIdLoader { - type Value = session::Model; - type Error = Arc; - - async fn load(&self, keys: &[Uuid]) -> Result, Self::Error> { - let results = sqlx::query_as!( - session::Model, - "SELECT * FROM sessions WHERE id = ANY($1)", - &keys - ) - .fetch_all(&*self.db) - .await - .map_err(|e| { - tracing::error!("Failed to fetch sessions: {}", e); - Arc::new(e) - })?; - - let mut map = HashMap::new(); - - for result in results { - map.insert(result.id, result); - } - - Ok(map) - } -} diff --git a/backend/api/src/dataloader/stream.rs b/backend/api/src/dataloader/stream.rs deleted file mode 100644 index 5bd2c50de..000000000 --- a/backend/api/src/dataloader/stream.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::database::stream; -use async_graphql::{ - async_trait::async_trait, - dataloader::{DataLoader, Loader}, -}; -use std::{collections::HashMap, sync::Arc}; -use uuid::Uuid; - -pub struct StreamByIdLoader { - db: Arc, -} - -impl StreamByIdLoader { - pub fn new(db: Arc) -> DataLoader { - DataLoader::new(Self { db }, tokio::spawn) - } -} - -#[async_trait] -impl Loader for StreamByIdLoader { - type Value = stream::Model; - type Error = Arc; - - async fn load(&self, keys: &[Uuid]) -> Result, Self::Error> { - let results = sqlx::query_as!( - stream::Model, - "SELECT * FROM streams WHERE id = ANY($1)", - &keys - ) - .fetch_all(&*self.db) - .await - .map_err(|e| { - tracing::error!("Failed to fetch streams: {}", e); - Arc::new(e) - })?; - - let mut map = HashMap::new(); - - for result in results { - map.insert(result.id, result); - } - - Ok(map) - } -} diff --git a/backend/api/src/dataloader/user.rs b/backend/api/src/dataloader/user.rs deleted file mode 100644 index 370757054..000000000 --- a/backend/api/src/dataloader/user.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::database::user; -use async_graphql::{ - async_trait::async_trait, - dataloader::{DataLoader, Loader}, -}; -use std::{collections::HashMap, sync::Arc}; -use uuid::Uuid; - -pub struct UserByUsernameLoader { - db: Arc, -} - -impl UserByUsernameLoader { - pub fn new(db: Arc) -> DataLoader { - DataLoader::new(Self { db }, tokio::spawn) - } -} - -#[async_trait] -impl Loader for UserByUsernameLoader { - type Value = user::Model; - type Error = Arc; - - async fn load(&self, keys: &[String]) -> Result, Self::Error> { - let results = sqlx::query_as!( - user::Model, - "SELECT * FROM users WHERE username = ANY($1)", - &keys - ) - .fetch_all(&*self.db) - .await?; - - let mut map = HashMap::new(); - - for result in results { - map.insert(result.username.clone(), result); - } - - Ok(map) - } -} - -pub struct UserByIdLoader { - db: Arc, -} - -impl UserByIdLoader { - pub fn new(db: Arc) -> DataLoader { - DataLoader::new(Self { db }, tokio::spawn) - } -} - -#[async_trait] -impl Loader for UserByIdLoader { - type Value = user::Model; - type Error = Arc; - - async fn load(&self, keys: &[Uuid]) -> Result, Self::Error> { - let results = sqlx::query_as!(user::Model, "SELECT * FROM users WHERE id = ANY($1)", &keys) - .fetch_all(&*self.db) - .await - .map_err(|e| { - tracing::error!("Failed to fetch users: {}", e); - Arc::new(e) - })?; - - let mut map = HashMap::new(); - - for result in results { - map.insert(result.id, result); - } - - Ok(map) - } -} diff --git a/backend/api/src/dataloader/user_permissions.rs b/backend/api/src/dataloader/user_permissions.rs deleted file mode 100644 index c8683ca6e..000000000 --- a/backend/api/src/dataloader/user_permissions.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::database::global_role; -use async_graphql::{ - async_trait::async_trait, - dataloader::{DataLoader, Loader}, -}; -use std::{collections::HashMap, sync::Arc}; -use uuid::Uuid; - -pub struct UserPermissionsByIdLoader { - db: Arc, -} - -impl UserPermissionsByIdLoader { - pub fn new(db: Arc) -> DataLoader { - DataLoader::new(Self { db }, tokio::spawn) - } -} - -#[derive(Debug, Clone, Default)] -pub struct UserPermission { - pub user_id: Uuid, - pub permissions: global_role::Permission, - pub roles: Vec, -} - -#[async_trait] -impl Loader for UserPermissionsByIdLoader { - type Value = UserPermission; - type Error = Arc; - - async fn load(&self, keys: &[Uuid]) -> Result, Self::Error> { - let default_role = sqlx::query_as!( - global_role::Model, - "SELECT * FROM global_roles WHERE rank = -1", - ) - .fetch_optional(&*self.db) - .await - .map_err(|e| { - tracing::error!("Failed to fetch default role: {}", e); - Arc::new(e) - })?; - - let results = sqlx::query!( - "SELECT rg.user_id, r.* FROM global_role_grants rg JOIN global_roles r ON rg.global_role_id = r.id WHERE rg.user_id = ANY($1) ORDER BY rg.user_id, r.rank ASC", - &keys - ) - .fetch_all(&*self.db) - .await.map_err(|e| { - tracing::error!("Failed to fetch user permissions: {}", e); - Arc::new(e) - })?; - - let mut map = HashMap::new(); - - // We only care about the allowed_permissions, because the denied permissions only work on previous roles. - // Since this is the first role, there are no previous roles, so the denied permissions are irrelevant. - if let Some(default_role) = default_role { - for key in keys { - map.insert( - *key, - UserPermission { - user_id: *key, - permissions: default_role.allowed_permissions, - roles: vec![default_role.clone()], - }, - ); - } - } else { - for key in keys { - map.insert( - *key, - UserPermission { - user_id: *key, - permissions: global_role::Permission::default(), - roles: Vec::new(), - }, - ); - } - } - - for result in results { - let current_user = map.entry(result.user_id).or_insert_with(|| UserPermission { - user_id: result.user_id, - permissions: global_role::Permission::default(), - roles: Vec::new(), - }); - - current_user.permissions |= global_role::Permission::from(result.allowed_permissions); - current_user.permissions &= !global_role::Permission::from(result.denied_permissions); - - current_user.roles.push(global_role::Model { - id: result.id, - name: result.name, - description: result.description, - allowed_permissions: result.allowed_permissions.into(), - denied_permissions: result.denied_permissions.into(), - created_at: result.created_at, - rank: result.rank, - }); - } - - Ok(map) - } -} diff --git a/backend/api/src/global/mod.rs b/backend/api/src/global/mod.rs deleted file mode 100644 index 138ccb0e8..000000000 --- a/backend/api/src/global/mod.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::config::AppConfig; -use std::sync::Arc; -use std::time::Duration; - -use async_graphql::dataloader::DataLoader; -use common::context::Context; -use common::prelude::FutureTimeout; -use fred::clients::SubscriberClient; -use fred::native_tls; -use fred::pool::RedisPool; -use fred::prelude::ClientLike; -use fred::types::{ReconnectPolicy, RedisConfig, ServerConfig}; - -use crate::dataloader::stream::StreamByIdLoader; -use crate::dataloader::user_permissions::UserPermissionsByIdLoader; -use crate::dataloader::{ - session::SessionByIdLoader, user::UserByIdLoader, user::UserByUsernameLoader, -}; -use crate::subscription::SubscriptionManager; - -pub mod turnstile; - -pub struct GlobalState { - pub config: AppConfig, - pub db: Arc, - pub ctx: Context, - pub user_by_username_loader: DataLoader, - pub user_by_id_loader: DataLoader, - pub session_by_id_loader: DataLoader, - pub user_permisions_by_id_loader: DataLoader, - pub stream_by_id_loader: DataLoader, - pub subscription_manager: SubscriptionManager, - pub rmq: common::rmq::ConnectionPool, - pub redis: RedisPool, -} - -impl GlobalState { - pub fn new( - config: AppConfig, - db: Arc, - rmq: common::rmq::ConnectionPool, - redis: RedisPool, - ctx: Context, - ) -> Self { - Self { - config, - ctx, - user_by_username_loader: UserByUsernameLoader::new(db.clone()), - user_by_id_loader: UserByIdLoader::new(db.clone()), - session_by_id_loader: SessionByIdLoader::new(db.clone()), - user_permisions_by_id_loader: UserPermissionsByIdLoader::new(db.clone()), - stream_by_id_loader: StreamByIdLoader::new(db.clone()), - subscription_manager: SubscriptionManager::default(), - db, - rmq, - redis, - } - } -} - -pub fn redis_config(config: &AppConfig) -> RedisConfig { - RedisConfig { - database: Some(config.redis.database), - username: config.redis.username.clone(), - password: config.redis.password.clone(), - server: if let Some(sentinel) = &config.redis.sentinel { - let addresses = config - .redis - .addresses - .iter() - .map(|a| { - let mut parts = a.split(':'); - let host = parts.next().expect("no redis host"); - let port = parts - .next() - .expect("no redis port") - .parse() - .expect("failed to parse redis port"); - - (host, port) - }) - .collect::>(); - - ServerConfig::new_sentinel(addresses, sentinel.service_name.clone()) - } else { - let server = config.redis.addresses.first().expect("no redis addresses"); - if config.redis.addresses.len() > 1 { - tracing::warn!("multiple redis addresses, only using first: {}", server); - } - - let mut parts = server.split(':'); - let host = parts.next().expect("no redis host"); - let port = parts - .next() - .expect("no redis port") - .parse() - .expect("failed to parse redis port"); - - ServerConfig::new_centralized(host, port) - }, - tls: if let Some(tls) = &config.redis.tls { - let cert = std::fs::read(&tls.cert).expect("failed to read redis cert"); - let key = std::fs::read(&tls.key).expect("failed to read redis key"); - let ca_cert = std::fs::read(&tls.ca_cert).expect("failed to read redis ca"); - - Some( - fred::native_tls::TlsConnector::builder() - .identity( - native_tls::Identity::from_pkcs8(&cert, &key) - .expect("failed to parse redis cert/key"), - ) - .add_root_certificate( - native_tls::Certificate::from_pem(&ca_cert) - .expect("failed to parse redis ca"), - ) - .build() - .expect("failed to build redis tls") - .into(), - ) - } else { - None - }, - ..Default::default() - } -} - -pub async fn setup_redis(config: &AppConfig) -> RedisPool { - let redis = RedisPool::new( - redis_config(config), - Some(Default::default()), - Some(Default::default()), - config.redis.pool_size, - ) - .expect("failed to create redis pool"); - - redis.connect(); - - redis - .wait_for_connect() - .timeout(Duration::from_secs(2)) - .await - .expect("failed to connect to redis") - .expect("failed to connect to redis"); - - redis -} - -pub async fn setup_redis_subscription( - config: &AppConfig, - reconnect_policy: ReconnectPolicy, -) -> SubscriberClient { - let redis = SubscriberClient::new( - redis_config(config), - Some(Default::default()), - Some(reconnect_policy), - ); - - redis.connect(); - - redis - .wait_for_connect() - .timeout(Duration::from_secs(2)) - .await - .expect("failed to connect to redis") - .expect("failed to connect to redis"); - - redis -} diff --git a/backend/api/src/global/turnstile.rs b/backend/api/src/global/turnstile.rs deleted file mode 100644 index 480395715..000000000 --- a/backend/api/src/global/turnstile.rs +++ /dev/null @@ -1,29 +0,0 @@ -use anyhow::Result; -use serde::de::Error; -use serde_json::json; - -use super::GlobalState; - -impl GlobalState { - pub async fn validate_turnstile_token(&self, token: &str) -> Result { - let client = reqwest::Client::new(); - - let body = json!({ - "response": token, - "secret": self.config.turnstile.secret_key, - }); - - let res = client - .post(self.config.turnstile.url.as_str()) - .header("Content-Type", "application/json") - .json(&body) - .send() - .await?; - - let body = res.json::().await?; - - Ok(body["success"] - .as_bool() - .ok_or(serde_json::Error::missing_field("success"))?) - } -} diff --git a/backend/api/src/gql.nocov.rs b/backend/api/src/gql.nocov.rs deleted file mode 100644 index 25d66ab0f..000000000 --- a/backend/api/src/gql.nocov.rs +++ /dev/null @@ -1,30 +0,0 @@ -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_variables)] - -mod api; -mod config; -mod database; -mod dataloader; -mod global; -mod pb; -mod subscription; - -use api::v1::gql::schema; -use async_graphql::SDLExportOptions; - -fn main() { - let schema = schema(); - - println!( - "{}", - schema.sdl_with_options( - SDLExportOptions::default() - .federation() - .include_specified_by() - .sorted_arguments() - .sorted_enum_items() - .sorted_fields() - ) - ); -} diff --git a/backend/api/src/grpc/api.rs b/backend/api/src/grpc/api.rs deleted file mode 100644 index 82c03e62c..000000000 --- a/backend/api/src/grpc/api.rs +++ /dev/null @@ -1,432 +0,0 @@ -use crate::global::GlobalState; -use std::sync::{Arc, Weak}; - -use crate::database::{ - global_role, - stream::{self, ReadyState}, - stream_event, -}; -use chrono::{Duration, TimeZone, Utc}; -use prost::Message; -use tonic::{async_trait, Request, Response, Status}; -use uuid::Uuid; - -use crate::pb::scuffle::backend::{ - api_server, - update_live_stream_request::{event::Level, update::Update}, - AuthenticateLiveStreamRequest, AuthenticateLiveStreamResponse, NewLiveStreamRequest, - NewLiveStreamResponse, StreamReadyState, UpdateLiveStreamRequest, UpdateLiveStreamResponse, -}; - -type Result = std::result::Result; - -pub struct ApiServer { - global: Weak, -} - -impl ApiServer { - pub fn new(global: &Arc) -> Self { - Self { - global: Arc::downgrade(global), - } - } - - pub fn into_service(self) -> api_server::ApiServer { - api_server::ApiServer::new(self) - } -} -#[async_trait] -impl api_server::Api for ApiServer { - async fn authenticate_live_stream( - &self, - request: Request, - ) -> Result> { - let global = self - .global - .upgrade() - .ok_or_else(|| Status::internal("internal server error"))?; - - // Split the stream key into components - let request = request.into_inner(); - - let components = request.stream_key.split('_').collect::>(); - if components.len() != 3 { - return Err(Status::invalid_argument("invalid stream key")); - } - - let (live, channel_id, stream_key) = ( - components[0].to_string(), - components[1].to_string(), - components[2].to_string(), - ); - - if live != "live" { - return Err(Status::invalid_argument("invalid stream key")); - } - - let channel_id = Uuid::from_u128( - channel_id - .parse::() - .map_err(|_| Status::invalid_argument("invalid stream key"))?, - ); - - let channel = global - .user_by_id_loader - .load_one(channel_id) - .await - .map_err(|_| Status::internal("failed to query database"))? - .ok_or_else(|| Status::invalid_argument("invalid stream key"))?; - - if channel.stream_key != stream_key { - return Err(Status::invalid_argument( - "invalid stream key: incorrect stream key", - )); - } - - // Check user permissions - let Ok(permissions) = global - .user_permisions_by_id_loader - .load_one(channel_id) - .await - else { - return Err(Status::internal("failed to query database")); - }; - - let Some(user_permissions) = permissions else { - return Err(Status::permission_denied( - "user has no permission to go live", - )); - }; - - if !user_permissions - .permissions - .has_permission(global_role::Permission::GoLive) - { - return Err(Status::permission_denied( - "user has no permission to go live", - )); - } - - // We need to create a new stream ID for this stream - let mut tx = global.db.begin().await.map_err(|e| { - tracing::error!("failed to begin transaction: {}", e); - Status::internal("internal server error") - })?; - - let record = user_permissions - .permissions - .has_permission(global_role::Permission::StreamRecording) - && channel.stream_recording_enabled; - let transcode = user_permissions - .permissions - .has_permission(global_role::Permission::StreamTranscoding) - && channel.stream_transcoding_enabled; - - let stream = match sqlx::query_as!( - stream::Model, - "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id, ended_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *", - channel_id, - channel.stream_title, - channel.stream_description, - record, - transcode, - request.ingest_address, - request.connection_id.parse::().map_err(|_| Status::invalid_argument("invalid connection ID: must be a valid UUID"))?, - Utc::now() + chrono::Duration::seconds(300), - ).fetch_one(&mut *tx).await { - Ok(stream) => stream, - Err(e) => { - tracing::error!("failed to insert stream: {}", e); - return Err(Status::internal("internal server error")); - } - }; - - if let Err(e) = tx.commit().await { - tracing::error!("failed to commit transaction: {}", e); - return Err(Status::internal("internal server error")); - } - - Ok(Response::new(AuthenticateLiveStreamResponse { - stream_id: stream.id.to_string(), - record, - transcode, - state: None, - })) - } - - async fn update_live_stream( - &self, - request: Request, - ) -> Result> { - let global = self - .global - .upgrade() - .ok_or_else(|| Status::internal("internal server error"))?; - - let request = request.into_inner(); - - let stream_id = request - .stream_id - .parse::() - .map_err(|_| Status::invalid_argument("invalid stream ID: must be a valid UUID"))?; - - let connection_id = request - .connection_id - .parse::() - .map_err(|_| Status::invalid_argument("invalid connection ID: must be a valid UUID"))?; - - if request.updates.is_empty() { - return Err(Status::invalid_argument("no updates provided")); - } - - let stream = global - .stream_by_id_loader - .load_one(stream_id) - .await - .map_err(|_| Status::internal("failed to query database"))? - .ok_or_else(|| Status::invalid_argument("invalid stream ID"))?; - - if stream.connection_id != connection_id { - return Err(Status::invalid_argument("invalid connection ID")); - } - - if stream.ended_at < Utc::now() - || stream.ready_state == ReadyState::Stopped - || stream.ready_state == ReadyState::Failed - || stream.ready_state == ReadyState::StoppedResumable - { - return Err(Status::invalid_argument("stream has ended")); - } - - let mut tx = global.db.begin().await.map_err(|e| { - tracing::error!("failed to begin transaction: {}", e); - Status::internal("internal server error") - })?; - - for u in request.updates { - let Some(update) = u.update else { - continue; - }; - - match update { - Update::Bitrate(bt) => { - sqlx::query!( - "INSERT INTO stream_bitrate_updates (stream_id, video_bitrate, audio_bitrate, metadata_bitrate, created_at) VALUES ($1, $2, $3, $4, $5)", - stream_id, - bt.video_bitrate as i64, - bt.audio_bitrate as i64, - bt.metadata_bitrate as i64, - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap(), - ).execute(&mut *tx).await.map_err(|e| { - tracing::error!("failed to insert stream bitrate update: {}", e); - Status::internal("internal server error") - })?; - - sqlx::query!( - "UPDATE streams SET updated_at = $2, ended_at = $3 WHERE id = $1", - stream_id, - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap(), - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap() - + chrono::Duration::seconds(300), - ) - .execute(&mut *tx) - .await - .map_err(|e| { - tracing::error!("failed to insert stream bitrate update: {}", e); - Status::internal("internal server error") - })?; - } - Update::ReadyState(st) => { - let state = StreamReadyState::from_i32(st).ok_or_else(|| { - Status::invalid_argument("invalid ready_state: must be a valid ready_state") - })?; - match state { - StreamReadyState::NotReady | StreamReadyState::Ready => { - sqlx::query!( - "UPDATE streams SET ready_state = $2, updated_at = $3, ended_at = $4 WHERE id = $1", - stream_id, - match state { - StreamReadyState::NotReady => ReadyState::NotReady as i64, - StreamReadyState::Ready => ReadyState::Ready as i64, - _ => unreachable!(), - }, - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap(), - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap() + chrono::Duration::seconds(300), - ) - .execute(&*global.db) - .await - .map_err(|e| { - tracing::error!("failed to update stream state: {}", e); - Status::internal("internal server error") - })?; - } - StreamReadyState::StoppedResumable => { - sqlx::query!( - "UPDATE streams SET ready_state = $2, updated_at = $3, ended_at = $4 WHERE id = $1", - stream_id, - ReadyState::StoppedResumable as i64, - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap(), - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap() + Duration::seconds(300), - ).execute(&*global.db).await.map_err(|e| { - tracing::error!("failed to update stream state: {}", e); - Status::internal("internal server error") - })?; - } - StreamReadyState::Stopped | StreamReadyState::Failed => { - sqlx::query!( - "UPDATE streams SET ready_state = $2, updated_at = $3, ended_at = $3 WHERE id = $1", - stream_id, - match state { - StreamReadyState::Stopped => ReadyState::Stopped as i64, - StreamReadyState::Failed => ReadyState::Failed as i64, - _ => unreachable!(), - }, - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap() - ) - .execute(&*global.db) - .await - .map_err(|e| { - tracing::error!("failed to update stream state: {}", e); - Status::internal("internal server error") - })?; - } - } - } - Update::Event(e) => { - let level = Level::from_i32(e.level).ok_or_else(|| { - Status::invalid_argument("invalid level: must be a valid level") - })?; - let level = match level { - Level::Info => stream_event::Level::Info, - Level::Warning => stream_event::Level::Warning, - Level::Error => stream_event::Level::Error, - }; - - sqlx::query!( - "INSERT INTO stream_events (stream_id, level, title, message, created_at) VALUES ($1, $2, $3, $4, $5)", - stream_id, - level as i64, - e.title, - e.message, - Utc.timestamp_opt(u.timestamp as i64, 0).unwrap(), - ).execute(&mut *tx).await.map_err(|e| { - tracing::error!("failed to insert stream event: {}", e); - Status::internal("internal server error") - })?; - } - Update::State(v) => { - sqlx::query!( - "UPDATE streams SET updated_at = NOW(), state = $2 WHERE id = $1", - stream_id, - v.encode_to_vec(), - ) - .execute(&mut *tx) - .await - .map_err(|e| { - tracing::error!("failed to insert stream bitrate update: {}", e); - Status::internal("internal server error") - })?; - } - } - } - - if let Err(e) = tx.commit().await { - tracing::error!("failed to commit transaction: {}", e); - return Err(Status::internal("internal server error")); - } - - Ok(Response::new(UpdateLiveStreamResponse {})) - } - - async fn new_live_stream( - &self, - request: Request, - ) -> Result> { - let global = self - .global - .upgrade() - .ok_or_else(|| Status::internal("internal server error"))?; - - let request = request.into_inner(); - - let old_stream_id = request - .old_stream_id - .parse::() - .map_err(|_| Status::invalid_argument("invalid old stream ID: must be a valid UUID"))?; - - let Some(old_stream) = global - .stream_by_id_loader - .load_one(old_stream_id) - .await - .map_err(|e| { - tracing::error!("failed to load stream by ID: {}", e); - Status::internal("internal server error") - })? - else { - return Err(Status::not_found("stream not found")); - }; - - if old_stream.ended_at < Utc::now() - || old_stream.ready_state == ReadyState::Stopped - || old_stream.ready_state == ReadyState::Failed - { - return Err(Status::failed_precondition("stream has already ended")); - } - - let stream_id = Uuid::new_v4(); - - let mut tx = global.db.begin().await.map_err(|e| { - tracing::error!("failed to begin transaction: {}", e); - Status::internal("internal server error") - })?; - - // Insert the new stream - sqlx::query!( - "INSERT INTO streams (id, channel_id, title, description, ready_state, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7)", - stream_id, - old_stream.channel_id, - old_stream.title, - old_stream.description, - ReadyState::NotReady as i64, - old_stream.ingest_address, - old_stream.connection_id, - ).execute(&mut *tx).await.map_err(|e| { - tracing::error!("failed to insert stream: {}", e); - Status::internal("internal server error") - })?; - - // Update the old stream - sqlx::query!( - "UPDATE streams SET ready_state = $2, ended_at = NOW(), updated_at = NOW() WHERE id = $1", - old_stream_id, - ReadyState::Stopped as i32, - ) - .execute(&mut *tx) - .await - .map_err(|e| { - tracing::error!("failed to update stream: {}", e); - Status::internal("internal server error") - })?; - - sqlx::query!( - "UPDATE streams SET updated_at = NOW(), state = $2 WHERE id = $1", - stream_id, - request.state.unwrap_or_default().encode_to_vec(), - ) - .execute(&mut *tx) - .await - .map_err(|e| { - tracing::error!("failed to insert stream bitrate update: {}", e); - Status::internal("internal server error") - })?; - - if let Err(e) = tx.commit().await { - tracing::error!("failed to commit transaction: {}", e); - return Err(Status::internal("internal server error")); - } - - Ok(Response::new(NewLiveStreamResponse { - stream_id: stream_id.to_string(), - })) - } -} diff --git a/backend/api/src/grpc/health.rs b/backend/api/src/grpc/health.rs deleted file mode 100644 index dcfde8256..000000000 --- a/backend/api/src/grpc/health.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::global::GlobalState; -use std::{ - pin::Pin, - sync::{Arc, Weak}, -}; - -use async_stream::try_stream; -use futures_util::Stream; -use tonic::{async_trait, Request, Response, Status}; - -use crate::pb::health::{ - health_check_response::ServingStatus, health_server, HealthCheckRequest, HealthCheckResponse, -}; - -pub struct HealthServer { - global: Weak, -} - -impl HealthServer { - pub fn new(global: &Arc) -> Self { - Self { - global: Arc::downgrade(global), - } - } - - pub fn into_service(self) -> health_server::HealthServer { - health_server::HealthServer::new(self) - } -} - -type Result = std::result::Result; - -#[async_trait] -impl health_server::Health for HealthServer { - type WatchStream = Pin> + Send>>; - - async fn check(&self, _: Request) -> Result> { - let serving = self - .global - .upgrade() - .map(|g| !g.ctx.is_done()) - .unwrap_or_default(); - - Ok(Response::new(HealthCheckResponse { - status: if serving { - ServingStatus::Serving.into() - } else { - ServingStatus::NotServing.into() - }, - })) - } - - async fn watch(&self, _: Request) -> Result> { - let global = self.global.clone(); - - let output = try_stream!({ - loop { - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - - let serving = global - .upgrade() - .map(|g| !g.ctx.is_done()) - .unwrap_or_default(); - - yield HealthCheckResponse { - status: if serving { - ServingStatus::Serving.into() - } else { - ServingStatus::NotServing.into() - }, - }; - } - }); - - Ok(Response::new(Box::pin(output))) - } -} diff --git a/backend/api/src/grpc/mod.rs b/backend/api/src/grpc/mod.rs deleted file mode 100644 index e2d0c3603..000000000 --- a/backend/api/src/grpc/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::global::GlobalState; -use anyhow::Result; -use std::sync::Arc; -use tokio::select; -use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; - -pub mod api; -pub mod health; - -pub async fn run(global: Arc) -> Result<()> { - tracing::info!("GRPC Listening on {}", global.config.grpc.bind_address); - - let server = if let Some(tls) = &global.config.grpc.tls { - let cert = tokio::fs::read(&tls.cert).await?; - let key = tokio::fs::read(&tls.key).await?; - let ca_cert = tokio::fs::read(&tls.ca_cert).await?; - tracing::info!("gRPC TLS enabled"); - Server::builder().tls_config( - ServerTlsConfig::new() - .identity(Identity::from_pem(cert, key)) - .client_ca_root(Certificate::from_pem(ca_cert)), - )? - } else { - tracing::info!("gRPC TLS disabled"); - Server::builder() - } - .add_service(api::ApiServer::new(&global).into_service()) - .add_service(health::HealthServer::new(&global).into_service()) - .serve_with_shutdown(global.config.grpc.bind_address, async { - global.ctx.done().await; - }); - - select! { - _ = global.ctx.done() => { - return Ok(()); - }, - r = server => { - if let Err(r) = r { - tracing::error!("gRPC server failed: {:?}", r); - return Err(r.into()); - } - }, - } - - Ok(()) -} diff --git a/backend/api/src/main.rs b/backend/api/src/main.rs deleted file mode 100644 index 74b631a3a..000000000 --- a/backend/api/src/main.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::{str::FromStr, sync::Arc, time::Duration}; - -use anyhow::{Context as _, Result}; -use common::{context::Context, logging, prelude::FutureTimeout, signal}; -use fred::types::ReconnectPolicy; -use sqlx::{postgres::PgConnectOptions, ConnectOptions}; -use tokio::{select, signal::unix::SignalKind, time}; - -mod api; -mod config; -mod database; -mod dataloader; -mod global; -mod grpc; -mod pb; -mod subscription; - -#[cfg(test)] -mod tests; - -#[tokio::main] -async fn main() -> Result<()> { - let config = config::AppConfig::parse()?; - logging::init(&config.logging.level, config.logging.mode)?; - - if let Some(file) = &config.config_file { - tracing::info!(file = file, "loaded config from file"); - } - - tracing::debug!("config: {:#?}", config); - - let db = Arc::new( - sqlx::PgPool::connect_with( - PgConnectOptions::from_str(&config.database.uri)? - .disable_statement_logging() - .to_owned(), - ) - .await?, - ); - - let (ctx, handler) = Context::new(); - - let rmq = common::rmq::ConnectionPool::connect( - config.rmq.uri.clone(), - lapin::ConnectionProperties::default(), - Duration::from_secs(30), - 1, - ) - .timeout(Duration::from_secs(5)) - .await - .context("failed to connect to rabbitmq, timedout")? - .context("failed to connect to rabbitmq")?; - - let redis = global::setup_redis(&config).await; - let subscription_redis = - global::setup_redis_subscription(&config, ReconnectPolicy::new_constant(0, 300)).await; - - tracing::info!("connected to redis"); - - let global = Arc::new(global::GlobalState::new(config, db, rmq, redis, ctx)); - - let api_future = tokio::spawn(api::run(global.clone())); - let grpc_future = tokio::spawn(grpc::run(global.clone())); - - // Listen on both sigint and sigterm and cancel the context when either is received - let mut signal_handler = signal::SignalHandler::new() - .with_signal(SignalKind::interrupt()) - .with_signal(SignalKind::terminate()); - - select! { - r = api_future => tracing::error!("api stopped unexpectedly: {:?}", r), - r = grpc_future => tracing::error!("grpc stopped unexpectedly: {:?}", r), - r = global.rmq.handle_reconnects() => tracing::error!("rmq stopped unexpectedly: {:?}", r), - r = global.subscription_manager.run(global.ctx.clone(), subscription_redis) => tracing::error!("subscription manager stopped unexpectedly: {:?}", r), - _ = signal_handler.recv() => tracing::info!("shutting down"), - } - - // We cannot have a context in scope when we cancel the handler, otherwise it will deadlock. - drop(global); - - // Cancel the context - tracing::info!("waiting for tasks to finish"); - - select! { - _ = time::sleep(Duration::from_secs(60)) => tracing::warn!("force shutting down"), - _ = signal_handler.recv() => tracing::warn!("force shutting down"), - _ = handler.cancel() => tracing::info!("shutting down"), - } - - Ok(()) -} diff --git a/backend/api/src/pb.rs b/backend/api/src/pb.rs deleted file mode 100644 index 16034e4e4..000000000 --- a/backend/api/src/pb.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub mod scuffle { - pub mod backend { - tonic::include_proto!("scuffle.backend"); - } - - pub mod types { - tonic::include_proto!("scuffle.types"); - } - - pub mod events { - tonic::include_proto!("scuffle.events"); - } -} - -pub mod health { - tonic::include_proto!("grpc.health.v1"); -} diff --git a/backend/api/src/subscription.rs b/backend/api/src/subscription.rs deleted file mode 100644 index f30b5f2f9..000000000 --- a/backend/api/src/subscription.rs +++ /dev/null @@ -1,161 +0,0 @@ -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, -}; - -use anyhow::Result; -use common::context::Context; -use fred::{clients::SubscriberClient, prelude::PubsubInterface, types::RedisValue}; -use tokio::{ - select, - sync::{broadcast, mpsc, oneshot, Mutex}, -}; - -#[derive(Debug)] -enum Event { - Subscribe { - topic: String, - tx: oneshot::Sender>, - }, - Unsubscribe { - topic: String, - }, -} - -pub struct SubscriptionManager { - events_tx: mpsc::UnboundedSender, - events_rx: Mutex>, -} - -impl Default for SubscriptionManager { - fn default() -> Self { - // Only one value is needed in the channel. - // This is a way to get around we cannot await in a drop. - let (events_tx, events_rx) = mpsc::unbounded_channel(); - - Self { - events_rx: Mutex::new(events_rx), - events_tx, - } - } -} - -pub struct SubscriberReceiver<'a> { - topic: String, - rx: broadcast::Receiver, - manager: &'a SubscriptionManager, -} - -impl Deref for SubscriberReceiver<'_> { - type Target = broadcast::Receiver; - - fn deref(&self) -> &Self::Target { - &self.rx - } -} - -impl DerefMut for SubscriberReceiver<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.rx - } -} - -impl SubscriptionManager { - pub async fn run(&self, ctx: Context, redis: SubscriberClient) -> Result<()> { - let mut handle = redis.manage_subscriptions(); - - let mut topics = HashMap::>::new(); - - let mut events_rx = self.events_rx.lock().await; - - let mut messages = redis.on_message(); - - loop { - select! { - event = events_rx.recv() => { - match event.unwrap() { - Event::Subscribe { topic, tx } => { - let topic = topic.to_lowercase(); - - match topics.get(&topic) { - Some(broadcast) => { - tx.send(broadcast.subscribe()).ok(); - }, - None => { - let (btx, rx) = broadcast::channel(16); - if tx.send(rx).is_err() { - continue; - } - - topics.insert(topic.clone(), btx); - - redis.subscribe(&topic).await?; - } - }; - } - Event::Unsubscribe { topic } => { - if let Some(btx) = topics.get_mut(&topic) { - if btx.receiver_count() == 0 { - topics.remove(&topic); - redis.unsubscribe(&topic).await?; - } - } - - if topics.is_empty() && ctx.is_done() { - break; - } - } - } - } - message = messages.recv() => { - let message = message.unwrap(); - - let topic = message.channel.to_string().to_lowercase(); - - let Some(subs) = topics.get(&topic) else { - continue; - }; - - subs.send(message.value).ok(); - } - r = &mut handle => { - r?; - break; - } - _ = ctx.done() => { - break; - } - } - } - - Ok(()) - } - - pub async fn subscribe(&self, topic: impl ToString) -> Result> { - let (tx, rx) = oneshot::channel(); - - self.events_tx.send(Event::Subscribe { - topic: topic.to_string(), - tx, - })?; - - let rx = rx.await?; - - Ok(SubscriberReceiver { - topic: topic.to_string(), - rx, - manager: self, - }) - } -} - -impl Drop for SubscriberReceiver<'_> { - fn drop(&mut self) { - self.manager - .events_tx - .send(Event::Unsubscribe { - topic: self.topic.clone(), - }) - .ok(); - } -} diff --git a/backend/api/src/tests/api/errors.rs b/backend/api/src/tests/api/errors.rs deleted file mode 100644 index 95d824f22..000000000 --- a/backend/api/src/tests/api/errors.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::error::Error; - -use http::StatusCode; -use hyper::{ - body::{Bytes, HttpBody}, - Body, -}; - -use crate::api::error::{Result, ResultExt, ShouldLog}; - -#[test] -fn test_error_from_residual_string() { - let fn1 = || -> Result<()> { - if true { Err("error from fn1") } else { Ok(()) }?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(err.should_log(), ShouldLog::Yes); - assert_eq!(err.location().file(), file!()); - assert_eq!(err.response().status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[test] -fn test_error_from_residual_response() { - let fn1 = || -> Result<()> { - if true { - Err(hyper::Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::empty()) - .unwrap()) - } else { - Ok(()) - }?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(err.should_log(), ShouldLog::No); - assert_eq!(err.location().file(), file!()); - assert_eq!(err.response().status(), StatusCode::BAD_REQUEST); -} - -#[test] -fn test_error_from_residual_tuple() { - let fn1 = || -> Result<()> { - if true { - Err((StatusCode::CONFLICT, "error from fn1")) - } else { - Ok(()) - }?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(err.should_log(), ShouldLog::No); - assert_eq!(err.location().file(), file!()); - assert_eq!(err.response().status(), StatusCode::CONFLICT); -} - -#[test] -fn test_error_from_residual_tuple_with_error() { - let fn1 = || -> Result<()> { - if true { - Err(( - StatusCode::CONFLICT, - "error from fn1", - anyhow::anyhow!("error from fn1"), - )) - } else { - Ok(()) - }?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(err.should_log(), ShouldLog::Debug); - assert_eq!(err.location().file(), file!()); - assert_eq!(err.response().status(), StatusCode::CONFLICT); -} - -#[test] -fn test_error_from_residual_error() { - let fn1 = || -> Result<()> { - if true { - Err(anyhow::anyhow!("error from fn1")) - } else { - Ok(()) - } - .map_err_route("failed somewhere")?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(err.should_log(), ShouldLog::Yes); - assert_eq!(err.location().file(), file!()); - assert_eq!(err.response().status(), StatusCode::INTERNAL_SERVER_ERROR); -} - -#[test] -fn test_error_debug_display() { - let fn1 = || -> Result<()> { - if true { - Err(anyhow::anyhow!("error from fn1")) - } else { - Ok(()) - } - .map_err_route("failed somewhere")?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(format!("{:?}", err), "RouteError: error from fn1"); - assert_eq!(format!("{}", err), "RouteError: error from fn1"); -} - -#[test] -fn test_error_debug_display2() { - let fn1 = || -> Result<()> { - if true { Err("error from fn1") } else { Ok(()) }?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(format!("{:?}", err), "RouteError: Unknown Source"); - assert_eq!(format!("{}", err), "RouteError: Unknown Source"); -} - -#[test] -fn test_std_error() { - let fn1 = || -> Result<()> { - if true { - Err(anyhow::anyhow!("error from fn1")) - } else { - Ok(()) - } - .map_err_route("failed somwehere")?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - - assert_eq!(err.source().unwrap().to_string(), "error from fn1"); -} - -#[tokio::test] -async fn test_hyper_response() { - let fn1 = || -> Result<()> { - if true { - Err(hyper::Response::builder() - .status(StatusCode::IM_A_TEAPOT) - .body(Body::from("raw body response")) - .unwrap()) - } else { - Ok(()) - }?; - - Ok(()) - }; - - let err = fn1().unwrap_err(); - let mut resp = err.response(); - assert_eq!(resp.status(), StatusCode::IM_A_TEAPOT); - assert_eq!( - resp.body_mut().data().await.unwrap().unwrap(), - Bytes::from("raw body response") - ); -} diff --git a/backend/api/src/tests/api/mod.rs b/backend/api/src/tests/api/mod.rs deleted file mode 100644 index 3ae888b21..000000000 --- a/backend/api/src/tests/api/mod.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::time::Duration; - -use common::prelude::FutureTimeout; -use hyper::StatusCode; - -use crate::{ - api::run, - config::{ApiConfig, AppConfig}, - tests::global::mock_global_state, -}; - -mod errors; -mod v1; - -#[tokio::test] -async fn test_api_v6() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - api: ApiConfig { - bind_address: format!("[::]:{}", port).parse().unwrap(), - tls: None, - }, - ..Default::default() - }) - .await; - - let handle = tokio::spawn(run(global)); - - // We need to wait for the server to start - tokio::time::sleep(Duration::from_millis(300)).await; - - let client = reqwest::Client::new(); - let resp = client - .get(format!("http://localhost:{}/v1/health", port)) - .send() - .await - .expect("failed to get health"); - - assert_eq!(resp.status(), StatusCode::OK); - let body = resp.text().await.expect("failed to read body"); - assert_eq!(body, "{\"status\":\"ok\"}"); - - // The client uses Keep-Alive, so we need to drop it to release the global context - drop(client); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel api") - .expect("api failed") - .expect("api failed"); -} - -#[tokio::test] -async fn test_api_v4() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let (global, handler) = mock_global_state(AppConfig { - api: ApiConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - tls: None, - }, - ..Default::default() - }) - .await; - - let handle = tokio::spawn(run(global)); - - // We need to wait for the server to start - tokio::time::sleep(Duration::from_millis(300)).await; - - let client = reqwest::Client::new(); - let resp = client - .get(format!("http://localhost:{}/v1/health", port)) - .send() - .await - .expect("failed to get health"); - - assert_eq!(resp.status(), StatusCode::OK); - let body = resp.text().await.expect("failed to read body"); - assert_eq!(body, "{\"status\":\"ok\"}"); - - // The client uses Keep-Alive, so we need to drop it to release the global context - drop(client); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel api") - .expect("api failed") - .expect("api failed"); -} diff --git a/backend/api/src/tests/api/v1/gql/auth.rs b/backend/api/src/tests/api/v1/gql/auth.rs deleted file mode 100644 index 311fca88d..000000000 --- a/backend/api/src/tests/api/v1/gql/auth.rs +++ /dev/null @@ -1,724 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use crate::database::{session, user}; -use async_graphql::{Name, Request, Variables}; -use chrono::Utc; -use common::prelude::FutureTimeout; -use serde_json::json; -use serial_test::serial; - -use crate::{ - api::v1::{ - gql::{ext::RequestExt, request_context::RequestContext, schema}, - jwt::JwtState, - }, - config::{AppConfig, TurnstileConfig}, - tests::global::{mock_global_state, turnstile::mock_turnstile}, -}; - -#[serial] -#[tokio::test] -async fn test_serial_login() { - let (mut rx, addr, h1) = mock_turnstile().await; - let (global, handler) = mock_global_state(AppConfig { - turnstile: TurnstileConfig { - url: addr, - secret_key: "DUMMY_KEY__DEADBEEF".to_string(), - }, - ..Default::default() - }) - .await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - let query = r#" - mutation { - auth { - login(username: "admin", password: "admin", captchaToken: "1234") { - token - } - } - } - "#; - - let h2 = tokio::spawn(async move { - let (req, resp) = rx.recv().await.unwrap(); - assert_eq!(req.response, "1234"); - assert_eq!(req.secret, "DUMMY_KEY__DEADBEEF"); - - resp.send(true).unwrap(); - }); - - let ctx = Arc::new(RequestContext::new(false)); - let res = schema - .execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ) - .timeout(Duration::from_secs(5)) - .await - .unwrap(); - - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - let session = sqlx::query_as!(session::Model, "SELECT * FROM sessions") - .fetch_one(&*global.db) - .timeout(Duration::from_secs(5)) - .await - .unwrap() - .unwrap(); - - let jwt_state = JwtState::from(session); - - let token = jwt_state - .serialize(&global) - .expect("failed to serialize jwt state"); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "login": { "token": token }} }) - ); - - h1.abort(); - - h1.timeout(Duration::from_secs(1)).await.unwrap().ok(); // ignore error because we aborted it - h2.timeout(Duration::from_secs(1)).await.unwrap().unwrap(); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_login_while_logged_in() { - let (mut rx, addr, h1) = mock_turnstile().await; - let (global, handler) = mock_global_state(AppConfig { - turnstile: TurnstileConfig { - url: addr, - secret_key: "batman's chest".to_string(), - }, - ..Default::default() - }) - .await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - sqlx::query!( - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4)", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .execute(&*global.db) - .await - .unwrap(); - - let schema = schema(); - let query = r#" - mutation { - auth { - login(username: "admin", password: "admin", captchaToken: "1234") { - token - } - } - } - "#; - - let ctx = Arc::new(RequestContext::new(true)); - ctx.set_session(Some(Default::default())); - - let h2 = tokio::spawn(async move { - let (req, resp) = rx.recv().await.unwrap(); - assert_eq!(req.response, "1234"); - assert_eq!(req.secret, "batman's chest"); - - resp.send(true).unwrap(); - }); - - let res = schema - .execute( - Request::from(query) - .provide_context(ctx) - .provide_global(global.clone()), - ) - .timeout(Duration::from_secs(2)) - .await - .unwrap(); - - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - let session = sqlx::query_as!(session::Model, "SELECT * FROM sessions") - .fetch_one(&*global.db) - .timeout(Duration::from_secs(1)) - .await - .unwrap() - .unwrap(); - - let jwt_state = JwtState::from(session); - - let token = jwt_state - .serialize(&global) - .expect("failed to serialize jwt state"); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "login": { "token": token }} }) - ); - - h1.abort(); - - h1.timeout(Duration::from_secs(1)).await.unwrap().ok(); // ignore error because we aborted it - - h2.timeout(Duration::from_secs(1)).await.unwrap().unwrap(); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_login_with_token() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = sqlx::query!( - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key() - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() + chrono::Duration::seconds(60) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - let token = JwtState::from(session).serialize(&global).unwrap(); - - let schema = schema(); - let query = r#" - mutation($token: String!) { - auth { - loginWithToken(sessionToken: $token) { - token - } - } - } - "#; - - let mut variables = Variables::default(); - variables.insert( - Name::new("token"), - async_graphql::Value::String(token.clone()), - ); - - let ctx = Arc::new(RequestContext::new(false)); - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .variables(variables) - .provide_context(ctx.clone()) - .provide_global(global.clone()), - ), - ) - .await - .unwrap(); - - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "loginWithToken": { "token": token }} }) - ); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_login_with_session_expired() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = sqlx::query!( - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key() - ) - .fetch_one(&*global.db) - .await - .unwrap(); - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() - chrono::Duration::seconds(60) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - let query = r#" - mutation Login($token: String!) { - auth { - loginWithToken(sessionToken: $token) { - token - } - } - } - "#; - - let jwt_state = JwtState::from(session); - - let mut variables = Variables::default(); - variables.insert( - Name::new("token"), - async_graphql::Value::String(jwt_state.serialize(&global).unwrap()), - ); - - let ctx = Arc::new(RequestContext::new(false)); - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx.clone()), - ), - ) - .await - .unwrap(); - - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!(json.unwrap(), serde_json::json!(null)); - - let errors = res - .errors - .into_iter() - .map(|e| { - e.extensions - .unwrap() - .get("reason") - .unwrap() - .clone() - .into_json() - .unwrap() - }) - .collect::>(); - assert_eq!(errors, vec![json!("Invalid session token")]); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_login_while_logged_in_with_session_expired() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key() - ) - .fetch_one(&*global.db) - .await - .unwrap(); - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() - chrono::Duration::seconds(60) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session2 = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() + chrono::Duration::seconds(60) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - let query = r#" - mutation Login($token: String!) { - auth { - loginWithToken(sessionToken: $token) { - token - } - } - } - "#; - - let jwt_state = JwtState::from(session2); - - let mut variables = Variables::default(); - variables.insert( - Name::new("token"), - async_graphql::Value::String(jwt_state.serialize(&global).unwrap()), - ); - - let ctx = Arc::new(RequestContext::new(true)); - ctx.set_session(Some((session, Default::default()))); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx.clone()), - ), - ) - .await - .unwrap(); - - println!("{:?}", res.errors); - - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "loginWithToken": { "token": jwt_state.serialize(&global).unwrap() }}}) - ); - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_register() { - let (mut rx, addr, h1) = mock_turnstile().await; - let (global, handler) = mock_global_state(AppConfig { - turnstile: TurnstileConfig { - url: addr, - secret_key: "DUMMY_KEY__LOREM_IPSUM".to_string(), - }, - ..Default::default() - }) - .await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - - let schema = schema(); - let query = r#" - mutation { - auth { - register(username: "admin", password: "SuperStr0ngP@ssword!", email: "admin@admin.com", captchaToken: "1234") { - token - } - } - } - "#; - - let h2 = tokio::spawn(async move { - let (req, resp) = rx.recv().await.unwrap(); - assert_eq!(req.response, "1234"); - assert_eq!(req.secret, "DUMMY_KEY__LOREM_IPSUM"); - - resp.send(true).unwrap(); - }); - - let ctx = Arc::new(RequestContext::new(false)); - let res = tokio::time::timeout( - Duration::from_secs(2), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - let user = tokio::time::timeout( - Duration::from_secs(1), - sqlx::query_as!(user::Model, "SELECT * FROM users").fetch_one(&*global.db), - ) - .await - .unwrap() - .unwrap(); - - assert_eq!(user.username, "admin"); - assert_eq!(user.email, "admin@admin.com"); - assert!(user.verify_password("SuperStr0ngP@ssword!")); - - let session = tokio::time::timeout( - Duration::from_secs(1), - sqlx::query_as!(session::Model, "SELECT * FROM sessions").fetch_one(&*global.db), - ) - .await - .unwrap() - .unwrap(); - let token = JwtState::from(session).serialize(&global).unwrap(); - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "register": { "token": token }} }) - ); - - h1.abort(); - - h1.timeout(Duration::from_secs(1)).await.unwrap().ok(); // ignore error because we aborted it - h2.timeout(Duration::from_secs(1)).await.unwrap().unwrap(); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_logout() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() + chrono::Duration::seconds(60) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - let query = r#" - mutation { - auth { - logout - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - ctx.set_session(Some((session, Default::default()))); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "logout": true }}) - ); - - let session = tokio::time::timeout( - Duration::from_secs(1), - sqlx::query_as!(session::Model, "SELECT * FROM sessions").fetch_one(&*global.db), - ) - .await - .unwrap() - .unwrap(); - assert!(!session.is_valid()); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_logout_with_token() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key() - ) - .fetch_one(&*global.db) - .await - .unwrap(); - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() + chrono::Duration::seconds(60) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - let token = JwtState::from(session.clone()).serialize(&global).unwrap(); - - let schema = schema(); - let query = r#" - mutation($token: String!) { - auth { - logout(sessionToken: $token) - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - let mut variables = Variables::default(); - variables.insert(Name::new("token"), async_graphql::Value::String(token)); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx.clone()), - ), - ) - .await - .unwrap(); - - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "logout": true }}) - ); - - let session = tokio::time::timeout( - Duration::from_secs(1), - sqlx::query_as!(session::Model, "SELECT * FROM sessions").fetch_one(&*global.db), - ) - .await - .unwrap() - .unwrap(); - assert!(!session.is_valid()); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} diff --git a/backend/api/src/tests/api/v1/gql/chat.rs b/backend/api/src/tests/api/v1/gql/chat.rs deleted file mode 100644 index 9a128711f..000000000 --- a/backend/api/src/tests/api/v1/gql/chat.rs +++ /dev/null @@ -1,358 +0,0 @@ -use crate::{ - api::v1::gql::ext::RequestExt, - database::{chat_message, session, user}, - pb, -}; -use async_graphql::{Name, Request, Variables}; -use chrono::Utc; -use common::prelude::FutureTimeout; -use prost::Message; -use serial_test::serial; -use std::sync::Arc; -use uuid::Uuid; - -use crate::{ - api::v1::gql::{request_context::RequestContext, schema}, - tests::global::mock_global_state, -}; - -#[tokio::test] -#[serial] -async fn test_serial_send_message_not_logged_in() { - let (global, _handler) = mock_global_state(Default::default()).await; - let schema = schema(); - - let query = r#" - mutation SendChatMessage($channelId: UUID!, $content: String!) { - chat { - sendMessage(channelId: $channelId, content: $content) { - id - } - } - } - "#; - - let ctx = Arc::new(RequestContext::default()); - - let mut variables = Variables::default(); - variables.insert( - Name::new("channelId"), - async_graphql::Value::String(Uuid::new_v4().to_string()), - ); - - variables.insert( - Name::new("content"), - async_graphql::Value::String("message".to_owned()), - ); - - let res = schema - .execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx), - ) - .await; - - println!("{:?}", res); - - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - assert_eq!(json.unwrap(), serde_json::json!(null)); - assert_eq!( - res.errors[0].message, - "Unauthorized: You need to be logged in" - ); -} - -#[tokio::test] -#[serial] -async fn test_serial_send_message_chat_not_found() { - let (global, _handler) = mock_global_state(Default::default()).await; - let schema = schema(); - let query = r#" - mutation SendChatMessage($channelId: UUID!, $content: String!) { - chat { - sendMessage(channelId: $channelId, content: $content) { - id - } - } - } - "#; - - sqlx::query!("DELETE FROM sessions") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() + chrono::Duration::seconds(120) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let ctx = Arc::new(RequestContext::new(false)); - ctx.set_session(Some((session, Default::default()))); - - let mut variables = Variables::default(); - variables.insert( - Name::new("channelId"), - async_graphql::Value::String(Uuid::new_v4().to_string()), - ); - - variables.insert( - Name::new("content"), - async_graphql::Value::String("message".to_owned()), - ); - - let res = schema - .execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx), - ) - .await; - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - assert_eq!(res.errors[0].message, "InvalidInput: Channel not found"); -} - -#[tokio::test] -#[serial] -async fn test_serial_send_message_success() { - let (global, _handler) = mock_global_state(Default::default()).await; - let schema = schema(); - let query = r#" - mutation SendChatMessage($channelId: UUID!, $content: String!) { - chat { - sendMessage(channelId: $channelId, content: $content) { - id - content - author { - username - } - channel { - username - } - } - } - } - "#; - - sqlx::query!("DELETE FROM chat_messages") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM sessions") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let channel = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "based", - "based@based.com", - user::hash_password("based"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() + chrono::Duration::seconds(120) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let ctx = Arc::new(RequestContext::new(true)); - ctx.set_session(Some((session, Default::default()))); - - let mut variables = Variables::default(); - variables.insert( - Name::new("channelId"), - async_graphql::Value::String(channel.id.to_string()), - ); - - variables.insert( - Name::new("content"), - async_graphql::Value::String("message".to_string()), - ); - - let mut subs = global - .subscription_manager - .subscribe(format!("user:{}:chat:messages", channel.id)) - .timeout(std::time::Duration::from_secs(1)) - .await - .unwrap() - .unwrap(); - - let res = schema - .execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx), - ) - .await; - assert_eq!(res.errors.len(), 0); - let json = res.data.to_owned().into_json(); - assert!(json.is_ok()); - - let json = json.unwrap(); - - assert_eq!(json["chat"]["sendMessage"]["author"]["username"], "test"); - - assert_eq!(json["chat"]["sendMessage"]["channel"]["username"], "based"); - - assert_eq!(json["chat"]["sendMessage"]["content"], "message"); - - let message = subs - .recv() - .timeout(std::time::Duration::from_secs(1)) - .await - .unwrap() - .unwrap(); - let message = pb::scuffle::events::ChatMessage::decode(message.as_bytes().unwrap()).unwrap(); - - assert_eq!(message.author_id, user.id.to_string()); - assert_eq!(message.channel_id, channel.id.to_string()); - assert_eq!(message.content, "message"); - assert_eq!( - message.id, - json["chat"]["sendMessage"]["id"].as_str().unwrap() - ); - - // Check if added to db - let db_message = sqlx::query_as!( - chat_message::Model, - "SELECT * FROM chat_messages WHERE id = $1", - Uuid::parse_str(json["chat"]["sendMessage"]["id"].as_str().unwrap()).unwrap() - ) - .fetch_one(&*global.db) - .await; - - assert!(db_message.is_ok()); - let db_message = db_message.unwrap(); - assert_eq!(db_message.author_id, user.id); - assert_eq!(db_message.channel_id, channel.id); - assert_eq!(db_message.content, "message"); -} - -#[tokio::test] -#[serial] -async fn test_serial_send_message_too_long() { - let (global, _handler) = mock_global_state(Default::default()).await; - let schema = schema(); - let query = r#" - mutation SendMessage($channelId: UUID!, $content: String!) { - chat { - sendMessage(channelId: $channelId, content: $content) { - id - } - } - } - "#; - - sqlx::query!("DELETE FROM sessions") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - Utc::now() + chrono::Duration::seconds(120) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let ctx = Arc::new(RequestContext::new(true)); - ctx.set_session(Some((session, Default::default()))); - - let mut variables = Variables::default(); - variables.insert( - Name::new("channelId"), - async_graphql::Value::String(Uuid::new_v4().to_string()), - ); - variables.insert( - Name::new("content"), - async_graphql::Value::String("a".repeat(501)), - ); - - let res = schema - .execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx), - ) - .await; - - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - assert_eq!(res.errors[0].message, "InvalidInput: Message too long"); -} diff --git a/backend/api/src/tests/api/v1/gql/errors.rs b/backend/api/src/tests/api/v1/gql/errors.rs deleted file mode 100644 index 8aa508467..000000000 --- a/backend/api/src/tests/api/v1/gql/errors.rs +++ /dev/null @@ -1,33 +0,0 @@ -use async_graphql::{ErrorExtensions, Value}; - -use crate::api::v1::gql::error::{GqlError, Result, ResultExt}; - -#[test] -fn test_error_from_residual_error() { - let fn1 = || -> Result<()> { - if true { - Err(anyhow::anyhow!("error from fn1")) - } else { - Ok(()) - } - .map_err_gql("error somewhere")?; - Ok(()) - }; - let err = fn1().unwrap_err(); - let err = err.extend(); - assert_eq!( - err.message, - format!("{}: error somewhere", GqlError::InternalServerError) - ); - assert!(err.source.is_none()); - assert!(err.extensions.is_some()); - let extensions = err.extensions.unwrap(); - assert_eq!( - extensions.get("kind"), - Some(&Value::from(format!("{}", GqlError::InternalServerError))) - ); - assert_eq!( - extensions.get("reason"), - Some(&Value::from("error somewhere".to_string())) - ); -} diff --git a/backend/api/src/tests/api/v1/gql/mod.rs b/backend/api/src/tests/api/v1/gql/mod.rs deleted file mode 100644 index e5427e8fa..000000000 --- a/backend/api/src/tests/api/v1/gql/mod.rs +++ /dev/null @@ -1,246 +0,0 @@ -use async_graphql::http::WebSocketProtocols; -use common::prelude::FutureTimeout; -use futures_util::{SinkExt, StreamExt}; -use http::HeaderValue; -use hyper_tungstenite::tungstenite::client::IntoClientRequest; -use serde_json::json; -use std::time::Duration; - -use crate::{ - api, - api::v1::gql::{schema, PLAYGROUND_HTML}, - config::{ApiConfig, AppConfig}, - tests::global::mock_global_state, -}; - -mod auth; -mod chat; -mod errors; -mod models; -mod subscription; - -#[tokio::test] -async fn test_query_noop() { - let schema = schema(); - let query = r#" - query { - noop - } - "#; - let res = schema.execute(query).await; - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - assert_eq!(json.unwrap(), serde_json::json!({ "noop": false })); -} - -#[tokio::test] -async fn test_subscription_noop() { - let schema = schema(); - let query = r#" - subscription { - noop - } - "#; - let mut res = schema.execute_stream(query); - let resp = res.next().await; - assert!(resp.is_none()); -} - -#[tokio::test] -async fn test_query_noop_via_http() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let (global, handler) = mock_global_state(AppConfig { - api: ApiConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - tls: None, - }, - ..Default::default() - }) - .await; - - let h = tokio::spawn(api::run(global)); - - tokio::time::sleep(Duration::from_millis(300)).await; - - let client = reqwest::Client::new(); - let res = client - .post(format!("http://localhost:{}/v1/gql", port)) - .json(&serde_json::json!({ - "query": "query { noop }", - })) - .send() - .await - .unwrap(); - - assert_eq!(res.status(), 200); - let json: serde_json::Value = res.json().await.unwrap(); - assert_eq!( - json.get("data"), - Some(&serde_json::json!({ "noop": false } )) - ); - - let res = client - .get(format!("http://localhost:{}/v1/gql", port)) - .query(&[("query", "query { noop }")]) - .send() - .await - .unwrap(); - - assert_eq!(res.status(), 200); - let json: serde_json::Value = res.json().await.unwrap(); - assert_eq!( - json.get("data"), - Some(&serde_json::json!({ "noop": false } )) - ); - - drop(client); - - // Connect via websocket - let mut req = format!("ws://localhost:{}/v1/gql", port) - .into_client_request() - .unwrap(); - req.headers_mut().insert( - http::header::SEC_WEBSOCKET_PROTOCOL, - HeaderValue::from_static(WebSocketProtocols::GraphQLWS.sec_websocket_protocol()), - ); - - let (mut ws_stream, resp) = tokio_tungstenite::connect_async(req).await.unwrap(); - - assert_eq!(resp.status(), 101); - assert_eq!( - resp.headers().get("sec-websocket-protocol"), - Some(&HeaderValue::from_static( - WebSocketProtocols::GraphQLWS.sec_websocket_protocol() - )) - ); - - // Send a message - let msg = serde_json::json!({ - "type": "connection_init", - "payload": {} - }); - - ws_stream - .send(tokio_tungstenite::tungstenite::Message::Text( - serde_json::to_string(&msg).unwrap(), - )) - .await - .unwrap(); - - // Receive a message - let msg = serde_json::from_str::( - ws_stream - .next() - .await - .unwrap() - .unwrap() - .to_string() - .as_str(), - ) - .unwrap(); - assert_eq!( - msg, - json!({ - "type": "connection_ack", - }) - ); - - // Send a message - let msg = serde_json::json!({"id":"bc491f76-500b-41c2-b6c2-3dd1274f3baa","type":"subscribe","payload":{"query":"subscription {\n noop\n}"}}); - - ws_stream - .send(tokio_tungstenite::tungstenite::Message::Text( - serde_json::to_string(&msg).unwrap(), - )) - .await - .unwrap(); - - // Receive a message - let msg = serde_json::from_str::( - ws_stream - .next() - .await - .unwrap() - .unwrap() - .to_string() - .as_str(), - ) - .unwrap(); - assert_eq!( - msg, - json!({ - "id": "bc491f76-500b-41c2-b6c2-3dd1274f3baa", - "type": "complete", - }) - ); - - // Close the connection - ws_stream - .send(tokio_tungstenite::tungstenite::Message::Close(None)) - .await - .unwrap(); - - // Wait for the connection to close - ws_stream.next().await; - - // Wait for the server to shutdown - handler - .cancel() - .timeout(std::time::Duration::from_secs(1)) - .await - .unwrap(); - h.timeout(std::time::Duration::from_secs(1)) - .await - .unwrap() - .unwrap() - .unwrap(); -} - -#[tokio::test] -async fn test_playground() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let (global, handler) = mock_global_state(AppConfig { - api: ApiConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - tls: None, - }, - ..Default::default() - }) - .await; - - let h = tokio::spawn(api::run(global)); - - tokio::time::sleep(Duration::from_millis(300)).await; - - let client = reqwest::Client::new(); - let res = client - .get(format!("http://localhost:{}/v1/gql/playground", port)) - .send() - .await - .unwrap(); - - assert_eq!(res.status(), 200); - assert_eq!( - res.headers().get("content-type").unwrap().to_str().unwrap(), - "text/html" - ); - let text = res.text().await.unwrap(); - assert_eq!(text, PLAYGROUND_HTML); - - drop(client); - - // Wait for the server to shutdown - handler - .cancel() - .timeout(std::time::Duration::from_secs(1)) - .await - .unwrap(); - h.timeout(std::time::Duration::from_secs(1)) - .await - .unwrap() - .unwrap() - .unwrap(); -} diff --git a/backend/api/src/tests/api/v1/gql/models/date.rs b/backend/api/src/tests/api/v1/gql/models/date.rs deleted file mode 100644 index 95531f1f5..000000000 --- a/backend/api/src/tests/api/v1/gql/models/date.rs +++ /dev/null @@ -1,19 +0,0 @@ -use async_graphql::{ScalarType, Value}; -use chrono::{Timelike, Utc}; - -use crate::api::v1::gql::models::date::DateRFC3339; - -#[test] -fn test_date_scalar() { - let now = Utc::now(); - - let date = DateRFC3339::from(now); - - assert_eq!(ScalarType::to_value(&date), Value::from(now.to_rfc3339())); - - let date = DateRFC3339::parse(Value::from(now.to_rfc3339())).unwrap(); - assert_eq!(date.0, now); - - let date = DateRFC3339::parse(Value::from(now.timestamp())).unwrap(); - assert_eq!(date.0, now.with_nanosecond(0).unwrap()); // Nanoscends are lost in the conversion -} diff --git a/backend/api/src/tests/api/v1/gql/models/global_roles.rs b/backend/api/src/tests/api/v1/gql/models/global_roles.rs deleted file mode 100644 index f6e81ac26..000000000 --- a/backend/api/src/tests/api/v1/gql/models/global_roles.rs +++ /dev/null @@ -1,168 +0,0 @@ -use crate::api::v1::gql::{ext::RequestExt, request_context::RequestContext, schema}; -use crate::database::{global_role::Permission, user}; -use crate::tests::global::mock_global_state; -use async_graphql::{Name, Request, Value, Variables}; -use common::prelude::FutureTimeout; -use serial_test::serial; -use std::sync::Arc; -use std::time::Duration; - -#[serial] -#[tokio::test] -async fn test_serial_user_by_name() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM global_roles") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM global_role_grants") - .execute(&*global.db) - .await - .unwrap(); - - let user_id = sqlx::query!( - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING id", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let admin_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "admin", - "admin", - 1, - Permission::Admin.bits(), - 0, - chrono::Utc::now() - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let go_live_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "go_live", - "go_live", - 2, - Permission::GoLive.bits(), - 0, - chrono::Utc::now() - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants(user_id, global_role_id, created_at) VALUES ($1, $2, $3)", - user_id, - admin_role_id, - chrono::Utc::now() - ) - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants(user_id, global_role_id, created_at) VALUES ($1, $2, $3)", - user_id, - go_live_role_id, - chrono::Utc::now() - ) - .execute(&*global.db) - .await - .unwrap(); - - let schema = schema(); - - { - let query = r#" - query($id: UUID!) { - userById(id: $id) { - id - permissions - globalRoles { - id - name - description - rank - allowedPermissions - deniedPermissions - } - } - } - "#; - - let mut variables = Variables::default(); - variables.insert(Name::new("id"), Value::String(user_id.to_string())); - - let ctx = Arc::new(RequestContext::new(false)); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ - "userById": { - "id": user_id, - "permissions": 3, // admin and go live permissions - "globalRoles": [ - { - "id": admin_role_id, - "name": "admin", - "description": "admin", - "rank": 1, - "allowedPermissions": 1, // admin permission - "deniedPermissions": 0 - }, - { - "id": go_live_role_id, - "name": "go_live", - "description": "go_live", - "rank": 2, - "allowedPermissions": 2, // go live permission - "deniedPermissions": 0 - }, - ] - } - }) - ); - } - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} diff --git a/backend/api/src/tests/api/v1/gql/models/mod.rs b/backend/api/src/tests/api/v1/gql/models/mod.rs deleted file mode 100644 index 3d428b773..000000000 --- a/backend/api/src/tests/api/v1/gql/models/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod date; -mod global_roles; -mod session; -mod user; diff --git a/backend/api/src/tests/api/v1/gql/models/session.rs b/backend/api/src/tests/api/v1/gql/models/session.rs deleted file mode 100644 index 4de652cc0..000000000 --- a/backend/api/src/tests/api/v1/gql/models/session.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::api::v1::gql::{ext::RequestExt, request_context::RequestContext, schema}; -use crate::api::v1::jwt::JwtState; -use crate::database::{session, user}; -use crate::tests::global::mock_global_state; -use async_graphql::{Request, Variables}; -use common::prelude::FutureTimeout; -use serial_test::serial; -use std::sync::Arc; -use std::time::Duration; - -#[serial] -#[tokio::test] -async fn test_serial_session_user() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - chrono::Utc::now() + chrono::Duration::seconds(30) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - - { - let query = r#" - mutation TestLoginWithToken($token: String!) { - auth { - loginWithToken(sessionToken: $token) { - id - user { - id - username - email - } - } - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - let token = JwtState::from(session.clone()).serialize(&global).unwrap(); - - let variables = Variables::from_json(serde_json::json!({ "token": token })); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - println!("{:?}", res.errors); - - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "auth": { "loginWithToken": { "id": session.id, "user": { "id": user.id, "username": user.username, "email": user.email } } }}) - ); - } - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} diff --git a/backend/api/src/tests/api/v1/gql/models/user.rs b/backend/api/src/tests/api/v1/gql/models/user.rs deleted file mode 100644 index 8df44ae2a..000000000 --- a/backend/api/src/tests/api/v1/gql/models/user.rs +++ /dev/null @@ -1,307 +0,0 @@ -use crate::api::v1::gql::{ext::RequestExt, request_context::RequestContext, schema}; -use crate::database::{session, user}; -use crate::tests::global::mock_global_state; -use async_graphql::{Request, Value}; -use common::prelude::FutureTimeout; -use serial_test::serial; -use std::sync::Arc; -use std::time::Duration; - -#[serial] -#[tokio::test] -async fn test_serial_user_by_name() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - chrono::Utc::now() + chrono::Duration::seconds(30) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - - { - let query = r#" - query { - userByUsername(username: "admin") { - id - username - createdAt - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "userByUsername": { "id": user.id, "username": user.username, "createdAt": user.created_at.to_rfc3339() } }) - ); - } - { - let query = r#" - query { - userByUsername(username: "admin") { - id - email - username - createdAt - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert!(!res.is_ok()); - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!(json.unwrap(), serde_json::json!({ "userByUsername": null })); - - assert_eq!( - res.errors[0].message, - "Unauthorized: you are not allowed to see this field" - ); - - let extensions = res.errors[0].extensions.as_ref().unwrap(); - - assert_eq!(extensions.get("fields"), Some(&Value::from(vec!["email"]))); - - assert_eq!(extensions.get("kind"), Some(&Value::from("Unauthorized"))); - } - { - let query = r#" - query { - userByUsername(username: "admin") { - id - emailVerified - username - createdAt - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert!(!res.is_ok()); - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!(json.unwrap(), serde_json::json!({ "userByUsername": null })); - - assert_eq!( - res.errors[0].message, - "Unauthorized: you are not allowed to see this field" - ); - - let extensions = res.errors[0].extensions.as_ref().unwrap(); - - assert_eq!( - extensions.get("fields"), - Some(&Value::from(vec!["emailVerified"])) - ); - - assert_eq!(extensions.get("kind"), Some(&Value::from("Unauthorized"))); - } - { - let query = r#" - query { - userByUsername(username: "admin") { - id - lastLoginAt - username - createdAt - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert!(!res.is_ok()); - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!(json.unwrap(), serde_json::json!({ "userByUsername": null })); - - assert_eq!( - res.errors[0].message, - "Unauthorized: you are not allowed to see this field" - ); - - let extensions = res.errors[0].extensions.as_ref().unwrap(); - - assert_eq!( - extensions.get("fields"), - Some(&Value::from(vec!["lastLoginAt"])) - ); - - assert_eq!(extensions.get("kind"), Some(&Value::from("Unauthorized"))); - } - { - let query = r#" - query { - userByUsername(username: "admin") { - id - email - emailVerified - lastLoginAt - username - createdAt - streamKey - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - ctx.set_session(Some((session.clone(), Default::default()))); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "userByUsername": { "id": user.id, "email": user.email, "emailVerified": user.email_verified, "lastLoginAt": user.last_login_at.to_rfc3339(), "username": user.username, "createdAt": user.created_at.to_rfc3339(), "streamKey": format!("live_{}_{}", user.id.as_u128(), user.stream_key) } }) - ); - } - - sqlx::query!("DELETE FROM sessions WHERE id = $1", session.id) - .execute(&*global.db) - .await - .expect("failed to delete user"); - - { - let query = r#" - query { - userByUsername(username: "admin") { - id - lastLoginAt - username - createdAt - } - } - "#; - - let ctx = Arc::new(RequestContext::new(true)); - ctx.set_session(Some((session.clone(), Default::default()))); - - let res = tokio::time::timeout( - Duration::from_secs(1), - schema.execute( - Request::from(query) - .provide_global(global.clone()) - .provide_context(ctx), - ), - ) - .await - .unwrap(); - - assert!(!res.is_ok()); - assert_eq!(res.errors.len(), 1); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!(json.unwrap(), serde_json::json!({ "userByUsername": null })); - - assert_eq!( - res.errors[0].message, - "InvalidSession: Session is no longer valid" - ); - - let extensions = res.errors[0].extensions.as_ref().unwrap(); - - assert_eq!(extensions.get("kind"), Some(&Value::from("InvalidSession"))); - } - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} diff --git a/backend/api/src/tests/api/v1/gql/subscription.rs b/backend/api/src/tests/api/v1/gql/subscription.rs deleted file mode 100644 index 461c48842..000000000 --- a/backend/api/src/tests/api/v1/gql/subscription.rs +++ /dev/null @@ -1,393 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use crate::{ - api::v1::gql::{ext::RequestExt, request_context::RequestContext, schema}, - database::{session, user}, - pb, - tests::global::mock_global_state, -}; -use async_graphql::Value; -use async_graphql::{Name, Request, Variables}; -use common::prelude::FutureTimeout; -use fred::prelude::PubsubInterface; -use futures_util::StreamExt; -use prost::Message; -use serial_test::serial; - -#[serial] -#[tokio::test] -async fn test_serial_user_display_name_subscription() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - - { - let query = r#" - subscription userByDisplayNameSub($userId: UUID!) { - userDisplayName(userId: $userId) { - displayName - username - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - let mut variables = Variables::default(); - variables.insert(Name::new("userId"), Value::from(user.id.to_string())); - - let mut stream = schema.execute_stream( - Request::from(query) - .variables(variables) - .provide_global(global.clone()) - .provide_context(ctx), - ); - - let res = tokio::time::timeout(Duration::from_secs(1), stream.next()) - .await - .expect("failed to execute stream"); - assert!(res.is_some()); - - let res = res.unwrap(); - - println!("{:?}", res); - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "userDisplayName": { "displayName": "admin", "username": "admin" } }) - ); - - // The above is the initial event send. - // We now need to publish an event to the redis channel to trigger the subscription. - let count: i32 = global - .redis - .publish( - format!("user:{}:display_name", user.id), - pb::scuffle::events::UserDisplayName { - display_name: Some("Admin".to_string()), - username: None, - } - .encode_to_vec() - .as_slice(), - ) - .await - .expect("failed to publish to redis"); - - assert_eq!(count, 1); - - let res = tokio::time::timeout(Duration::from_secs(1), stream.next()) - .await - .expect("failed to execute stream"); - - assert!(res.is_some()); - - let res = res.unwrap(); - - println!("{:?}", res); - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ "userDisplayName": { "displayName": "Admin", "username": "admin" } }) - ); - } - - tokio::time::sleep(Duration::from_millis(100)).await; - - // Try publish to redis to see if we are still subscribed. - let count: i32 = global - .redis - .publish( - format!("user:{}:display_name", user.id), - pb::scuffle::events::UserDisplayName { - display_name: Some("Admin".to_string()), - username: None, - } - .encode_to_vec() - .as_slice(), - ) - .await - .expect("failed to publish to redis"); - assert_eq!(count, 0); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} - -#[serial] -#[tokio::test] -async fn test_serial_chat_subscribe() { - let (global, handler) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - chrono::Utc::now() + chrono::Duration::days(1), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let schema = schema(); - - { - let query = r#" - subscription ChatWatch($channelId: UUID!) { - chatMessages(channelId: $channelId) { - id - author { - id - username - displayName - } - channel { - id - username - displayName - } - content - type - } - } - "#; - - let ctx = Arc::new(RequestContext::new(false)); - - ctx.set_session(Some((session, Default::default()))); - - let mut variables = Variables::default(); - variables.insert(Name::new("channelId"), Value::from(user.id.to_string())); - - let mut stream = schema.execute_stream( - Request::from(query) - .variables(variables.clone()) - .provide_global(global.clone()) - .provide_context(ctx.clone()), - ); - - let res = tokio::time::timeout(Duration::from_secs(1), stream.next()) - .await - .expect("failed to execute stream"); - assert!(res.is_some()); - - let res = res.unwrap(); - - println!("{:?}", res); - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ - "chatMessages": { - "id": "00000000-0000-0000-0000-000000000000", - "author": serde_json::Value::Null, - "channel": { - "id": user.id.to_string(), - "username": "admin", - "displayName": "admin" - }, - "content": "Welcome to the chat!", - "type": "WELCOME" - } - }) - ); - - // The above is the initial event send. - // We now need to publish an event to the redis channel to trigger the subscription. - let count: i32 = global - .redis - .publish( - format!("user:{}:chat:messages", user.id), - pb::scuffle::events::ChatMessage { - author_id: user.id.to_string(), - channel_id: user.id.to_string(), - content: "Hello world!".to_string(), - id: "00000000-0000-0000-0000-000000000001".to_string(), - created_at: chrono::Utc::now().timestamp(), - } - .encode_to_vec() - .as_slice(), - ) - .await - .expect("failed to publish to redis"); - - assert_eq!(count, 1); - - let res = tokio::time::timeout(Duration::from_secs(1), stream.next()) - .await - .expect("failed to execute stream"); - - assert!(res.is_some()); - - let res = res.unwrap(); - - println!("{:?}", res); - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ - "chatMessages": { - "id": "00000000-0000-0000-0000-000000000001", - "author": { - "id": user.id.to_string(), - "username": "admin", - "displayName": "admin" - }, - "channel": { - "id": user.id.to_string(), - "username": "admin", - "displayName": "admin" - }, - "content": "Hello world!", - "type": "USER" - } - }) - ); - - let query = r#" - mutation SendMessage($channelId: UUID!) { - chat { - sendMessage(channelId: $channelId, content: "Hello world!2") { - id - } - } - } - "#; - - let resp = schema - .execute( - Request::from(query) - .variables(variables.clone()) - .provide_global(global.clone()) - .provide_context(ctx.clone()), - ) - .await; - - println!("{:?}", resp); - - assert!(resp.is_ok()); - assert_eq!(resp.errors.len(), 0); - - let json = resp.data.into_json(); - assert!(json.is_ok()); - - let json = json.unwrap(); - let id = json["chat"]["sendMessage"]["id"].as_str().unwrap(); - - let res = tokio::time::timeout(Duration::from_secs(1), stream.next()) - .await - .expect("failed to execute stream"); - - assert!(res.is_some()); - - let res = res.unwrap(); - - println!("{:?}", res); - - assert!(res.is_ok()); - assert_eq!(res.errors.len(), 0); - let json = res.data.into_json(); - assert!(json.is_ok()); - - assert_eq!( - json.unwrap(), - serde_json::json!({ - "chatMessages": { - "id": id, - "author": { - "id": user.id.to_string(), - "username": "admin", - "displayName": "admin" - }, - "channel": { - "id": user.id.to_string(), - "username": "admin", - "displayName": "admin" - }, - "content": "Hello world!2", - "type": "USER" - } - }) - ); - } - - tokio::time::sleep(Duration::from_millis(100)).await; - - // Try publish to redis to see if we are still subscribed. - let count: i32 = global - .redis - .publish( - format!("user:{}:chat:messages", user.id), - pb::scuffle::events::ChatMessage { - author_id: user.id.to_string(), - channel_id: user.id.to_string(), - content: "Hello world!".to_string(), - id: "00000000-0000-0000-0000-000000000002".to_string(), - created_at: chrono::Utc::now().timestamp(), - } - .encode_to_vec() - .as_slice(), - ) - .await - .expect("failed to publish to redis"); - assert_eq!(count, 0); - - drop(global); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); -} diff --git a/backend/api/src/tests/api/v1/middleware/auth.rs b/backend/api/src/tests/api/v1/middleware/auth.rs deleted file mode 100644 index a169e23d9..000000000 --- a/backend/api/src/tests/api/v1/middleware/auth.rs +++ /dev/null @@ -1,203 +0,0 @@ -use crate::database::{session, user}; -use chrono::{Duration, Utc}; -use common::prelude::FutureTimeout; -use core::time; -use http::header; -use serde_json::{json, Value}; -use serial_test::serial; - -use crate::{ - api::{self, v1::jwt::JwtState}, - config::{ApiConfig, AppConfig}, - tests::global::mock_global_state, -}; - -#[serial] -#[tokio::test] - -async fn test_serial_auth_middleware() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - api: ApiConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - tls: None, - }, - ..Default::default() - }) - .await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .expect("failed to clear users"); - let id = sqlx::query!( - "INSERT INTO users (username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING id", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .expect("failed to insert user"); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions (user_id, expires_at) VALUES ($1, $2) RETURNING *", - id, - Utc::now() + Duration::seconds(30) - ) - .fetch_one(&*global.db) - .await - .expect("failed to insert session"); - - let session_id = session.id; - - let token = JwtState::from(session) - .serialize(&global) - .expect("failed to create token"); - - let handle = tokio::spawn(api::run(global.clone())); - - // We need to wait for the server to start - tokio::time::sleep(time::Duration::from_millis(300)).await; - - let client = reqwest::Client::new(); - let resp = client - .get(format!("http://localhost:{}/v1/health", port)) - .header(header::AUTHORIZATION, format!("Bearer {}", token)) - .send() - .await - .expect("failed to get health"); - - assert_eq!(resp.status(), http::StatusCode::OK); - let body: Value = resp.json().await.expect("failed to read body"); - assert_eq!(body, json!({"status": "ok"})); - - sqlx::query!( - "UPDATE sessions SET invalidated_at = NOW() WHERE id = $1", - session_id - ) - .execute(&*global.db) - .await - .expect("failed to update session"); - - let resp = client - .get(format!("http://localhost:{}/v1/health", port)) - .header(header::AUTHORIZATION, format!("Bearer {}", token)) - .send() - .await - .expect("failed to get health"); - - assert_eq!(resp.status(), http::StatusCode::OK); - assert_eq!( - resp.headers() - .get("X-Auth-Token-Check-Status") - .and_then(|s| s.to_str().ok()), - Some("failed") - ); - let body: Value = resp.json().await.expect("failed to read body"); - assert_eq!(body, json!({"status": "ok"})); - - // The client uses Keep-Alive, so we need to drop it to release the global context - drop(global); - drop(client); - - handler - .cancel() - .timeout(time::Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(time::Duration::from_secs(1)) - .await - .unwrap() - .unwrap() - .unwrap(); -} - -#[serial] -#[tokio::test] -async fn test_serial_auth_middleware_failed() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - api: ApiConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - tls: None, - }, - ..Default::default() - }) - .await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .expect("failed to clear users"); - let id = sqlx::query!( - "INSERT INTO users (username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING id", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .expect("failed to insert user"); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions (user_id, expires_at) VALUES ($1, $2) RETURNING *", - id, - Utc::now() - Duration::seconds(30) - ) - .fetch_one(&*global.db) - .await - .expect("failed to insert session"); - - let token = JwtState::from(session) - .serialize(&global) - .expect("failed to create token"); - - let handle = tokio::spawn(api::run(global)); - - // We need to wait for the server to start - tokio::time::sleep(time::Duration::from_millis(300)).await; - - let client = reqwest::Client::new(); - let resp = client - .get(format!("http://localhost:{}/v1/health", port)) - .header(header::AUTHORIZATION, format!("Bearer {}", token)) - .header("X-Auth-Token-Check", "always") - .send() - .await - .expect("failed to get health"); - - assert_eq!(resp.status(), http::StatusCode::UNAUTHORIZED); - let body: Value = resp.json().await.expect("failed to read body"); - assert_eq!( - body, - json!({ - "message": "unauthorized", - "success": false, - }) - ); - - // The client uses Keep-Alive, so we need to drop it to release the global context - drop(client); - - handler - .cancel() - .timeout(time::Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(time::Duration::from_secs(1)) - .await - .unwrap() - .unwrap() - .unwrap(); -} diff --git a/backend/api/src/tests/api/v1/middleware/mod.rs b/backend/api/src/tests/api/v1/middleware/mod.rs deleted file mode 100644 index 12bc9de49..000000000 --- a/backend/api/src/tests/api/v1/middleware/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod auth; diff --git a/backend/api/src/tests/api/v1/mod.rs b/backend/api/src/tests/api/v1/mod.rs deleted file mode 100644 index 47539a02b..000000000 --- a/backend/api/src/tests/api/v1/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod gql; -mod middleware; diff --git a/backend/api/src/tests/config.rs b/backend/api/src/tests/config.rs deleted file mode 100644 index 74680da8a..000000000 --- a/backend/api/src/tests/config.rs +++ /dev/null @@ -1,142 +0,0 @@ -use serial_test::serial; - -use crate::config::AppConfig; - -fn clear_env() { - for (key, _) in std::env::vars() { - if key.starts_with("SCUF_") { - std::env::remove_var(key); - } - } -} - -#[serial] -#[test] -fn test_parse() { - clear_env(); - - let config = AppConfig::parse().expect("Failed to parse config"); - assert_eq!( - config, - AppConfig { - config_file: None, - ..Default::default() - } - ); -} - -#[serial] -#[test] -fn test_parse_env() { - clear_env(); - - std::env::set_var("SCUF_LOGGING_LEVEL", "api=debug"); - std::env::set_var("SCUF_API_BIND_ADDRESS", "[::]:8081"); - std::env::set_var( - "SCUF_DATABASE_URI", - "postgres://postgres:postgres@localhost:5433/postgres", - ); - - let config = AppConfig::parse().expect("Failed to parse config"); - assert_eq!(config.logging.level, "api=debug"); - assert_eq!(config.api.bind_address, "[::]:8081".parse().unwrap()); - assert_eq!( - config.database.uri, - "postgres://postgres:postgres@localhost:5433/postgres" - ); -} - -#[serial] -#[test] -fn test_parse_file() { - clear_env(); - - let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir"); - let config_file = tmp_dir.path().join("config.toml"); - - std::fs::write( - &config_file, - r#" -[logging] -level = "api=debug" - -[api] -bind_address = "[::]:8081" - -[database] -uri = "postgres://postgres:postgres@localhost:5433/postgres" -"#, - ) - .expect("Failed to write config file"); - - std::env::set_var( - "SCUF_CONFIG_FILE", - config_file.to_str().expect("Failed to get str"), - ); - - let config = AppConfig::parse().expect("Failed to parse config"); - - assert_eq!(config.logging.level, "api=debug"); - assert_eq!(config.api.bind_address, "[::]:8081".parse().unwrap()); - assert_eq!( - config.database.uri, - "postgres://postgres:postgres@localhost:5433/postgres" - ); - assert_eq!( - config.config_file, - Some( - std::fs::canonicalize(config_file) - .unwrap() - .display() - .to_string() - ) - ); -} - -#[serial] -#[test] -fn test_parse_file_env() { - clear_env(); - - let tmp_dir = tempfile::tempdir().expect("Failed to create temp dir"); - let config_file = tmp_dir.path().join("config.toml"); - - std::fs::write( - &config_file, - r#" -[logging] -level = "api=debug" - -[api] -bind_address = "[::]:8081" - -[database] -uri = "postgres://postgres:postgres@localhost:5433/postgres" -"#, - ) - .expect("Failed to write config file"); - - std::env::set_var( - "SCUF_CONFIG_FILE", - config_file.to_str().expect("Failed to get str"), - ); - std::env::set_var("SCUF_LOGGING_LEVEL", "api=info"); - - let config = AppConfig::parse().expect("Failed to parse config"); - - assert_eq!(config.logging.level, "api=info"); - assert_eq!(config.api.bind_address, "[::]:8081".parse().unwrap()); - assert_eq!( - config.database.uri, - "postgres://postgres:postgres@localhost:5433/postgres" - ); - assert_eq!( - config.config_file, - Some( - std::fs::canonicalize(config_file) - .unwrap() - .display() - .to_string() - ) - ); -} diff --git a/backend/api/src/tests/database/global_role.rs b/backend/api/src/tests/database/global_role.rs deleted file mode 100644 index 36a0459f7..000000000 --- a/backend/api/src/tests/database/global_role.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::database::global_role::Permission; - -#[test] -fn test_has_permission_admin() { - let p = Permission::Admin | Permission::GoLive; - - // Admin has all permissions - assert!(p.has_permission( - Permission::Admin - | Permission::GoLive - | Permission::StreamRecording - | Permission::StreamTranscoding - )); -} - -#[test] -fn test_has_permission_go_live() { - let p = Permission::GoLive; - - // GoLive has GoLive permission - assert!(p.has_permission(Permission::GoLive)); - - // GoLive does not have Admin permission - assert!(!p.has_permission(Permission::Admin)); -} - -#[test] -fn test_has_permission_default() { - let p = Permission::default(); - - // default has no permissions - assert!(p.is_none()); -} diff --git a/backend/api/src/tests/database/mod.rs b/backend/api/src/tests/database/mod.rs deleted file mode 100644 index 64697ce60..000000000 --- a/backend/api/src/tests/database/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod global_role; -mod user; diff --git a/backend/api/src/tests/database/user.rs b/backend/api/src/tests/database/user.rs deleted file mode 100644 index 522615eff..000000000 --- a/backend/api/src/tests/database/user.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::database::user; - -#[test] -fn test_verify_password() { - let password = "mypassword"; - let hash = - "$argon2id$v=19$m=16,t=2,p=1$MTJ0bmNqYzhDaXZsS1BkZw$/PWmjCzvNhg1aeUVaV8Z9w".to_string(); - - assert!(user::Model { - password_hash: hash, - ..Default::default() - } - .verify_password(password)); -} - -#[test] -fn test_hash_password() { - let password = "mypassword"; - let hash = user::hash_password(password); - - assert!(user::Model { - password_hash: hash, - ..Default::default() - } - .verify_password(password)); -} - -#[test] -fn test_validate_usernames() { - let tests = vec![ - ("test", Ok(())), - ("test_", Ok(())), - ("test123", Ok(())), - ("test123_", Ok(())), - ( - "&garbage", - Err("Username must only contain alphanumeric characters and underscores"), - ), - ("te", Err("Username must be at least 3 characters long")), - ("tes", Ok(())), - ( - "1111111111111111111111111111111111111111111111111111111111111", - Err("Username must be at most 20 characters long"), - ), - ( - "test!", - Err("Username must only contain alphanumeric characters and underscores"), - ), - ]; - - for (username, result) in tests { - assert_eq!( - user::validate_username(username), - result, - "username: {}", - username - ); - } -} - -#[test] -fn test_validate_password() { - let tests = vec![ - ("123", Err("Password must be at least 8 characters long")), - ("12345678", Err("Password must contain at least one lowercase character")), - ("1234567c", Err("Password must contain at least one uppercase character")), - ("abcdefgH", Err("Password must contain at least one digit")), - ("1bcdefgH", Err("Password must contain at least one special character")), - ("1!cdefgH", Ok(())), - ("1!cdefgH111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", Err("Password must be at most 100 characters long")) - ]; - - for (password, result) in tests { - assert_eq!( - user::validate_password(password), - result, - "pasword: {}", - password - ); - } -} - -#[test] -fn test_validate_email() { - let tests = vec![ - ("test", Err("Email must be at least 5 characters long")), - ("testa", Err("Email must contain an @")), - ("testa@", Err("Email must contain a .")), - ("testa@.", Err("Email is not a valid email address")), - ("testa@.com", Err("Email is not a valid email address")), - ("testa@c.om", Ok(())), - ("testa@abc.com", Ok(())), - ]; - - for (email, result) in tests { - assert_eq!(user::validate_email(email), result, "email: {}", email); - } -} diff --git a/backend/api/src/tests/dataloader/mod.rs b/backend/api/src/tests/dataloader/mod.rs deleted file mode 100644 index e52a97e30..000000000 --- a/backend/api/src/tests/dataloader/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -mod session; -mod streams; -mod user; -mod user_permissions; diff --git a/backend/api/src/tests/dataloader/session.rs b/backend/api/src/tests/dataloader/session.rs deleted file mode 100644 index b38399d29..000000000 --- a/backend/api/src/tests/dataloader/session.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::tests::global::mock_global_state; - -use crate::database::{session, user}; -use serial_test::serial; - -#[serial] -#[tokio::test] -async fn test_serial_user_by_username_loader() { - let (global, _) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let session = sqlx::query_as!( - session::Model, - "INSERT INTO sessions(user_id, expires_at) VALUES ($1, $2) RETURNING *", - user.id, - chrono::Utc::now() + chrono::Duration::seconds(30) - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let loaded = global - .session_by_id_loader - .load_one(session.id) - .await - .unwrap(); - - assert!(loaded.is_some()); - - let loaded = loaded.unwrap(); - assert_eq!(loaded.id, session.id); - assert_eq!(loaded.user_id, session.user_id); - assert_eq!(loaded.expires_at, session.expires_at); - assert_eq!(loaded.created_at, session.created_at); - assert_eq!(loaded.invalidated_at, session.invalidated_at); - assert_eq!(loaded.last_used_at, session.last_used_at); -} diff --git a/backend/api/src/tests/dataloader/streams.rs b/backend/api/src/tests/dataloader/streams.rs deleted file mode 100644 index 1f667c968..000000000 --- a/backend/api/src/tests/dataloader/streams.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::tests::global::mock_global_state; - -use crate::database::{stream, user}; -use serial_test::serial; -use uuid::Uuid; - -#[serial] -#[tokio::test] -async fn test_serial_stream_by_id_loader() { - let (global, _) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - sqlx::query!("DELETE FROM streams") - .execute(&*global.db) - .await - .unwrap(); - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let conn_id = Uuid::new_v4(); - let s = sqlx::query_as!(stream::Model, - "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", - user.id, - "test", - "test", - false, - false, - "some address", - conn_id, - ).fetch_one(&*global.db).await.unwrap(); - - let loaded = global.stream_by_id_loader.load_one(s.id).await.unwrap(); - - assert!(loaded.is_some()); - - let loaded = loaded.unwrap(); - assert_eq!(loaded.id, s.id); - assert_eq!(loaded.channel_id, user.id); - assert_eq!(loaded.title, "test"); - assert_eq!(loaded.description, "test"); - assert!(!loaded.recorded); - assert!(!loaded.transcoded); - assert_eq!(loaded.ingest_address, "some address"); - assert_eq!(loaded.connection_id, conn_id); -} diff --git a/backend/api/src/tests/dataloader/user.rs b/backend/api/src/tests/dataloader/user.rs deleted file mode 100644 index a87722a66..000000000 --- a/backend/api/src/tests/dataloader/user.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::tests::global::mock_global_state; - -use crate::database::user; -use serial_test::serial; - -#[serial] -#[tokio::test] -async fn test_serial_user_by_username_loader() { - let (global, _) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let loaded = global - .user_by_username_loader - .load_one(user.username.clone()) - .await - .unwrap(); - - assert!(loaded.is_some()); - - let loaded = loaded.unwrap(); - assert_eq!(loaded.id, user.id); - assert_eq!(loaded.username, user.username); - assert_eq!(loaded.email, user.email); - assert_eq!(loaded.password_hash, user.password_hash); - assert_eq!(loaded.created_at, user.created_at); -} - -#[serial] -#[tokio::test] -async fn test_serial_user_by_id_loader() { - let (global, _) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - let user = - sqlx::query_as!(user::Model, - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .fetch_one(&*global.db) - .await - .unwrap(); - - let loaded = global.user_by_id_loader.load_one(user.id).await.unwrap(); - - assert!(loaded.is_some()); - - let loaded = loaded.unwrap(); - assert_eq!(loaded.id, user.id); - assert_eq!(loaded.username, user.username); - assert_eq!(loaded.email, user.email); - assert_eq!(loaded.password_hash, user.password_hash); - assert_eq!(loaded.created_at, user.created_at); -} diff --git a/backend/api/src/tests/dataloader/user_permissions.rs b/backend/api/src/tests/dataloader/user_permissions.rs deleted file mode 100644 index a6f9f541f..000000000 --- a/backend/api/src/tests/dataloader/user_permissions.rs +++ /dev/null @@ -1,237 +0,0 @@ -use crate::tests::global::mock_global_state; - -use crate::database::{global_role::Permission, user}; -use serial_test::serial; - -#[serial] -#[tokio::test] -async fn test_serial_permissions_loader() { - let (global, _) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM global_roles") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM global_role_grants") - .execute(&*global.db) - .await - .unwrap(); - - let user_id = sqlx::query!( - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING id", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let admin_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "admin", - "admin", - 1, - Permission::Admin.bits(), - 0, - chrono::Utc::now() - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let go_live_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "go_live", - "go_live", - 2, - Permission::GoLive.bits(), - 0, - chrono::Utc::now() - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let no_go_live_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "no_go_live", - "no_go_live", - 3, - 0, - Permission::GoLive.bits(), - chrono::Utc::now() - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let no_admin_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "no_admin", - "no_admin", - 0, - 0, - Permission::Admin.bits(), - chrono::Utc::now() - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants(user_id, global_role_id, created_at) VALUES ($1, $2, $3)", - user_id, - admin_role_id, - chrono::Utc::now() - ) - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants(user_id, global_role_id, created_at) VALUES ($1, $2, $3)", - user_id, - go_live_role_id, - chrono::Utc::now() - ) - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants(user_id, global_role_id, created_at) VALUES ($1, $2, $3)", - user_id, - no_go_live_role_id, - chrono::Utc::now() - ) - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants(user_id, global_role_id, created_at) VALUES ($1, $2, $3)", - user_id, - no_admin_role_id, - chrono::Utc::now() - ) - .execute(&*global.db) - .await - .unwrap(); - - let loaded = global - .user_permisions_by_id_loader - .load_one(user_id) - .await - .unwrap(); - assert!(loaded.is_some()); - - let loaded = loaded.unwrap(); - assert_eq!(loaded.permissions, Permission::Admin); - assert_eq!(loaded.user_id, user_id); - - assert_eq!(loaded.roles[0].id, no_admin_role_id); - assert_eq!(loaded.roles[0].name, "no_admin"); - assert_eq!(loaded.roles[0].description, "no_admin"); - assert_eq!(loaded.roles[0].rank, 0); - assert_eq!(loaded.roles[0].allowed_permissions, 0); - assert_eq!(loaded.roles[0].denied_permissions, Permission::Admin); - - assert_eq!(loaded.roles[1].id, admin_role_id); - assert_eq!(loaded.roles[1].name, "admin"); - assert_eq!(loaded.roles[1].description, "admin"); - assert_eq!(loaded.roles[1].rank, 1); - assert_eq!(loaded.roles[1].allowed_permissions, Permission::Admin); - assert_eq!(loaded.roles[1].denied_permissions, 0); - - assert_eq!(loaded.roles[2].id, go_live_role_id); - assert_eq!(loaded.roles[2].name, "go_live"); - assert_eq!(loaded.roles[2].description, "go_live"); - assert_eq!(loaded.roles[2].rank, 2); - assert_eq!(loaded.roles[2].allowed_permissions, Permission::GoLive); - assert_eq!(loaded.roles[2].denied_permissions, 0); - - assert_eq!(loaded.roles[3].id, no_go_live_role_id); - assert_eq!(loaded.roles[3].name, "no_go_live"); - assert_eq!(loaded.roles[3].description, "no_go_live"); - assert_eq!(loaded.roles[3].rank, 3); - assert_eq!(loaded.roles[3].allowed_permissions, 0); - assert_eq!(loaded.roles[3].denied_permissions, Permission::GoLive); -} - -#[serial] -#[tokio::test] -async fn test_serial_permissions_loader_default_role() { - let (global, _) = mock_global_state(Default::default()).await; - - sqlx::query!("DELETE FROM users") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM global_roles") - .execute(&*global.db) - .await - .unwrap(); - - sqlx::query!("DELETE FROM global_role_grants") - .execute(&*global.db) - .await - .unwrap(); - - let user_id = sqlx::query!( - "INSERT INTO users(username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING id", - "admin", - "admin@admin.com", - user::hash_password("admin"), - user::generate_stream_key(), - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let default_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "default", - "default", - -1, - Permission::GoLive.bits(), - 0, - chrono::Utc::now() - ) - .map(|row| row.id) - .fetch_one(&*global.db) - .await - .unwrap(); - - let loaded = global - .user_permisions_by_id_loader - .load_one(user_id) - .await - .unwrap(); - assert!(loaded.is_some()); - - let loaded = loaded.unwrap(); - assert_eq!(loaded.permissions, Permission::GoLive); - assert_eq!(loaded.user_id, user_id); - - assert_eq!(loaded.roles[0].id, default_role_id); - assert_eq!(loaded.roles[0].name, "default"); - assert_eq!(loaded.roles[0].description, "default"); - assert_eq!(loaded.roles[0].rank, -1); - assert_eq!(loaded.roles[0].allowed_permissions, Permission::GoLive); - assert_eq!(loaded.roles[0].denied_permissions, 0); -} diff --git a/backend/api/src/tests/global/mod.rs b/backend/api/src/tests/global/mod.rs deleted file mode 100644 index f4354684c..000000000 --- a/backend/api/src/tests/global/mod.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use crate::{config::AppConfig, global::GlobalState}; -use common::{ - config::RedisSentinelConfig, - context::{Context, Handler}, - logging, - prelude::FutureTimeout, -}; -use fred::types::ServerConfig; -use tokio::select; - -pub mod turnstile; - -pub async fn mock_global_state(mut config: AppConfig) -> (Arc, Handler) { - let (ctx, handler) = Context::new(); - - dotenvy::dotenv().ok(); - - logging::init(&config.logging.level, config.logging.mode) - .expect("failed to initialize logging"); - - let db = Arc::new( - sqlx::PgPool::connect(&std::env::var("DATABASE_URL").expect("DATABASE_URL not set")) - .await - .expect("failed to connect to database"), - ); - - let rmq = common::rmq::ConnectionPool::connect( - std::env::var("RMQ_URL").expect("RMQ_URL not set"), - lapin::ConnectionProperties::default(), - Duration::from_secs(30), - 1, - ) - .timeout(Duration::from_secs(5)) - .await - .expect("failed to connect to rabbitmq") - .expect("failed to connect to rabbitmq"); - - let redis_config = fred::types::RedisConfig::from_url( - std::env::var("REDIS_URL") - .expect("REDIS_URL not set") - .as_str(), - ) - .expect("failed to parse redis url"); - - config.redis.addresses = redis_config - .server - .hosts() - .into_iter() - .map(|x| x.to_string()) - .collect(); - config.redis.database = redis_config.database.unwrap_or_default(); - config.redis.password = redis_config.password; - config.redis.username = redis_config.username; - config.redis.sentinel = match redis_config.server { - ServerConfig::Sentinel { service_name, .. } => Some(RedisSentinelConfig { service_name }), - _ => None, - }; - - let redis = crate::global::setup_redis(&config).await; - let redis_subscription = - crate::global::setup_redis_subscription(&config, Default::default()).await; - - let global = Arc::new(GlobalState::new(config, db, rmq, redis, ctx)); - let global2 = global.clone(); - tokio::spawn(async move { - select! { - _ = global2.subscription_manager.run(global2.ctx.clone(), redis_subscription) => {}, - _ = global2.rmq.handle_reconnects() => {}, - _ = global2.ctx.done() => {}, - } - }); - - (global, handler) -} diff --git a/backend/api/src/tests/global/turnstile.rs b/backend/api/src/tests/global/turnstile.rs deleted file mode 100644 index 8d38a1482..000000000 --- a/backend/api/src/tests/global/turnstile.rs +++ /dev/null @@ -1,59 +0,0 @@ -use hyper::server::conn::Http; -use serde::Deserialize; -use serde_json::json; -use tokio::{ - net::TcpListener, - sync::{mpsc, oneshot}, -}; - -#[derive(Debug, Deserialize)] -pub struct MockRequest { - pub response: String, - pub secret: String, -} - -pub async fn mock_turnstile() -> ( - mpsc::Receiver<(MockRequest, oneshot::Sender)>, - String, - tokio::task::JoinHandle<()>, -) { - let (tx, rx) = mpsc::channel(1); - - // Bind to a random port - let listener = TcpListener::bind("0.0.0.0:0").await.unwrap(); - - let addr = listener.local_addr().unwrap(); - let addr = format!("http://{}", addr); - - // Wait for http requests - let handle = tokio::spawn(async move { - loop { - let (socket, _) = listener.accept().await.unwrap(); - Http::new() - .serve_connection( - socket, - hyper::service::service_fn(|req| { - let tx = tx.clone(); - async move { - let (_, body) = req.into_parts(); - let body = hyper::body::to_bytes(body).await.unwrap(); - let req = serde_json::from_slice(body.to_vec().as_slice()).unwrap(); - let (otx, orx) = oneshot::channel::(); - tx.send((req, otx)).await.unwrap(); - let response = orx.await.unwrap(); - Ok::<_, hyper::Error>(hyper::Response::new(hyper::Body::from( - json!({ - "success": response, - }) - .to_string(), - ))) - } - }), - ) - .await - .unwrap(); - } - }); - - (rx, addr, handle) -} diff --git a/backend/api/src/tests/grpc/api.rs b/backend/api/src/tests/grpc/api.rs deleted file mode 100644 index 023feda76..000000000 --- a/backend/api/src/tests/grpc/api.rs +++ /dev/null @@ -1,1109 +0,0 @@ -use crate::config::{AppConfig, GrpcConfig}; -use crate::database::{global_role::Permission, user}; -use crate::database::{stream, stream_bitrate_update, stream_event}; -use crate::grpc::run; -use crate::pb; -use crate::pb::scuffle::backend::{ - update_live_stream_request, NewLiveStreamRequest, StreamReadyState, -}; -use crate::pb::scuffle::types::{stream_state, StreamState}; -use crate::tests::global::mock_global_state; -use chrono::Utc; -use common::grpc::make_channel; -use common::prelude::FutureTimeout; -use serial_test::serial; -use std::time::Duration; -use uuid::Uuid; - -#[serial] -#[tokio::test] -async fn test_serial_grpc_authenticate_invalid_stream_key() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let handle = tokio::spawn(run(global)); - - // We only want a single resolve attempt, so we set the timeout to 0 - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - let err = client - .authenticate_live_stream(pb::scuffle::backend::AuthenticateLiveStreamRequest { - app_name: "test".to_string(), - stream_key: "test".to_string(), - ip_address: "127.0.0.1".to_string(), - ingest_address: "127.0.0.1:1234".to_string(), - connection_id: Uuid::new_v4().to_string(), - }) - .await - .unwrap_err(); - - assert_eq!(err.code(), tonic::Code::InvalidArgument); - assert_eq!(err.message(), "invalid stream key"); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_authenticate_valid_stream_key() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM global_roles") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM global_role_grants") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key) VALUES ($1, $1, $2, $3, $4) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let go_live_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "Go Live", - "Allows a user to go live", - 0, - Permission::GoLive.bits(), - 0, - chrono::Utc::now(), - ).map(|r| r.id).fetch_one(&*db).await.unwrap(); - - let handle = tokio::spawn(run(global)); - - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - let resp = client - .authenticate_live_stream(pb::scuffle::backend::AuthenticateLiveStreamRequest { - app_name: "test".to_string(), - stream_key: user.get_stream_key(), - ip_address: "127.0.0.1".to_string(), - ingest_address: "127.0.0.1:1234".to_string(), - connection_id: Uuid::new_v4().to_string(), - }) - .await - .unwrap_err(); - - assert_eq!(resp.code(), tonic::Code::PermissionDenied); - assert_eq!(resp.message(), "user has no permission to go live"); - - sqlx::query!( - "INSERT INTO global_role_grants (user_id, global_role_id) VALUES ($1, $2)", - user.id, - go_live_role_id - ) - .execute(&*db) - .await - .unwrap(); - - let resp = client - .authenticate_live_stream(pb::scuffle::backend::AuthenticateLiveStreamRequest { - app_name: "test".to_string(), - stream_key: user.get_stream_key(), - ip_address: "127.0.0.1".to_string(), - ingest_address: "127.0.0.1:1234".to_string(), - connection_id: Uuid::new_v4().to_string(), - }) - .await - .unwrap() - .into_inner(); - - assert!(!resp.record); - assert!(!resp.transcode); - assert!(!resp.stream_id.is_empty()); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_authenticate_valid_stream_key_ext() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM global_roles") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM global_role_grants") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let go_live_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "Go Live", - "Allows a user to go live", - 0, - (Permission::GoLive | Permission::StreamRecording).bits(), - 0, - chrono::Utc::now(), - ).map(|r| r.id).fetch_one(&*db).await.unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants (user_id, global_role_id) VALUES ($1, $2)", - user.id, - go_live_role_id - ) - .execute(&*db) - .await - .unwrap(); - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - - let resp = client - .authenticate_live_stream(pb::scuffle::backend::AuthenticateLiveStreamRequest { - app_name: "test".to_string(), - stream_key: user.get_stream_key(), - ip_address: "127.0.0.1".to_string(), - ingest_address: "127.0.0.1:1234".to_string(), - connection_id: Uuid::new_v4().to_string(), - }) - .await - .unwrap() - .into_inner(); - - assert!(resp.record); - assert!(!resp.transcode); - assert!(!resp.stream_id.is_empty()); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_authenticate_valid_stream_key_ext_2() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM global_roles") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM global_role_grants") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let go_live_role_id = sqlx::query!( - "INSERT INTO global_roles(name, description, rank, allowed_permissions, denied_permissions, created_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id", - "Go Live", - "Allows a user to go live", - 0, - (Permission::GoLive | Permission::StreamTranscoding).bits(), - 0, - chrono::Utc::now(), - ).map(|r| r.id).fetch_one(&*db).await.unwrap(); - - sqlx::query!( - "INSERT INTO global_role_grants (user_id, global_role_id) VALUES ($1, $2)", - user.id, - go_live_role_id - ) - .execute(&*db) - .await - .unwrap(); - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - - let resp = client - .authenticate_live_stream(pb::scuffle::backend::AuthenticateLiveStreamRequest { - app_name: "test".to_string(), - stream_key: user.get_stream_key(), - ip_address: "127.0.0.1".to_string(), - ingest_address: "127.0.0.1:1234".to_string(), - connection_id: Uuid::new_v4().to_string(), - }) - .await - .unwrap() - .into_inner(); - - assert!(!resp.record); - assert!(resp.transcode); - assert!(!resp.stream_id.is_empty()); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_update_live_stream_state() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM streams") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let conn_id = Uuid::new_v4(); - - let s = sqlx::query_as!(stream::Model, - "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", - user.id, - "test", - "test", - false, - false, - "some address", - conn_id, - ).fetch_one(&*db).await.unwrap(); - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - - { - let timestamp = Utc::now().timestamp() as u64; - - assert!(client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::ReadyState( - StreamReadyState::Ready as i32 - )), - }] - }) - .await - .is_ok()); - - let s = sqlx::query_as!(stream::Model, "SELECT * FROM streams WHERE id = $1", s.id,) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.ready_state, stream::ReadyState::Ready); - assert_eq!(s.updated_at.unwrap().timestamp() as u64, timestamp); - } - - { - let timestamp = Utc::now().timestamp() as u64; - - assert!(client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::ReadyState( - StreamReadyState::NotReady as i32 - )), - }] - }) - .await - .is_ok()); - - let s = sqlx::query_as!(stream::Model, "SELECT * FROM streams WHERE id = $1", s.id,) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.ready_state, stream::ReadyState::NotReady); - assert_eq!(s.updated_at.unwrap().timestamp() as u64, timestamp); - } - - { - let timestamp = Utc::now().timestamp() as u64; - - assert!(client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::ReadyState( - StreamReadyState::Failed as i32 - )), - }] - }) - .await - .is_ok()); - - let s = sqlx::query_as!(stream::Model, "SELECT * FROM streams WHERE id = $1", s.id,) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.ready_state, stream::ReadyState::Failed); - assert_eq!(s.updated_at.unwrap().timestamp() as u64, timestamp); - assert_eq!(s.ended_at.timestamp() as u64, timestamp); - } - - for i in 0..2 { - let timestamp = Utc::now().timestamp() as u64; - - let res = client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::ReadyState( - StreamReadyState::Stopped as i32, - )), - }], - }) - .await; - - if i == 0 { - assert!(res.is_err()); - sqlx::query!( - "UPDATE streams SET ready_state = 0, ended_at = $2 WHERE id = $1;", - s.id, - Utc::now() + chrono::Duration::seconds(300) - ) - .execute(&*db) - .await - .unwrap(); - } else { - assert!(res.is_ok()); - let s = sqlx::query_as!(stream::Model, "SELECT * FROM streams WHERE id = $1", s.id,) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.ready_state, stream::ReadyState::Stopped); - assert_eq!(s.updated_at.unwrap().timestamp() as u64, timestamp); - assert_eq!(s.ended_at.timestamp() as u64, timestamp); - } - } - - for i in 0..2 { - let timestamp = Utc::now().timestamp() as u64; - - let res = client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::ReadyState( - StreamReadyState::StoppedResumable as i32, - )), - }], - }) - .await; - - if i == 0 { - assert!(res.is_err()); - sqlx::query!( - "UPDATE streams SET ready_state = 0, ended_at = $2 WHERE id = $1;", - s.id, - Utc::now() + chrono::Duration::seconds(300) - ) - .execute(&*db) - .await - .unwrap(); - } else { - assert!(res.is_ok()); - let s = sqlx::query_as!(stream::Model, "SELECT * FROM streams WHERE id = $1", s.id,) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.ready_state, stream::ReadyState::StoppedResumable); - assert_eq!(s.updated_at.unwrap().timestamp() as u64, timestamp); - assert_eq!(s.ended_at.timestamp() as u64, timestamp + 300); - } - } - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_update_live_stream_bitrate() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM streams") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let conn_id = Uuid::new_v4(); - - let s = sqlx::query_as!(stream::Model, - "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", - user.id, - "test", - "test", - false, - false, - "some address", - conn_id, - ).fetch_one(&*db).await.unwrap(); - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - - { - let timestamp = Utc::now().timestamp() as u64; - - assert!(client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::Bitrate( - update_live_stream_request::Bitrate { - video_bitrate: 1000, - audio_bitrate: 1000, - metadata_bitrate: 1000 - } - )), - }] - }) - .await - .is_ok()); - - let s = sqlx::query_as!( - stream_bitrate_update::Model, - "SELECT * FROM stream_bitrate_updates WHERE stream_id = $1", - s.id, - ) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.audio_bitrate, 1000); - assert_eq!(s.video_bitrate, 1000); - assert_eq!(s.metadata_bitrate, 1000); - assert_eq!(s.created_at.timestamp() as u64, timestamp); - } - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_update_live_stream_event() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM streams") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let conn_id = Uuid::new_v4(); - - let s = sqlx::query_as!(stream::Model, - "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", - user.id, - "test", - "test", - false, - false, - "some address", - conn_id, - ).fetch_one(&*db).await.unwrap(); - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - - { - let timestamp = Utc::now().timestamp() as u64; - - assert!(client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::Event( - update_live_stream_request::Event { - level: update_live_stream_request::event::Level::Info.into(), - message: "test - message".to_string(), - title: "test - title".to_string(), - } - )), - }] - }) - .await - .is_ok()); - - let s = sqlx::query_as!( - stream_event::Model, - "SELECT * FROM stream_events WHERE stream_id = $1", - s.id, - ) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.level, stream_event::Level::Info); - assert_eq!(s.message, "test - message"); - assert_eq!(s.title, "test - title"); - assert_eq!(s.created_at.timestamp() as u64, timestamp); - } - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_update_live_stream_variants() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM streams") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let conn_id = Uuid::new_v4(); - - let s = sqlx::query_as!(stream::Model, - "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", - user.id, - "test", - "test", - false, - false, - "some address", - conn_id, - ).fetch_one(&*db).await.unwrap(); - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - - { - let timestamp = Utc::now().timestamp() as u64; - - let source_id = Uuid::new_v4().to_string(); - let audio_id = Uuid::new_v4().to_string(); - - let stream_state = StreamState { - variants: vec![ - stream_state::Variant { - name: "source".to_string(), - group: "aac".to_string(), - transcode_ids: vec![source_id.to_string(), audio_id.to_string()], - }, - stream_state::Variant { - name: "audio-only".to_string(), - group: "aac".to_string(), - transcode_ids: vec![audio_id.to_string()], - }, - ], - transcodes: vec![ - stream_state::Transcode { - bitrate: 8000 * 1024, - codec: "avc1.640028".to_string(), - id: source_id.to_string(), - copy: true, - settings: Some(stream_state::transcode::Settings::Video( - stream_state::transcode::VideoSettings { - framerate: 60, - height: 1080, - width: 1920, - }, - )), - }, - stream_state::Transcode { - bitrate: 128 * 1024, - codec: "mp4a.40.2".to_string(), - id: audio_id.to_string(), - copy: false, - settings: Some(stream_state::transcode::Settings::Audio( - stream_state::transcode::AudioSettings { - channels: 2, - sample_rate: 48000, - }, - )), - }, - ], - groups: vec![ - stream_state::Group { - name: "opus".to_string(), - priority: 1, - }, - stream_state::Group { - name: "aac".to_string(), - priority: 2, - }, - ], - }; - - assert!(client - .update_live_stream(pb::scuffle::backend::UpdateLiveStreamRequest { - connection_id: conn_id.to_string(), - stream_id: s.id.to_string(), - updates: vec![update_live_stream_request::Update { - timestamp, - update: Some(update_live_stream_request::update::Update::State( - stream_state.clone() - )), - }] - }) - .await - .is_ok()); - - let s = sqlx::query_as!(stream::Model, "SELECT * FROM streams WHERE id = $1", s.id,) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.state, Some(stream_state)); - } - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[serial] -#[tokio::test] -async fn test_serial_grpc_new_live_stream() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let db = global.db.clone(); - sqlx::query!("DELETE FROM users") - .execute(&*db) - .await - .unwrap(); - sqlx::query!("DELETE FROM streams") - .execute(&*db) - .await - .unwrap(); - - let user = sqlx::query_as!(user::Model, - "INSERT INTO users (username, display_name, email, password_hash, stream_key, stream_recording_enabled, stream_transcoding_enabled) VALUES ($1, $1, $2, $3, $4, true, true) RETURNING *", - "test", - "test@test.com", - user::hash_password("test"), - user::generate_stream_key(), - ).fetch_one(&*db).await.unwrap(); - - let conn_id = Uuid::new_v4(); - - let s = sqlx::query_as!(stream::Model, - "INSERT INTO streams (channel_id, title, description, recorded, transcoded, ingest_address, connection_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *", - user.id, - "test", - "test", - false, - false, - "some address", - conn_id, - ).fetch_one(&*db).await.unwrap(); - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::scuffle::backend::api_client::ApiClient::new(channel); - - let source_id = Uuid::new_v4().to_string(); - let audio_id = Uuid::new_v4().to_string(); - - let stream_state = StreamState { - variants: vec![ - stream_state::Variant { - name: "source".to_string(), - group: "aac".to_string(), - transcode_ids: vec![source_id.to_string(), audio_id.to_string()], - }, - stream_state::Variant { - name: "audio-only".to_string(), - group: "aac".to_string(), - transcode_ids: vec![audio_id.to_string()], - }, - ], - transcodes: vec![ - stream_state::Transcode { - bitrate: 8000 * 1024, - codec: "avc1.640028".to_string(), - id: source_id.to_string(), - copy: true, - settings: Some(stream_state::transcode::Settings::Video( - stream_state::transcode::VideoSettings { - framerate: 60, - height: 1080, - width: 1920, - }, - )), - }, - stream_state::Transcode { - bitrate: 128 * 1024, - codec: "mp4a.40.2".to_string(), - id: audio_id.to_string(), - copy: false, - settings: Some(stream_state::transcode::Settings::Audio( - stream_state::transcode::AudioSettings { - channels: 2, - sample_rate: 48000, - }, - )), - }, - ], - groups: vec![stream_state::Group { - name: "aac".to_string(), - priority: 1, - }], - }; - - let response = client - .new_live_stream(NewLiveStreamRequest { - old_stream_id: s.id.to_string(), - state: Some(stream_state.clone()), - }) - .await - .unwrap() - .into_inner(); - - let s = sqlx::query_as!(stream::Model, "SELECT * FROM streams WHERE id = $1", s.id) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.channel_id, user.id); - assert_eq!(s.title, "test"); - assert_eq!(s.description, "test"); - assert!(!s.recorded); - assert!(!s.transcoded); - assert_eq!(s.ingest_address, "some address"); - assert_eq!(s.connection_id, conn_id); - assert_eq!(s.ready_state, stream::ReadyState::Stopped); - - let stream_id = Uuid::parse_str(&response.stream_id).unwrap(); - - let s = sqlx::query_as!( - stream::Model, - "SELECT * FROM streams WHERE id = $1", - stream_id, - ) - .fetch_one(&*db) - .await - .unwrap(); - - assert_eq!(s.channel_id, user.id); - assert_eq!(s.title, "test"); - assert_eq!(s.description, "test"); - assert!(!s.recorded); - assert!(!s.transcoded); - assert_eq!(s.ingest_address, "some address"); - assert_eq!(s.connection_id, conn_id); - assert_eq!(s.ready_state, stream::ReadyState::NotReady); - assert_eq!(s.state, Some(stream_state)); - - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} diff --git a/backend/api/src/tests/grpc/certs/ca.ec.crt b/backend/api/src/tests/grpc/certs/ca.ec.crt deleted file mode 100644 index 82ec20569..000000000 --- a/backend/api/src/tests/grpc/certs/ca.ec.crt +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBbDCCARKgAwIBAgIUD7lIwYJfD4pXzc5Hmka6YW8ZaPkwCgYIKoZIzj0EAwIw -FDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIzMDQyNjA3NDYwN1oXDTI0MDQyNTA3 -NDYwN1owFDESMBAGA1UEAwwJMTI3LjAuMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAE/Z1nx+SdBH3DEkDJp8meqPghyi8+Zte19nT1a0j9F9rs0Rp3JFqqg19k -3rnk4NhEOpest8oXdxGdGuN9SeAqp6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFEDW3iY//ykVWPuZUP6031FUkADEMAoGCCqG -SM49BAMCA0gAMEUCIGF3DQXktKgLsbG9qAJc5HMmaIn6sslHDmeCXhoHBBnCAiEA -2Cr7XwDhb0G/CeoQFFqN3DqIqdcF9Nx+xD7bq9cmcsQ= ------END CERTIFICATE----- diff --git a/backend/api/src/tests/grpc/certs/ca.ec.key b/backend/api/src/tests/grpc/certs/ca.ec.key deleted file mode 100644 index c88a9c62c..000000000 --- a/backend/api/src/tests/grpc/certs/ca.ec.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgP2JTKPru+7n9VssT -ulAKqEhuXNY6qT53Al0QPMF3akahRANCAAT9nWfH5J0EfcMSQMmnyZ6o+CHKLz5m -17X2dPVrSP0X2uzRGnckWqqDX2TeueTg2EQ6l6y3yhd3EZ0a431J4Cqn ------END PRIVATE KEY----- diff --git a/backend/api/src/tests/grpc/certs/ca.ini b/backend/api/src/tests/grpc/certs/ca.ini deleted file mode 100644 index bf362da62..000000000 --- a/backend/api/src/tests/grpc/certs/ca.ini +++ /dev/null @@ -1,13 +0,0 @@ -[req] -prompt = no -default_md = sha256 -distinguished_name = dn -# Since this is a CA, the key usage is critical -x509_extensions = v3_ca - -[v3_ca] -basicConstraints = critical,CA:TRUE -keyUsage = critical, digitalSignature, cRLSign, keyCertSign - -[dn] -CN = 127.0.0.1 diff --git a/backend/api/src/tests/grpc/certs/ca.rsa.crt b/backend/api/src/tests/grpc/certs/ca.rsa.crt deleted file mode 100644 index 054ad4306..000000000 --- a/backend/api/src/tests/grpc/certs/ca.rsa.crt +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC+DCCAeCgAwIBAgIUXqZUdmW4azF3HUzOBmLvHdVFeEIwDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIzMDQyNjA3NDYwN1oXDTI0MDQy -NTA3NDYwN1owFDESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEA2StIP6B/kMC2Bj/FDbod+Vy7NRMR7xmKV1H3jJW68Ect -tjYtAdbMvUpG83hxzZYPJtf7XxT6ZLqzoyTcINMUTMjmKJIL6G7tnw3h38SjjRxN -rkc2GF0a7wF1XKKnL2QBkbIp275j1Sx10BJ1JjY8d9Xpb7DpRy9AhVqpBq6yQu0K -9lYQ0tBKHCq4xiX6cACnh0onIXyoh//NhFrYwkC5092I2M+qHjCT4lxUPj/5uz85 -cFYUM19FaIpr6AwlLwij80Sgiaa9OGvHti7irh2wZZ0QO8nka8Rt6QvjZDW0Ceni -qi7UvLVj23pPCONGiTlfL+moYxBP4DJC7ookpbjEWQIDAQABo0IwQDAPBgNVHRMB -Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUaTUP1AzqvhhKX6br -P+b+4V+J7U4wDQYJKoZIhvcNAQELBQADggEBAAKFV6N/NeXTagXq8xJKn9+7LLQk -TuxDYAvSguDvDHKZdfi36w+UntGtTMnvDinQhSkagYXIvBbQCA9ZsNnv3UsZ/atc -uMPpN7qoM3MVmoNi1/T9h5ZBFmIXjSl7aY/vRWfogt0k6IRF4fU9RR5Vc7gG77aH -OvXeJAvW6swgg36GDLgvwizzMJECdGLRnWAkJ4uHp/PUXbY8XKKi3eooNjgXjM2m -xMPkev3mSL6nBYqQc3Jtw5r9pqbc/3bgu3w3Wb//yuUSzCi2a+PL2jN0SfDMUgKx -X/CN8Dd4sewYpC5gBqW29W2cDSmDFgy5s6PGyxCOBwNhXlHjyraRRKcgBfc= ------END CERTIFICATE----- diff --git a/backend/api/src/tests/grpc/certs/ca.rsa.key b/backend/api/src/tests/grpc/certs/ca.rsa.key deleted file mode 100644 index 25fe34f92..000000000 --- a/backend/api/src/tests/grpc/certs/ca.rsa.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZK0g/oH+QwLYG -P8UNuh35XLs1ExHvGYpXUfeMlbrwRy22Ni0B1sy9SkbzeHHNlg8m1/tfFPpkurOj -JNwg0xRMyOYokgvobu2fDeHfxKONHE2uRzYYXRrvAXVcoqcvZAGRsinbvmPVLHXQ -EnUmNjx31elvsOlHL0CFWqkGrrJC7Qr2VhDS0EocKrjGJfpwAKeHSichfKiH/82E -WtjCQLnT3YjYz6oeMJPiXFQ+P/m7PzlwVhQzX0VoimvoDCUvCKPzRKCJpr04a8e2 -LuKuHbBlnRA7yeRrxG3pC+NkNbQJ6eKqLtS8tWPbek8I40aJOV8v6ahjEE/gMkLu -iiSluMRZAgMBAAECggEAJctUDAq1GK6JHyZK93wcClE6nV5/wQJLYq33rIZEXdut -V2gvRgIpaIn8NhQQjixe364355DBkPUzHSHlk2rYvhI6h/X+z4k6nnMui6BvrDew -RzPKdMwDS3QQBjqiaOt5IG+GvGDyg990c2066RcIR/y43wDFYGeXTX39K5YMnPvM -00Rnj3gNiqjiOKTxyyx3w0AZ/vrnBStuhsOekUjjPXm5vMGMJrQUUlcCUVLIwVhE -TTiu740yh4dpIRLwiYpEZZ69Ag2wlY+qCOPltjoDdDSaR86oFSx35d13vDcWThc1 -ve4NRbCLQCYsdRvc2HiHEmTDvZTBbO8k9AxGONxrBwKBgQDx73teWFtlWmKep6kn -DMcEFFt1ichYc5USmxFpNAHm+pCISGk1lElNU7cZ/0WMuv7xDjMsMEzqDKHZtVPm -Azb5dr0qTWij6tEmRXVoZIbCmVyak/hrUYKVlKnGBNxix/WanvUIknmBKo5kHcq8 -h3Sd+HUNit/kVYGqoi27V71mswKBgQDlyzkg48GZpnHnSoKm1LyHnKOKkdCSSrO0 -5xs8Yeq7UWgLRVkRlTjOwdXz5/pYcCyVbVkekj9TARiZupdCyQqlEYShgNp7w9zg -NRmesOOZGHatMs/wIkTfoMpFG5cqPmKU2TvEps89hd+Gg52kDBXFKFS9oICd9fzx -9oZmhLNOwwKBgCylYwDQEV2sxlI84mxAYWGRWCdim8Qm4DWkxBvD6y3yw0VDB5dJ -nBVXA40anH0R7QYS9sKKz0bJufxxB+CEa1qx3Mq3qj3FkX8chkQTeQLkRkCIWemE -CzMLUiEmuHzKJbq45sMENMPvVIOJM+aCoLSeKwuquxJp7RnN/954nI0dAoGBANJm -fspUf2EV+1jQ6kuioXRxwXQRCq3H5D8RE+j3ppsYcHFRb7ofrUHyTNnkX142Zzvy -QRUyxvRTHpkzNWga97ooDg5qEqIbtdM8C1c3k00MDy2KRsYSOomfiVQ5bPFq6Yxs -UsM+EKa+OunI/L/FqPE6ekyd9uWq440QMgMQIbVNAoGBAJEHM0k5ixOy7dddp8HL -g1fCMSG97OP9RvAokJ4IoxYfsN5UrIyzTMEl+X48wr60x4CZ4ujfWJjCf+T0BlOT -ySWJmL/UENgcXXJT9o0r5zDsaHzl50TTqzc2e88bEZugMZtI4irsYKdTNbpMAB4t -s8iLHThB6pH8Zm+l9uIJ+5jS ------END PRIVATE KEY----- diff --git a/backend/api/src/tests/grpc/certs/client.ec.crt b/backend/api/src/tests/grpc/certs/client.ec.crt deleted file mode 100644 index d559430b8..000000000 --- a/backend/api/src/tests/grpc/certs/client.ec.crt +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBtjCCAV2gAwIBAgIUWdzGQA6hGcxsRcr/c7Zt0WyEoPkwCgYIKoZIzj0EAwIw -FDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIzMDQyNjA3NDYwN1oXDTI0MDQyNTA3 -NDYwN1owFDESMBAGA1UEAwwJMTI3LjAuMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAE7PXNCQBhW4UNHRlJYUWNtarw3DJI4L3jc/q5nHBCCm+fe29VrKw4kn3f -Lsy3671/l7iCzncuaADxjZpHYhJCP6OBjDCBiTAMBgNVHRMBAf8EAjAAMA4GA1Ud -DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjAUBgNVHREEDTALgglsb2Nh -bGhvc3QwHQYDVR0OBBYEFD7BGHJdZGtp2Ax/2WfWnZiWplBHMB8GA1UdIwQYMBaA -FEDW3iY//ykVWPuZUP6031FUkADEMAoGCCqGSM49BAMCA0cAMEQCIBZnmCIL/cSz -wA1Cm7H7umLXddgwkApe/LzW/jjGkBZhAiAV4XGwZ/r3RjnprZLGykE3EDh9AQF4 -k8FVihOBnbTepQ== ------END CERTIFICATE----- diff --git a/backend/api/src/tests/grpc/certs/client.ec.key b/backend/api/src/tests/grpc/certs/client.ec.key deleted file mode 100644 index 7f6040c15..000000000 --- a/backend/api/src/tests/grpc/certs/client.ec.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQggQHoqFAca1SpwuZK -61I7pV/UM27GyaDlqeSRpmKQyaOhRANCAATs9c0JAGFbhQ0dGUlhRY21qvDcMkjg -veNz+rmccEIKb597b1WsrDiSfd8uzLfrvX+XuILOdy5oAPGNmkdiEkI/ ------END PRIVATE KEY----- diff --git a/backend/api/src/tests/grpc/certs/client.ini b/backend/api/src/tests/grpc/certs/client.ini deleted file mode 100644 index 01771e661..000000000 --- a/backend/api/src/tests/grpc/certs/client.ini +++ /dev/null @@ -1,17 +0,0 @@ -[req] -prompt = no -default_md = sha256 -distinguished_name = dn -x509_extensions = v3_client - -[v3_client] -basicConstraints = critical,CA:FALSE -keyUsage = critical, digitalSignature, keyEncipherment -extendedKeyUsage = clientAuth -subjectAltName = @alt_names - -[dn] -CN = 127.0.0.1 - -[alt_names] -DNS.1 = localhost diff --git a/backend/api/src/tests/grpc/certs/client.rsa.crt b/backend/api/src/tests/grpc/certs/client.rsa.crt deleted file mode 100644 index 25eb02fe7..000000000 --- a/backend/api/src/tests/grpc/certs/client.rsa.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDQzCCAiugAwIBAgIUaMd1mBrftVLT5LEX6x2F5w6nu0EwDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIzMDQyNjA3NDYwN1oXDTI0MDQy -NTA3NDYwN1owFDESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAtpY9Mz1nNDT5tV7SOQhoeF90TcL80A8XN/+svXSWOIZI -xsuScR9GyMbmHuxCNWr1WGm/rYAE9lXLMQBVGR5DCFhriPIhFXV96Bk/mvF4FnTt -kMa63X1SrjwMpjc3PiQFwvo8IZrpb3Scy+npIow4LWeDt2QhRfoFV/OfKFcfe5lw -KrCCD3ciX8GWV3pSKoRpKwYhYa9uG6YmRcTkfJSOEE9Mwf37qDhmJBZ4LONKR+wA -2D0aHRSL2h5PnVi3g+09+PpNyR4YIVCTUk14Bkra/ArRIx+y9kbBFc3ScS1Qws2w -b9KULV9G11c2ZU9uS/JoLPExwHPYr9uhJUbyGlx8gwIDAQABo4GMMIGJMAwGA1Ud -EwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMBQG -A1UdEQQNMAuCCWxvY2FsaG9zdDAdBgNVHQ4EFgQU54Z9d3V0fBN0sNuNjKnkSmHS -MMcwHwYDVR0jBBgwFoAUaTUP1AzqvhhKX6brP+b+4V+J7U4wDQYJKoZIhvcNAQEL -BQADggEBAMbwivHIEyfsiGcstppemgkRZQPo5nrHPVp2rS7DZSXeNQn8rimwKBAc -DKcR3K7LAc2iBY3lxlhl7UiELpDxxJkRiH0wCTcogXiOz4Dbavm+zVUmcp73A3GT -AfRmmyE4hhB0GPFHEAgWzQ6NX4m8/hNRHHEu4UWHLbR70eoFcpN1ZQ/ju94O5XWp -0FzaB/EVMG3dq0gD2YjAJud/v8+fZ/dMws7xPtXc+UdoIBNBG9BUgUYFB03aU04h -GdXK/biPc3H45Cn3o/tT+tAZj5D63wqGLsERsDaw0Zndi4CjVMVAcb41o8tx9dwq -7V918+QYCiKL75E44jAE57JdKDu/rJM= ------END CERTIFICATE----- diff --git a/backend/api/src/tests/grpc/certs/client.rsa.key b/backend/api/src/tests/grpc/certs/client.rsa.key deleted file mode 100644 index 03ea9f001..000000000 --- a/backend/api/src/tests/grpc/certs/client.rsa.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC2lj0zPWc0NPm1 -XtI5CGh4X3RNwvzQDxc3/6y9dJY4hkjGy5JxH0bIxuYe7EI1avVYab+tgAT2Vcsx -AFUZHkMIWGuI8iEVdX3oGT+a8XgWdO2QxrrdfVKuPAymNzc+JAXC+jwhmulvdJzL -6ekijDgtZ4O3ZCFF+gVX858oVx97mXAqsIIPdyJfwZZXelIqhGkrBiFhr24bpiZF -xOR8lI4QT0zB/fuoOGYkFngs40pH7ADYPRodFIvaHk+dWLeD7T34+k3JHhghUJNS -TXgGStr8CtEjH7L2RsEVzdJxLVDCzbBv0pQtX0bXVzZlT25L8mgs8THAc9iv26El -RvIaXHyDAgMBAAECggEAD+94c4XMmxVAi5ObnDF5EYBrOyb+2TEBlF6ZKe1uxIvC -KeK2Hjwqxl0toM2uUNEyWb9Cl/1QJMIdU/wbPgnF5rv1oePxmHJ2Eaq0DwbVT0hw -1eDlidOYde9Bv12auBUWxHFojBpKfPLCUiQd6wKUtWNcCJNE01tCpu4/s5qe+2XY -fbwyTr3ECh+4ykOe700FkRXvOp6g2MCnYed9UX6R6QnFba4usAYB/6r+caAQLuFU -xkUG0wtgwc3Y5e4ARZfm+jLSk1n0QqmBtRCar+yCZCBv+alvyjUi/6ckMVtKQOls -QmTyVE/0RLJ+ouyxKUSwW+dYgLcGLEtvNPnWD3fIKQKBgQD7qrInLcBnEmGUpuBV -w6e8uz6NjYLnJiFYXuL5R/egdOsZWmN0bU0G4RlYhSlNvtmnJbunkXSxVcUvld/4 -yslrIyTIefp9r0odel8jp0RlGcFSEiX5Jd2lZgwSSEaINfeVLdymYaHT3bG+OaG+ -Fg+YfSZDTpA109nAjbQEHIMZ1wKBgQC5uwz9Ev8ocTM6DqTe7/sdOA/Rmq1A9dPb -UBo/qY7/5zlEX8nh46hDRHK7CDVc2siWtbE5wTmeuQJBq2An0kbJ9cEvNQf2AhcF -jxIjHXTn/D6pH9NEunJ9KIyYa5gpwwiOwZ2i1aLx68zeCUgWJn8QpaVoMtbOD/Om -KDncqoKVNQKBgEYM813YbE/EXAkGkjcNKOltrTG1jBRPFsUEVGVS1OiC4tXBxSEp -M2GQ0n9DQYX0c50E8cDoyfR6jVJ7g0Y7G+mIdLqgyUqmuhiOcWD5NB54FsmDZ7J8 -Cb/6Ma39FDTh4LJTe7GMR4Ezmj/6xrykY17spvPhMF7rQxdH2i2yygMtAoGAbPSM -RBJW2TS/JnncwmPp3lQ7A21H4enUZL3zCBHCL+FHORMwbXGODhgjbhD8rQIz1iW0 -V/tAgBpsdnXArNuwZ13h/H2RtAG526r4qtzm7giOAc0HtTPjYgTdpbTIyQb/CDAQ -96t4MY9w2Xh8b3IZ7HwgMqZQWQYGmxzSBN+KR1kCgYAT/lNpI0ronU9Ag07IERk3 -/okG9FiICDl9hrm/7M3xApbAO3qY6FGh3JXjUpi3eS2nDRmd2ChgBKV6XC1cjeJF -52EIcUM0Qw93iHlJ500NipOO3gxkvm5uMSji+OG3WUW2V/TGrJugR8fH7ndIJuti -i9gnWIcZg0gNKTrfd/Z+0g== ------END PRIVATE KEY----- diff --git a/backend/api/src/tests/grpc/certs/generate.sh b/backend/api/src/tests/grpc/certs/generate.sh deleted file mode 100755 index 775547903..000000000 --- a/backend/api/src/tests/grpc/certs/generate.sh +++ /dev/null @@ -1,23 +0,0 @@ -openssl genrsa -out ca.rsa.key 2048 -openssl genrsa -out server.rsa.key 2048 -openssl genrsa -out client.rsa.key 2048 - -openssl req -x509 -sha256 -days 365 -nodes -key ca.rsa.key -config ca.ini -out ca.rsa.crt -openssl req -x509 -sha256 -days 365 -CA ca.rsa.crt -CAkey ca.rsa.key -nodes -key server.rsa.key -config server.ini -out server.rsa.crt -openssl req -x509 -sha256 -days 365 -CA ca.rsa.crt -CAkey ca.rsa.key -nodes -key client.rsa.key -config client.ini -out client.rsa.crt - -openssl ecparam -name prime256v1 -genkey -noout -out ca.ec.key -openssl ecparam -name prime256v1 -genkey -noout -out server.ec.key -openssl ecparam -name prime256v1 -genkey -noout -out client.ec.key - -openssl pkcs8 -topk8 -nocrypt -in ca.ec.key -out ca.ec.key.pem -openssl pkcs8 -topk8 -nocrypt -in server.ec.key -out server.ec.key.pem -openssl pkcs8 -topk8 -nocrypt -in client.ec.key -out client.ec.key.pem - -mv ca.ec.key.pem ca.ec.key -mv server.ec.key.pem server.ec.key -mv client.ec.key.pem client.ec.key - -openssl req -x509 -sha256 -days 365 -nodes -key ca.ec.key -config ca.ini -out ca.ec.crt -openssl req -x509 -sha256 -days 365 -CA ca.ec.crt -CAkey ca.ec.key -nodes -key server.ec.key -config server.ini -out server.ec.crt -openssl req -x509 -sha256 -days 365 -CA ca.ec.crt -CAkey ca.ec.key -nodes -key client.ec.key -config client.ini -out client.ec.crt \ No newline at end of file diff --git a/backend/api/src/tests/grpc/certs/server.ec.crt b/backend/api/src/tests/grpc/certs/server.ec.crt deleted file mode 100644 index 15dc78b7e..000000000 --- a/backend/api/src/tests/grpc/certs/server.ec.crt +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBuDCCAV2gAwIBAgIUHlnL3L9grMgQlMCTMIYI8VqIqC0wCgYIKoZIzj0EAwIw -FDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIzMDQyNjA3NDYwN1oXDTI0MDQyNTA3 -NDYwN1owFDESMBAGA1UEAwwJMTI3LjAuMC4xMFkwEwYHKoZIzj0CAQYIKoZIzj0D -AQcDQgAEHpdp0sq/kN5TyTYYpVoxi9nZ7nEaW3wE4siqU4H62N7nySTqTFC/RqKm -zyhrsMKQe0h4b8R4PS+oj/YLY3U0WKOBjDCBiTAMBgNVHRMBAf8EAjAAMA4GA1Ud -DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAUBgNVHREEDTALgglsb2Nh -bGhvc3QwHQYDVR0OBBYEFNuM6maKVUzmfUp11LFR6dSjjRjIMB8GA1UdIwQYMBaA -FEDW3iY//ykVWPuZUP6031FUkADEMAoGCCqGSM49BAMCA0kAMEYCIQDYME9Gv8mm -yD1v9zHmig8j0yIN/u7oQqK1CtD8Q9kTjgIhAK+yPVwLb2HLzitQSiNkwssjTO55 -bFZFkRpQt5SJERFY ------END CERTIFICATE----- diff --git a/backend/api/src/tests/grpc/certs/server.ec.key b/backend/api/src/tests/grpc/certs/server.ec.key deleted file mode 100644 index e4691e9b6..000000000 --- a/backend/api/src/tests/grpc/certs/server.ec.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJlXWMaPnYbDFU3ZT -NWA3y3jYC0MbRjdV1pDdJb8/PjKhRANCAAQel2nSyr+Q3lPJNhilWjGL2dnucRpb -fATiyKpTgfrY3ufJJOpMUL9GoqbPKGuwwpB7SHhvxHg9L6iP9gtjdTRY ------END PRIVATE KEY----- diff --git a/backend/api/src/tests/grpc/certs/server.ini b/backend/api/src/tests/grpc/certs/server.ini deleted file mode 100644 index 343318079..000000000 --- a/backend/api/src/tests/grpc/certs/server.ini +++ /dev/null @@ -1,17 +0,0 @@ -[req] -prompt = no -default_md = sha256 -distinguished_name = dn -x509_extensions = v3_server - -[v3_server] -basicConstraints = critical,CA:FALSE -keyUsage = critical, digitalSignature, keyEncipherment -extendedKeyUsage = serverAuth -subjectAltName = @alt_names - -[dn] -CN = 127.0.0.1 - -[alt_names] -DNS.1 = localhost diff --git a/backend/api/src/tests/grpc/certs/server.rsa.crt b/backend/api/src/tests/grpc/certs/server.rsa.crt deleted file mode 100644 index cb501436e..000000000 --- a/backend/api/src/tests/grpc/certs/server.rsa.crt +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDQzCCAiugAwIBAgIUQKWD2OUEgRQOZOu5JGFMUviu5gcwDQYJKoZIhvcNAQEL -BQAwFDESMBAGA1UEAwwJMTI3LjAuMC4xMB4XDTIzMDQyNjA3NDYwN1oXDTI0MDQy -NTA3NDYwN1owFDESMBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAqe5/bibogtPbqRVLwRrIPWs/uj8fjEFkAv7NQJDcYrY8 -Oe7OH8jzsPd+4/bTYLPCsvI+1qslIWUlvprNfYlXflLiKvNZZ6ahZG5uslj6Hg/I -0mUD43m+mJDpfrti13CTHC7a+i71SWEBgaSWfIK0rN8iqNu4NudR54uWU8USIDyK -8Q2oLTtV4ufOGER3mmEG1IQmuWoCbG/yenNBufH8AFiov/PK1gecOPMeWggKhokB -hZgSCJiKK7ROhY3kBiopHv1dxFHnvLoDAM/tDgTi7WcMwlKeLQ9i3a5z2GzM0PTF -l5FC+haIv9tQmGbtdxvmybl9MD6yKuvYNGQIoM0k5QIDAQABo4GMMIGJMAwGA1Ud -EwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBQG -A1UdEQQNMAuCCWxvY2FsaG9zdDAdBgNVHQ4EFgQUYCk0gGYAR1kqhuebLop9IPij -9yAwHwYDVR0jBBgwFoAUaTUP1AzqvhhKX6brP+b+4V+J7U4wDQYJKoZIhvcNAQEL -BQADggEBAJV2pwDkvxZGxr5WS5x6PzlekhhhOiSP7BLC30VIN2X+NBZXZ9fYvOfg -GRLF+CWwErInJafFi3BRIsww3y//JVbChBbZMTxZj7IrrxP7A7TPZ17m1M22thO8 -QB8Qk4hMG2zvVH68eVi1ZYecNnH/TtTEXB4Y//nMmOyi6pymJ4SVWOV/320r8xq4 -N0cAlL4ubARbxPkzKIdODJ7F7ZQjqAkn1z1cAdGxSUpjxBI7p86IvI3MxucCIcpv -gSTFmCTAp7i2Dld72jodVo5WbjLxvuvgGfdseDjhhbsQh7Bn7o5KITOJ7HpxBCda -eGK1B7RKkTiqdG/ZSrVj6k9JTzyS3kg= ------END CERTIFICATE----- diff --git a/backend/api/src/tests/grpc/certs/server.rsa.key b/backend/api/src/tests/grpc/certs/server.rsa.key deleted file mode 100644 index 4bb934d1e..000000000 --- a/backend/api/src/tests/grpc/certs/server.rsa.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCp7n9uJuiC09up -FUvBGsg9az+6Px+MQWQC/s1AkNxitjw57s4fyPOw937j9tNgs8Ky8j7WqyUhZSW+ -ms19iVd+UuIq81lnpqFkbm6yWPoeD8jSZQPjeb6YkOl+u2LXcJMcLtr6LvVJYQGB -pJZ8grSs3yKo27g251Hni5ZTxRIgPIrxDagtO1Xi584YRHeaYQbUhCa5agJsb/J6 -c0G58fwAWKi/88rWB5w48x5aCAqGiQGFmBIImIortE6FjeQGKike/V3EUee8ugMA -z+0OBOLtZwzCUp4tD2LdrnPYbMzQ9MWXkUL6Foi/21CYZu13G+bJuX0wPrIq69g0 -ZAigzSTlAgMBAAECggEAD5z7tV260EZx8MulnbT9v/LqNI0XM3ZQn5vUtQF6VlGD -IBmKc84tYc2jqYNksYZitblfP68S5soZ2TT0+3tSgCdSY3rfdJARVR52akmVlYyC -uZ4RaOWnNvJdmcjS0JOl2JmPghwtalQQ5N3/+6mwuw93akdh2h2P33PqWIELZM20 -7HKjvJ5kxfGFI56/0cft3lzmbFZyPZSa5aXwDq82X/7cLFr1pZhlCNZnbNr0pyG+ -BiIH7ZeP3caJ3lwhHXdVdsVV2IXrWFQ6qVIvfLBmoRGn4SsSlSl3IlsKOehP0eAW -TubbKsgoZTpB12JyNXIM+2ssFLN0e/JtuhPQzzygMwKBgQDRdNsOM8WKe1XlgkCK -EEH0+4lNhKIc6KFP6IqSPCywPAGDhlkSej3LFbS1kHe4QZsXW5OhFk8LbuNLoPBE -yyD9qKBkuMJR5Xad6tN6uP/7FJuAZusu+7jl+QpgYxMv+GrTo3M6QUuPcG6lOSOd -/pvIvC+NNxIX8No0DbXbQMRx0wKBgQDPsTtJrNgB2eDlik6xIBnwvN3R/WlXY2r2 -DqyaogCvml5ofqnKmEjSrfh7vNtmvHZ8P6ehPMs6s0ap31Kgtuy7DM8DNkw6GwyY -JLlNqzjCdPAnwR8gyU2h3fAAy/4cuJJwsHLjxWJx4sBbHsyK7nzpH7UGkzktxua8 -o5telU6jZwKBgQCO7N5NYqZ5SI/kfGztyQo40Stv6gF1GIh6roNgJg+YclnWFebR -5PgljDozatFGuf3KgoLKeR6W/qO7B6bsSm/IpzhLgoeWuq2mNIb6RyLlgbpac+An -vzz8MGQUQYbmRO0gXXhTWBrnViEqPUNAnGxRHZiVE+8UxxUeT/y4EAn8YQKBgQCY -p6H2Mw7JvYUp8hCI7Blk8szvzZ0h2DcECCEhvzVV3NbLY14VRP0xrSFYgaWZy6gj -Bv6E6pRN3vtvXG/1JL63dWCq8bvxcXQ+V6/DwLgFZcIm1jG0/YEMGn6Pd2CdZ6Rr -I6YueCQ1pP7Rer/I1iYFi4KZBJkgZnOt72sBiCi2vQKBgGmAiVd9RBBecM4fakpB -8H5w2VGm/OEWHy67WWYXi9VpX+j5ovTmERs+q6wo0Qe2ZRyJTB0KyUNiyRqwdCwI -+XmyA97naWoFnRtYwsspm1Q7B8QOfXf0nFQkWJMJamvmYNzEQV3X9HFwIJ7xiz9i -UbTOC+sRqOm9dYHfNn12Us93 ------END PRIVATE KEY----- diff --git a/backend/api/src/tests/grpc/health.rs b/backend/api/src/tests/grpc/health.rs deleted file mode 100644 index 8bb2aa8c8..000000000 --- a/backend/api/src/tests/grpc/health.rs +++ /dev/null @@ -1,107 +0,0 @@ -use common::grpc::make_channel; -use common::prelude::FutureTimeout; -use std::time::Duration; - -use crate::config::{AppConfig, GrpcConfig}; -use crate::grpc::run; -use crate::pb; -use crate::tests::global::mock_global_state; - -#[tokio::test] -async fn test_grpc_health_check() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let handle = tokio::spawn(run(global)); - - let channel = make_channel( - vec![format!("http://localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::health::health_client::HealthClient::new(channel); - let resp = client - .check(pb::health::HealthCheckRequest::default()) - .await - .unwrap(); - assert_eq!( - resp.into_inner().status, - pb::health::health_check_response::ServingStatus::Serving as i32 - ); - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[tokio::test] -async fn test_grpc_health_watch() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - ..Default::default() - }, - ..Default::default() - }) - .await; - - let handle = tokio::spawn(run(global)); - let channel = make_channel( - vec![format!("http://localhost:{}", port)], - Duration::from_secs(0), - None, - ) - .unwrap(); - - let mut client = pb::health::health_client::HealthClient::new(channel); - - let resp = client - .watch(pb::health::HealthCheckRequest::default()) - .await - .unwrap(); - - let mut stream = resp.into_inner(); - let resp = stream.message().await.unwrap().unwrap(); - assert_eq!( - resp.status, - pb::health::health_check_response::ServingStatus::Serving as i32 - ); - - let cancel = handler.cancel(); - - let resp = stream.message().await.unwrap().unwrap(); - assert_eq!( - resp.status, - pb::health::health_check_response::ServingStatus::NotServing as i32 - ); - - cancel - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} diff --git a/backend/api/src/tests/grpc/mod.rs b/backend/api/src/tests/grpc/mod.rs deleted file mode 100644 index b30535930..000000000 --- a/backend/api/src/tests/grpc/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod api; -mod health; -mod tls; diff --git a/backend/api/src/tests/grpc/tls.rs b/backend/api/src/tests/grpc/tls.rs deleted file mode 100644 index 3255f60f1..000000000 --- a/backend/api/src/tests/grpc/tls.rs +++ /dev/null @@ -1,152 +0,0 @@ -use common::config::TlsConfig; -use common::grpc::{make_channel, TlsSettings}; -use common::prelude::FutureTimeout; -use std::path::PathBuf; -use std::time::Duration; -use tonic::transport::{Certificate, Identity}; - -use crate::config::{AppConfig, GrpcConfig}; -use crate::grpc::run; -use crate::pb; -use crate::tests::global::mock_global_state; - -#[tokio::test] -async fn test_grpc_tls_rsa() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/tests/grpc/certs"); - - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - tls: Some(TlsConfig { - cert: dir.join("server.rsa.crt").to_str().unwrap().to_string(), - ca_cert: dir.join("ca.rsa.crt").to_str().unwrap().to_string(), - key: dir.join("server.rsa.key").to_str().unwrap().to_string(), - domain: Some("localhost".to_string()), - }), - }, - ..Default::default() - }) - .await; - - let ca_content = - Certificate::from_pem(std::fs::read_to_string(dir.join("ca.rsa.crt")).unwrap()); - let client_cert = std::fs::read_to_string(dir.join("client.rsa.crt")).unwrap(); - let client_key = std::fs::read_to_string(dir.join("client.rsa.key")).unwrap(); - let client_identity = Identity::from_pem(client_cert, client_key); - - let channel = make_channel( - vec![format!("https://localhost:{}", port)], - Duration::from_secs(0), - Some(TlsSettings { - domain: "localhost".to_string(), - ca_cert: ca_content, - identity: client_identity, - }), - ) - .unwrap(); - - let handle = tokio::spawn(async move { - if let Err(e) = run(global).await { - tracing::error!("grpc failed: {}", e); - Err(e) - } else { - Ok(()) - } - }); - - tokio::time::sleep(Duration::from_millis(500)).await; - - let mut client = pb::health::health_client::HealthClient::new(channel); - - let resp = client - .check(pb::health::HealthCheckRequest::default()) - .await - .unwrap(); - assert_eq!( - resp.into_inner().status, - pb::health::health_check_response::ServingStatus::Serving as i32 - ); - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} - -#[tokio::test] -async fn test_grpc_tls_ec() { - let port = portpicker::pick_unused_port().expect("failed to pick port"); - let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/tests/grpc/certs"); - - let (global, handler) = mock_global_state(AppConfig { - grpc: GrpcConfig { - bind_address: format!("0.0.0.0:{}", port).parse().unwrap(), - tls: Some(TlsConfig { - cert: dir.join("server.ec.crt").to_str().unwrap().to_string(), - ca_cert: dir.join("ca.ec.crt").to_str().unwrap().to_string(), - key: dir.join("server.ec.key").to_str().unwrap().to_string(), - domain: Some("localhost".to_string()), - }), - }, - ..Default::default() - }) - .await; - - let ca_content = Certificate::from_pem(std::fs::read_to_string(dir.join("ca.ec.crt")).unwrap()); - let client_cert = std::fs::read_to_string(dir.join("client.ec.crt")).unwrap(); - let client_key = std::fs::read_to_string(dir.join("client.ec.key")).unwrap(); - let client_identity = Identity::from_pem(client_cert, client_key); - - let channel = make_channel( - vec![format!("https://localhost:{}", port)], - Duration::from_secs(0), - Some(TlsSettings { - domain: "localhost".to_string(), - ca_cert: ca_content, - identity: client_identity, - }), - ) - .unwrap(); - - let handle = tokio::spawn(async move { - if let Err(e) = run(global).await { - tracing::error!("grpc failed: {}", e); - Err(e) - } else { - Ok(()) - } - }); - - tokio::time::sleep(Duration::from_millis(500)).await; - - let mut client = pb::health::health_client::HealthClient::new(channel); - - let resp = client - .check(pb::health::HealthCheckRequest::default()) - .await - .unwrap(); - assert_eq!( - resp.into_inner().status, - pb::health::health_check_response::ServingStatus::Serving as i32 - ); - handler - .cancel() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel context"); - - handle - .timeout(Duration::from_secs(1)) - .await - .expect("failed to cancel grpc") - .expect("grpc failed") - .expect("grpc failed"); -} diff --git a/backend/api/src/tests/mod.rs b/backend/api/src/tests/mod.rs deleted file mode 100644 index 250afb11f..000000000 --- a/backend/api/src/tests/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod api; -mod config; -mod database; -mod dataloader; -mod global; -mod grpc; diff --git a/backend/migrations/20230217024406_Initial_Database_Structure.down.sql b/backend/migrations/20230217024406_Initial_Database_Structure.down.sql deleted file mode 100644 index 61ed3f9af..000000000 --- a/backend/migrations/20230217024406_Initial_Database_Structure.down.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Add down migration script here -DROP TABLE IF EXISTS users CASCADE; -DROP TABLE IF EXISTS sessions CASCADE; -DROP TABLE IF EXISTS global_roles CASCADE; -DROP TABLE IF EXISTS global_role_grants CASCADE; -DROP TABLE IF EXISTS channel_roles CASCADE; -DROP TABLE IF EXISTS channel_role_grants CASCADE; -DROP TABLE IF EXISTS streams CASCADE; -DROP TABLE IF EXISTS stream_bitrate_updates CASCADE; -DROP TABLE IF EXISTS stream_events CASCADE; -DROP TABLE IF EXISTS chat_messages CASCADE; diff --git a/backend/migrations/20230217024406_Initial_Database_Structure.up.sql b/backend/migrations/20230217024406_Initial_Database_Structure.up.sql deleted file mode 100644 index 56e821d30..000000000 --- a/backend/migrations/20230217024406_Initial_Database_Structure.up.sql +++ /dev/null @@ -1,170 +0,0 @@ -CREATE TABLE users ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - username varchar(32) NOT NULL, - display_name varchar(32) NOT NULL, - password_hash varchar(255) NOT NULL, - email varchar(255) NOT NULL, - email_verified boolean NOT NULL DEFAULT FALSE, - - -- Stream state - stream_key varchar(255) NOT NULL, - stream_title varchar(255) NOT NULL DEFAULT '', - stream_description text NOT NULL DEFAULT '', - stream_transcoding_enabled boolean NOT NULL DEFAULT FALSE, - stream_recording_enabled boolean NOT NULL DEFAULT FALSE, - - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW(), - last_login_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE sessions ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - user_id uuid NOT NULL, -- foreign key to users(id) - invalidated_at timestamptz DEFAULT NULL, - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW(), - expires_at timestamptz NOT NULL DEFAULT NOW(), - last_used_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE global_roles ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - name varchar(32) NOT NULL, - description text NOT NULL, - rank int NOT NULL CHECK (rank >= -1), - -- allowed_permissions & denied_permissions = 0 - -- We only need to check one of them - allowed_permissions bigint NOT NULL DEFAULT 0 CHECK (allowed_permissions & denied_permissions = 0), - denied_permissions bigint NOT NULL DEFAULT 0, - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE global_role_grants ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - user_id uuid NOT NULL, -- foreign key to users(id) - global_role_id uuid NOT NULL, -- foreign key to global_roles(id) - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE channel_roles ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - channel_id uuid NOT NULL, -- foreign key to users(id) - name varchar(32) NOT NULL, - description text NOT NULL, - rank int NOT NULL CHECK (rank >= -1), - -- allowed_permissions & denied_permissions = 0 - -- We only need to check one of them - allowed_permissions bigint NOT NULL DEFAULT 0 CHECK (allowed_permissions & denied_permissions = 0), - denied_permissions bigint NOT NULL DEFAULT 0, - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE channel_role_grants ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - user_id uuid NOT NULL, -- foreign key to users(id) - channel_role_id uuid NOT NULL, -- foreign key to channel_roles(id) - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE streams ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - channel_id uuid NOT NULL, -- foreign key to users(id) - title varchar(255) NOT NULL, - description text NOT NULL, - recorded boolean NOT NULL DEFAULT FALSE, - transcoded boolean NOT NULL DEFAULT FALSE, - deleted boolean NOT NULL DEFAULT FALSE, - ready_state int NOT NULL DEFAULT 0, -- 0 = not ready, 1 = ready, 2 = stopped, 3 = stopped resumable, 4 = failed, 5 = was ready - ingest_address varchar(255) NOT NULL, - connection_id uuid NOT NULL, - state bytea, - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW(), - updated_at timestamptz DEFAULT NULL, -- NULL = not started (last bitrate is report) - ended_at timestamptz NOT NULL DEFAULT NOW() + interval '5 minutes' -); - -CREATE TABLE stream_bitrate_updates ( - stream_id uuid NOT NULL, -- foreign key to streams(id) - video_bitrate bigint NOT NULL, - audio_bitrate bigint NOT NULL, - metadata_bitrate bigint NOT NULL, - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE stream_events ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - stream_id uuid NOT NULL, -- foreign key to streams(id) - title varchar(255) NOT NULL, - message text NOT NULL, - level int NOT NULL, -- 0 = info, 1 = warning, 2 = error - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW() -); - -CREATE TABLE chat_messages ( - id uuid PRIMARY KEY DEFAULT gen_random_uuid(), - channel_id uuid NOT NULL, -- foreign key to users(id) - author_id uuid NOT NULL, -- foreign key to users(id) - content text NOT NULL, - -- Timestamps - created_at timestamptz NOT NULL DEFAULT NOW() -); - --- Indexes - -CREATE INDEX users_username_idx ON users (username); - -CREATE INDEX global_roles_user_id_idx ON global_role_grants (user_id); -CREATE INDEX global_roles_global_role_id_idx ON global_role_grants (global_role_id); -CREATE INDEX global_roles_rank_idx ON global_roles (rank); - -CREATE INDEX channel_roles_user_id_idx ON channel_role_grants (user_id); -CREATE INDEX channel_roles_channel_role_id_idx ON channel_role_grants (channel_role_id); -CREATE INDEX channel_roles_rank_idx ON channel_roles (rank); - -CREATE INDEX streams_channel_id_idx ON streams (channel_id); - -CREATE INDEX stream_bitrate_updates_stream_id_idx ON stream_bitrate_updates (stream_id); -CREATE INDEX stream_bitrate_updates_created_at_idx ON stream_bitrate_updates (created_at); - -CREATE INDEX stream_events_stream_id_idx ON stream_events (stream_id); - -CREATE INDEX chat_messages_channel_id_idx ON chat_messages (channel_id); - -CREATE INDEX chat_messages_author_id_idx ON chat_messages (author_id); - --- CONSTRAINTS - -ALTER TABLE IF EXISTS users ADD CONSTRAINT users_username_unique UNIQUE (username); - -ALTER TABLE IF EXISTS global_roles ADD CONSTRAINT global_roles_name_unique UNIQUE (name); -ALTER TABLE IF EXISTS global_roles ADD CONSTRAINT global_roles_rank_unique UNIQUE (rank); - -ALTER TABLE IF EXISTS channel_roles ADD CONSTRAINT channel_roles_name_unique UNIQUE (channel_id, name); -ALTER TABLE IF EXISTS channel_roles ADD CONSTRAINT channel_roles_rank_unique UNIQUE (channel_id, rank); - --- Foreign keys - -ALTER TABLE sessions ADD CONSTRAINT sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; - -ALTER TABLE global_role_grants ADD CONSTRAINT global_role_grants_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; -ALTER TABLE global_role_grants ADD CONSTRAINT global_role_grants_global_role_id_fkey FOREIGN KEY (global_role_id) REFERENCES global_roles(id) ON DELETE CASCADE; - -ALTER TABLE channel_role_grants ADD CONSTRAINT channel_role_grants_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; -ALTER TABLE channel_role_grants ADD CONSTRAINT channel_role_grants_channel_role_id_fkey FOREIGN KEY (channel_role_id) REFERENCES channel_roles(id) ON DELETE CASCADE; - -ALTER TABLE streams ADD CONSTRAINT streams_channel_id_fkey FOREIGN KEY (channel_id) REFERENCES users(id) ON DELETE CASCADE; - -ALTER TABLE stream_bitrate_updates ADD CONSTRAINT stream_bitrate_updates_stream_id_fkey FOREIGN KEY (stream_id) REFERENCES streams(id) ON DELETE CASCADE; - -ALTER TABLE stream_events ADD CONSTRAINT stream_events_stream_id_fkey FOREIGN KEY (stream_id) REFERENCES streams(id) ON DELETE CASCADE; - -ALTER TABLE chat_messages ADD CONSTRAINT chat_messages_channel_id_fkey FOREIGN KEY (channel_id) REFERENCES users(id) ON DELETE CASCADE; -ALTER TABLE chat_messages ADD CONSTRAINT chat_messages_author_id_fkey FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE; diff --git a/binary-helper/APACHE2_LICENSE b/binary-helper/APACHE2_LICENSE new file mode 120000 index 000000000..f7ae1b662 --- /dev/null +++ b/binary-helper/APACHE2_LICENSE @@ -0,0 +1 @@ +../licenses/APACHE2_LICENSE \ No newline at end of file diff --git a/binary-helper/Cargo.toml b/binary-helper/Cargo.toml new file mode 100644 index 000000000..e1aa0d7fd --- /dev/null +++ b/binary-helper/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "binary-helper" +version = "0.0.1" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +tracing = "0.1" +thiserror = "1.0" +tokio = { version = "1.36", features = ["full"] } +serde = { version = "1.0.1", features = ["derive"] } +async-nats = "0.33" +ulid = "1.1" +async-trait = "0.1" +tonic = { version = "0.11", features = ["tls"] } +anyhow = "1.0" +tower-layer = "0.3" +async-stream = "0.3" +futures-util = "0.3" +rustls = "0.22" +rustls-pemfile = "2.0" +fred = { version = "8.0.0", features = ["enable-rustls", "sentinel-client", "dns"] } +tokio-postgres-rustls = "0.11" +tracing-subscriber = { features = ["env-filter", "fmt", "json"], version = "0.3" } +once_cell = "1.19" +aws-config = { version = "1.1" } +aws-sdk-s3 = { version = "1.12", features = ["behavior-version-latest"] } +aws-credential-types = { version = "1.1", features = ["hardcoded-credentials"] } +aws-smithy-types = { version = "1.1", features = ["http-body-1-x"] } +http-body = { version = "1.0.0"} +hyper = "1" +bytes = "1.0" +pin-project = "1" + +tokio-postgres = { version = "0.7" } +postgres-types = { version = "0.2", features = ["with-serde_json-1", "with-chrono-0_4", "derive"] } +deadpool-postgres = { version = "0.12" } +postgres-from-row = { version = "0.5" } +prost = { version = "0.12" } + +config = { workspace = true } +utils = { workspace = true, features = ["all"] } +pb = { workspace = true } diff --git a/binary-helper/MIT_LICENSE b/binary-helper/MIT_LICENSE new file mode 120000 index 000000000..b0845139e --- /dev/null +++ b/binary-helper/MIT_LICENSE @@ -0,0 +1 @@ +../licenses/MIT_LICENSE \ No newline at end of file diff --git a/binary-helper/src/config.rs b/binary-helper/src/config.rs new file mode 100644 index 000000000..b34745d19 --- /dev/null +++ b/binary-helper/src/config.rs @@ -0,0 +1,322 @@ +use std::net::SocketAddr; +use std::sync::Arc; + +use super::Config; +use crate::logging; + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct AppConfig { + /// The name of the application + pub name: String, + + /// The path to the config file + pub config_file: Option, + + /// The logging configuration + pub logging: LoggingConfig, + + /// The gRPC configuration + pub grpc: GrpcConfig, + + /// The database configuration + pub database: DatabaseConfig, + + /// The NATS configuration + pub nats: NatsConfig, + + #[serde(flatten)] + #[config(flatten)] + pub extra: T, +} + +#[derive(Debug, Clone, Default, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct TlsConfig { + /// Domain name to use for TLS + /// Only used for gRPC TLS connections + pub domain: Option, + + /// The path to the TLS certificate + pub cert: String, + + /// The path to the TLS private key + pub key: String, + + /// The path to the TLS CA certificate + pub ca_cert: Option, +} + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct LoggingConfig { + /// The log level to use, this is a tracing env filter + pub level: String, + + /// What logging mode we should use + pub mode: logging::Mode, +} + +impl ::config::Config for logging::Mode { + fn graph() -> Arc<::config::KeyGraph> { + Arc::new(::config::KeyGraph::String) + } +} + +impl Default for LoggingConfig { + fn default() -> Self { + Self { + level: "info".to_string(), + mode: logging::Mode::Default, + } + } +} + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct RedisConfig { + /// The address of the Redis server + pub addresses: Vec, + + /// Number of connections to keep in the pool + pub pool_size: usize, + + /// The username to use for authentication + pub username: Option, + + /// The password to use for authentication + pub password: Option, + + /// The database to use + pub database: u8, + + /// The TLS configuration + pub tls: Option, + + /// To use Redis Sentinel + pub sentinel: Option, +} + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct RedisSentinelConfig { + /// The master group name + pub service_name: String, +} + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct NatsConfig { + /// The URI to use for connecting to Nats + pub servers: Vec, + + /// The username to use for authentication (user-pass auth) + pub username: Option, + + /// The password to use for authentication (user-pass auth) + pub password: Option, + + /// The token to use for authentication (token auth) + pub token: Option, + + /// The TLS configuration (can be used for mTLS) + pub tls: Option, +} + +impl Default for NatsConfig { + fn default() -> Self { + Self { + servers: vec!["localhost:4222".into()], + token: None, + password: None, + tls: None, + username: None, + } + } +} + +impl Default for RedisSentinelConfig { + fn default() -> Self { + Self { + service_name: "myservice".to_string(), + } + } +} + +impl Default for RedisConfig { + fn default() -> Self { + Self { + addresses: vec!["localhost:6379".to_string()], + pool_size: 10, + username: None, + password: None, + database: 0, + tls: None, + sentinel: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +pub struct DatabaseConfig { + /// The database URL to use + pub uri: String, + + /// The TLS configuration + pub tls: Option, +} + +impl Default for DatabaseConfig { + fn default() -> Self { + Self { + uri: "postgres://localhost:5432".to_string(), + tls: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct GrpcConfig { + /// The bind address for the gRPC server + pub bind_address: SocketAddr, + + /// If we should use TLS for the gRPC server + pub tls: Option, +} + +impl Default for GrpcConfig { + fn default() -> Self { + Self { + bind_address: "[::]:50055".to_string().parse().unwrap(), + tls: None, + } + } +} + +#[derive(Debug, Default, Clone, PartialEq, config::Config, serde::Deserialize)] +pub struct S3CredentialsConfig { + /// The access key for the S3 bucket + pub access_key: Option, + + /// The secret key for the S3 bucket + pub secret_key: Option, +} + +#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[serde(default)] +pub struct S3BucketConfig { + /// The name of the S3 bucket + pub name: String, + + /// The region the S3 bucket is in + pub region: String, + + /// The custom endpoint for the S3 bucket + pub endpoint: Option, + + /// The credentials for the S3 bucket + pub credentials: S3CredentialsConfig, +} + +impl Default for S3BucketConfig { + fn default() -> Self { + Self { + name: "scuffle".to_owned(), + region: "us-east-1".to_owned(), + endpoint: Some("http://localhost:9000".to_string()), + credentials: S3CredentialsConfig::default(), + } + } +} + +pub trait ConfigExtention: config::Config + Default { + const APP_NAME: &'static str; + + fn config_default() -> AppConfig { + AppConfig { + name: Self::APP_NAME.to_owned(), + config_file: Some("config".to_owned()), + logging: Default::default(), + grpc: Default::default(), + database: Default::default(), + nats: Default::default(), + extra: Self::default(), + } + } + + fn pre_hook(_config: &mut AppConfig) -> anyhow::Result<()> { + Ok(()) + } +} + +impl Default for AppConfig { + fn default() -> Self { + T::config_default() + } +} + +impl<'de, T: ConfigExtention + serde::Deserialize<'de>> Config for AppConfig { + fn logging(&self) -> &LoggingConfig { + &self.logging + } + + fn parse() -> anyhow::Result + where + Self: Sized, + { + let (mut config, config_file) = parse::(!cfg!(test), Self::default().config_file)?; + + config.config_file = config_file; + + Ok(config) + } + + fn name(&self) -> &str { + &self.name + } + + fn pre_hook(&mut self) -> anyhow::Result<()> { + T::pre_hook(self) + } +} + +pub fn parse<'de, C: config::Config + serde::Deserialize<'de> + 'static>( + enable_cli: bool, + config_file: Option, +) -> config::Result<(C, Option)> { + let mut builder = config::ConfigBuilder::new(); + + if enable_cli { + builder.add_source_with_priority(config::sources::CliSource::new()?, 3); + } + + builder.add_source_with_priority(config::sources::EnvSource::with_prefix("SCUF")?, 2); + + let key = builder.parse_key::>("config_file")?; + + let key_provided = key.is_some(); + + let mut config_path = None; + + if let Some(path) = key.or(config_file) { + match config::sources::FileSource::with_path(path) { + Ok(source) => { + config_path = Some(source.location().to_string()); + builder.add_source_with_priority(source, 1); + } + Err(err) => { + if key_provided || !err.is_io() { + return Err(err); + } + + tracing::debug!("failed to load config file: {}", err); + } + } + } + + Ok(( + builder.build()?, + config_path.map(|p| std::fs::canonicalize(p).unwrap().display().to_string()), + )) +} diff --git a/binary-helper/src/global.rs b/binary-helper/src/global.rs new file mode 100644 index 000000000..ed6a44b87 --- /dev/null +++ b/binary-helper/src/global.rs @@ -0,0 +1,290 @@ +use std::io; +use std::sync::{Arc, Weak}; +use std::time::Duration; + +use anyhow::Context as _; +use async_nats::ServerAddr; +use bytes::Bytes; +use fred::interfaces::ClientLike; +use fred::types::ServerConfig; +use hyper::StatusCode; +use rustls::RootCertStore; +use utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; +use utils::database::tokio_postgres::NoTls; +use utils::database::Pool; +use utils::http::RouteError; + +use crate::config::{DatabaseConfig, NatsConfig, RedisConfig}; + +#[macro_export] +macro_rules! impl_global_traits { + ($struct:ty) => { + impl binary_helper::global::GlobalCtx for $struct { + #[inline(always)] + fn ctx(&self) -> &Context { + &self.ctx + } + } + + impl binary_helper::global::GlobalNats for $struct { + #[inline(always)] + fn nats(&self) -> &async_nats::Client { + &self.nats + } + + #[inline(always)] + fn jetstream(&self) -> &async_nats::jetstream::Context { + &self.jetstream + } + } + + impl binary_helper::global::GlobalDb for $struct { + #[inline(always)] + fn db(&self) -> &Arc { + &self.db + } + } + + impl binary_helper::global::GlobalConfig for $struct {} + }; +} + +pub trait GlobalCtx { + fn ctx(&self) -> &utils::context::Context; +} + +pub trait GlobalConfig { + #[inline(always)] + fn config(&self) -> &C + where + Self: GlobalConfigProvider, + { + GlobalConfigProvider::provide_config(self) + } +} + +pub trait GlobalConfigProvider { + fn provide_config(&self) -> &C; +} + +pub trait GlobalNats { + fn nats(&self) -> &async_nats::Client; + fn jetstream(&self) -> &async_nats::jetstream::Context; +} + +pub trait GlobalDb { + fn db(&self) -> &Arc; +} + +pub trait GlobalRedis { + fn redis(&self) -> &Arc; +} + +pub async fn setup_nats( + name: &str, + config: &NatsConfig, +) -> anyhow::Result<(async_nats::Client, async_nats::jetstream::Context)> { + let nats = { + let mut options = async_nats::ConnectOptions::new() + .connection_timeout(Duration::from_secs(5)) + .name(name) + .retry_on_initial_connect(); + + if let Some(user) = &config.username { + options = options.user_and_password(user.clone(), config.password.clone().unwrap_or_default()) + } else if let Some(token) = &config.token { + options = options.token(token.clone()) + } + + if let Some(tls) = &config.tls { + options = options + .require_tls(true) + .add_client_certificate((&tls.cert).into(), (&tls.key).into()); + + if let Some(ca_cert) = &tls.ca_cert { + options = options.add_root_certificates(ca_cert.into()) + } + } + + options + .connect( + config + .servers + .iter() + .map(|s| s.parse::()) + .collect::, _>>() + .context("failed to parse nats server addresses")?, + ) + .await + .context("failed to connect to nats")? + }; + + let jetstream = async_nats::jetstream::new(nats.clone()); + + Ok((nats, jetstream)) +} + +pub async fn setup_database(config: &DatabaseConfig) -> anyhow::Result> { + let mut pg_config = config + .uri + .parse::() + .context("invalid database uri")?; + + pg_config.ssl_mode(if config.tls.is_some() { + utils::database::tokio_postgres::config::SslMode::Require + } else { + utils::database::tokio_postgres::config::SslMode::Disable + }); + + let manager = if let Some(tls) = &config.tls { + let cert = tokio::fs::read(&tls.cert).await.context("failed to read redis client cert")?; + let key = tokio::fs::read(&tls.key) + .await + .context("failed to read redis client private key")?; + + let key = rustls_pemfile::pkcs8_private_keys(&mut io::BufReader::new(io::Cursor::new(key))) + .next() + .ok_or_else(|| anyhow::anyhow!("failed to find private key in redis client private key file"))?? + .into(); + + let certs = rustls_pemfile::certs(&mut io::BufReader::new(io::Cursor::new(cert))).collect::, _>>()?; + + let mut cert_store = RootCertStore::empty(); + if let Some(ca_cert) = &tls.ca_cert { + let ca_cert = tokio::fs::read(ca_cert).await.context("failed to read redis ca cert")?; + let ca_certs = + rustls_pemfile::certs(&mut io::BufReader::new(io::Cursor::new(ca_cert))).collect::, _>>()?; + for cert in ca_certs { + cert_store.add(cert).context("failed to add redis ca cert")?; + } + } + + let tls = rustls::ClientConfig::builder() + .with_root_certificates(cert_store) + .with_client_auth_cert(certs, key) + .context("failed to create redis tls config")?; + + utils::database::deadpool_postgres::Manager::from_config( + pg_config, + tokio_postgres_rustls::MakeRustlsConnect::new(tls), + ManagerConfig { + recycling_method: RecyclingMethod::Fast, + }, + ) + } else { + utils::database::deadpool_postgres::Manager::from_config( + pg_config, + NoTls, + ManagerConfig { + recycling_method: RecyclingMethod::Fast, + }, + ) + }; + + Ok(Arc::new( + Pool::builder(manager) + .config(PoolConfig::default()) + .runtime(Runtime::Tokio1) + .build() + .context("failed to create database pool")?, + )) +} + +pub async fn setup_redis(config: &RedisConfig) -> anyhow::Result> { + let hosts = config + .addresses + .iter() + .map(|host| { + let mut server = fred::types::Server::try_from(host.as_str()).context("failed to parse redis server address")?; + if let Some(tls) = &config.tls { + server.tls_server_name = tls.domain.as_ref().map(|d| d.into()); + } + + Ok(server) + }) + .collect::>>()?; + + let server = if let Some(sentinel) = &config.sentinel { + ServerConfig::Sentinel { + hosts, + service_name: sentinel.service_name.clone(), + } + } else if hosts.len() == 1 { + ServerConfig::Centralized { + server: hosts.into_iter().next().unwrap(), + } + } else { + ServerConfig::Clustered { hosts } + }; + + let tls = if let Some(tls) = &config.tls { + let cert = tokio::fs::read(&tls.cert).await.context("failed to read redis client cert")?; + let key = tokio::fs::read(&tls.key) + .await + .context("failed to read redis client private key")?; + + let key = rustls_pemfile::pkcs8_private_keys(&mut io::BufReader::new(io::Cursor::new(key))) + .next() + .ok_or_else(|| anyhow::anyhow!("failed to find private key in redis client private key file"))?? + .into(); + + let certs = rustls_pemfile::certs(&mut io::BufReader::new(io::Cursor::new(cert))).collect::, _>>()?; + + let mut cert_store = RootCertStore::empty(); + if let Some(ca_cert) = &tls.ca_cert { + let ca_cert = tokio::fs::read(ca_cert).await.context("failed to read redis ca cert")?; + let ca_certs = + rustls_pemfile::certs(&mut io::BufReader::new(io::Cursor::new(ca_cert))).collect::, _>>()?; + for cert in ca_certs { + cert_store.add(cert).context("failed to add redis ca cert")?; + } + } + + Some(fred::types::TlsConfig::from(fred::types::TlsConnector::from( + rustls::ClientConfig::builder() + .with_root_certificates(cert_store) + .with_client_auth_cert(certs, key) + .context("failed to create redis tls config")?, + ))) + } else { + None + }; + + let redis = Arc::new( + fred::clients::RedisPool::new( + fred::types::RedisConfig { + database: Some(config.database), + password: config.password.clone(), + username: config.username.clone(), + server, + tls, + ..Default::default() + }, + None, + None, + None, + config.pool_size, + ) + .context("failed to create redis pool")?, + ); + + redis.connect(); + redis.wait_for_connect().await.context("failed to connect to redis")?; + + Ok(redis) +} + +pub trait RequestGlobalExt { + fn get_global>(&self) -> std::result::Result, RouteError>; +} + +impl RequestGlobalExt for hyper::Request { + fn get_global>(&self) -> std::result::Result, RouteError> { + Ok(self + .extensions() + .get::>() + .expect("global state not set") + .upgrade() + .ok_or((StatusCode::INTERNAL_SERVER_ERROR, "failed to upgrade global state"))?) + } +} diff --git a/binary-helper/src/grpc_health.rs b/binary-helper/src/grpc_health.rs new file mode 100644 index 000000000..1376a4c0d --- /dev/null +++ b/binary-helper/src/grpc_health.rs @@ -0,0 +1,94 @@ +use std::pin::Pin; +use std::sync::{Arc, Weak}; + +use async_stream::try_stream; +use futures_util::{Future, Stream}; +use pb::grpc::health::v1::health_check_response::ServingStatus; +use pb::grpc::health::v1::{health_server, HealthCheckRequest, HealthCheckResponse}; +use tonic::{async_trait, Request, Response, Status}; + +pub struct HealthServer< + G, + F: Future + Send + Sync + 'static, + H: Fn(Arc, HealthCheckRequest) -> F + Send + Sync + 'static, +> { + global: Weak, + health_check: Arc, +} + +impl< + G: Send + Sync + 'static, + F: Future + Send + Sync + 'static, + H: Fn(Arc, HealthCheckRequest) -> F + Send + Sync + 'static, +> HealthServer +{ + pub fn new(global: &Arc, health_check: H) -> health_server::HealthServer { + health_server::HealthServer::new(Self { + global: Arc::downgrade(global), + health_check: Arc::new(health_check), + }) + } +} + +type Result = std::result::Result; + +#[async_trait] +impl< + G: Send + Sync + 'static, + F: Future + Send + Sync + 'static, + H: Fn(Arc, HealthCheckRequest) -> F + Send + Sync + 'static, +> health_server::Health for HealthServer +{ + type WatchStream = Pin> + Send>>; + + async fn check(&self, req: Request) -> Result> { + let global = self + .global + .upgrade() + .ok_or_else(|| Status::internal("global state dropped"))?; + + let serving = (self.health_check)(global, req.into_inner()).await; + + Ok(Response::new(HealthCheckResponse { + status: if serving { + ServingStatus::Serving.into() + } else { + ServingStatus::NotServing.into() + }, + })) + } + + async fn watch(&self, req: Request) -> Result> { + let global = self.global.clone(); + let health_check = self.health_check.clone(); + let req = req.into_inner(); + + let output = try_stream!({ + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + let global = match global.upgrade() { + Some(global) => global, + None => { + yield HealthCheckResponse { + status: ServingStatus::NotServing.into(), + }; + return; + } + }; + + let serving = (health_check)(global, req.clone()).await; + + yield HealthCheckResponse { + status: if serving { + ServingStatus::Serving.into() + } else { + ServingStatus::NotServing.into() + }, + }; + } + }); + + Ok(Response::new(Box::pin(output))) + } +} diff --git a/binary-helper/src/lib.rs b/binary-helper/src/lib.rs new file mode 100644 index 000000000..53b06324e --- /dev/null +++ b/binary-helper/src/lib.rs @@ -0,0 +1,91 @@ +use std::future::Future; +use std::sync::Arc; +use std::time::Duration; + +use anyhow::Context as _; +use tokio::signal::unix::SignalKind; +use tokio::{select, time}; +use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; +pub use traits::{Config, Global}; +use utils::context::Context; +use utils::signal; + +use self::config::GrpcConfig; + +pub mod config; +pub mod global; +pub mod grpc_health; +pub mod logging; +pub mod s3; +pub mod traits; + +pub async fn bootstrap, F: Future> + Send + 'static>( + process: impl FnOnce(Arc) -> F, +) -> anyhow::Result<()> { + let (ctx, handler) = Context::new(); + + let config = C::parse() + .and_then(|mut config| { + config.pre_hook()?; + Ok(config) + }) + .map_err(|err| { + logging::init("trace", Default::default()).expect("failed to init logging"); + + err + }) + .context("failed to parse config")?; + + logging::init(&config.logging().level, config.logging().mode).expect("failed to init logging"); + + tracing::info!(name = config.name(), "starting up"); + + let global = Arc::new(G::new(ctx, config).await.context("failed to create global state")?); + + tracing::debug!("global state created, starting process"); + + let process_future = tokio::spawn(process(global)); + + let mut signal_handler = signal::SignalHandler::new() + .with_signal(SignalKind::interrupt()) + .with_signal(SignalKind::terminate()); + + select! { + _ = signal_handler.recv() => tracing::info!("shutting down"), + r = process_future => tracing::error!("process stopped unexpectedly: {:#}", match &r { + Ok(Ok(())) => &"no error raised" as &dyn std::fmt::Display, + Err(err) => err as &dyn std::fmt::Display, + Ok(Err(err)) => err as &dyn std::fmt::Display, + }), + } + + tracing::info!("waiting for tasks to finish"); + + select! { + _ = time::sleep(Duration::from_secs(60)) => tracing::warn!("force shutting down"), + _ = signal_handler.recv() => tracing::warn!("force shutting down"), + _ = handler.cancel() => tracing::info!("shutting down"), + } + + Ok(()) +} + +pub async fn grpc_server(config: &GrpcConfig) -> anyhow::Result { + Ok(if let Some(tls) = &config.tls { + let key = tokio::fs::read(&tls.key).await.context("failed to read grpc private key")?; + let cert = tokio::fs::read(&tls.cert).await.context("failed to read grpc cert")?; + + let tls_config = ServerTlsConfig::new().identity(Identity::from_pem(cert, key)); + + let tls_config = if let Some(ca_cert) = &tls.ca_cert { + let ca_cert = tokio::fs::read(ca_cert).await.context("failed to read grpc ca cert")?; + tls_config.client_ca_root(Certificate::from_pem(ca_cert)) + } else { + tls_config + }; + + Server::builder().tls_config(tls_config)? + } else { + Server::builder() + }) +} diff --git a/binary-helper/src/logging.rs b/binary-helper/src/logging.rs new file mode 100644 index 000000000..aeee79b23 --- /dev/null +++ b/binary-helper/src/logging.rs @@ -0,0 +1,113 @@ +use std::str::FromStr; + +use once_cell::sync::OnceCell; +use tracing_subscriber::prelude::*; +use tracing_subscriber::EnvFilter; + +type ReloadHandle = Box Result<(), LoggingError> + Sync + Send>; + +static RELOAD_HANDLE: OnceCell = OnceCell::new(); + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum Mode { + #[default] + Default, + Json, + Pretty, + Compact, +} + +#[derive(Debug, thiserror::Error)] +pub enum LoggingError { + #[error("invalid logging mode: {0}")] + InvalidMode(#[from] tracing_subscriber::filter::ParseError), + #[error("failed to init logger: {0}")] + Init(#[from] tracing_subscriber::util::TryInitError), + #[error("failed to reload logger: {0}")] + Reload(#[from] tracing_subscriber::reload::Error), +} + +pub fn init(level: &str, mode: Mode) -> Result<(), LoggingError> { + let reload = RELOAD_HANDLE.get_or_try_init(|| { + let env_filter = EnvFilter::from_str(level)?; + + match mode { + Mode::Default => { + let filter = tracing_subscriber::fmt() + .with_line_number(true) + .with_file(true) + .with_env_filter(env_filter) + .with_filter_reloading(); + + let handle = filter.reload_handle(); + + filter.finish().try_init()?; + + Ok::<_, LoggingError>(Box::new(move |level: &str| { + let level = EnvFilter::from_str(level)?; + handle.reload(level)?; + Ok(()) + }) as ReloadHandle) + } + Mode::Json => { + let filter = tracing_subscriber::fmt() + .json() + .with_line_number(true) + .with_file(true) + .with_env_filter(env_filter) + .with_filter_reloading(); + + let handle = filter.reload_handle(); + + filter.finish().try_init()?; + + Ok(Box::new(move |level: &str| { + let level = EnvFilter::from_str(level)?; + handle.reload(level)?; + Ok(()) + }) as ReloadHandle) + } + Mode::Pretty => { + let filter = tracing_subscriber::fmt() + .pretty() + .with_line_number(true) + .with_file(true) + .with_env_filter(env_filter) + .with_filter_reloading(); + + let handle = filter.reload_handle(); + + filter.finish().try_init()?; + + Ok(Box::new(move |level: &str| { + let level = EnvFilter::from_str(level)?; + handle.reload(level)?; + Ok(()) + }) as ReloadHandle) + } + Mode::Compact => { + let filter = tracing_subscriber::fmt() + .compact() + .with_line_number(true) + .with_file(true) + .with_env_filter(env_filter) + .with_filter_reloading(); + + let handle = filter.reload_handle(); + + filter.finish().try_init()?; + + Ok(Box::new(move |level: &str| { + let level = EnvFilter::from_str(level)?; + handle.reload(level)?; + Ok(()) + }) as ReloadHandle) + } + } + })?; + + reload(level)?; + + Ok(()) +} diff --git a/binary-helper/src/s3.rs b/binary-helper/src/s3.rs new file mode 100644 index 000000000..71936e4d4 --- /dev/null +++ b/binary-helper/src/s3.rs @@ -0,0 +1,139 @@ +use std::task::Poll; + +use aws_config::Region; +use aws_credential_types::Credentials; +use aws_sdk_s3::error::SdkError; +use aws_sdk_s3::operation::delete_object::DeleteObjectError; +use aws_sdk_s3::operation::get_object::{GetObjectError, GetObjectOutput}; +use aws_sdk_s3::operation::put_object::PutObjectError; +use aws_sdk_s3::primitives::ByteStream; +use aws_sdk_s3::types::ObjectCannedAcl; +use bytes::Bytes; + +use crate::config::{S3BucketConfig, S3CredentialsConfig}; + +#[derive(Debug, Clone)] +pub struct Bucket { + name: String, + client: aws_sdk_s3::Client, +} + +#[derive(Debug, Clone, Default)] +pub struct PutObjectOptions { + pub acl: Option, + pub content_type: Option, +} + +impl From for Credentials { + fn from(value: S3CredentialsConfig) -> Self { + Self::from_keys( + value.access_key.unwrap_or_default(), + value.secret_key.unwrap_or_default(), + None, + ) + } +} + +impl S3BucketConfig { + pub fn setup(&self) -> Bucket { + Bucket::new( + self.name.clone(), + self.credentials.clone().into(), + Region::new(self.region.clone()), + self.endpoint.clone(), + ) + } +} + +#[pin_project::pin_project] +pub struct AsyncStreamBody(#[pin] pub T); + +impl http_body::Body for AsyncStreamBody +where + T: futures_util::stream::TryStream + Send + Sync + 'static, + T::Ok: Into + Send + Sync + 'static, + T::Error: Into + Send + Sync + 'static, +{ + type Data = Bytes; + type Error = aws_smithy_types::body::Error; + + fn poll_frame( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll, Self::Error>>> { + let this = self.project(); + + match this.0.try_poll_next(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(http_body::Frame::data(bytes.into())))), + Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e.into()))), + Poll::Ready(None) => Poll::Ready(None), + } + } +} + +impl From> for ByteStream +where + T: futures_util::stream::TryStream + Send + Sync + 'static, + T::Ok: Into + Send + Sync + 'static, + T::Error: Into + Send + Sync + 'static, +{ + fn from(value: AsyncStreamBody) -> Self { + ByteStream::from_body_1_x(value) + } +} + +impl Bucket { + pub fn new(name: String, credentials: Credentials, region: Region, endpoint: Option) -> Self { + let config = if let Some(endpoint) = endpoint { + aws_sdk_s3::config::Builder::new().endpoint_url(endpoint) + } else { + aws_sdk_s3::config::Builder::new() + } + .region(region) + .credentials_provider(credentials) + .force_path_style(true) + .build(); + + let client = aws_sdk_s3::Client::from_conf(config); + + Self { name, client } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub async fn get_object(&self, key: &str) -> Result> { + let resp = self.client.get_object().bucket(self.name()).key(key).send().await?; + + Ok(resp) + } + + pub async fn put_object( + &self, + key: impl Into, + body: impl Into, + options: Option, + ) -> Result<(), SdkError> { + let options = options.unwrap_or_default(); + + self.client + .put_object() + .bucket(self.name()) + .key(key) + .body(body.into()) + .set_acl(options.acl) + .set_content_type(options.content_type) + .send() + .await?; + + Ok(()) + } + + pub async fn delete_object(&self, key: &str) -> Result<(), SdkError> { + self.client.delete_object().bucket(self.name()).key(key).send().await?; + + Ok(()) + } +} diff --git a/binary-helper/src/traits.rs b/binary-helper/src/traits.rs new file mode 100644 index 000000000..f5143c8c1 --- /dev/null +++ b/binary-helper/src/traits.rs @@ -0,0 +1,22 @@ +use utils::context::Context; + +pub trait Config { + fn parse() -> anyhow::Result + where + Self: Sized; + + fn logging(&self) -> &crate::config::LoggingConfig; + + fn name(&self) -> &str; + + fn pre_hook(&mut self) -> anyhow::Result<()> { + Ok(()) + } +} + +#[allow(async_fn_in_trait)] +pub trait Global { + async fn new(ctx: Context, config: C) -> anyhow::Result + where + Self: Sized; +} diff --git a/common/Cargo.toml b/common/Cargo.toml deleted file mode 100644 index cbaf3cf74..000000000 --- a/common/Cargo.toml +++ /dev/null @@ -1,48 +0,0 @@ -[package] -name = "common" -version = "0.1.0" -edition = "2021" -authors = ["Scuffle "] -description = "Scuffle Common Library" - -[features] -logging = ["dep:log", "dep:tracing", "dep:tracing-log", "dep:tracing-subscriber", "dep:arc-swap", "dep:anyhow", "dep:once_cell", "dep:thiserror", "dep:serde"] -rmq = ["dep:lapin", "dep:arc-swap", "dep:anyhow", "dep:futures", "dep:tracing", "dep:tokio", "dep:async-stream", "prelude"] -grpc = ["dep:tonic", "dep:anyhow", "dep:async-trait", "dep:futures", "dep:http", "dep:tower", "dep:trust-dns-resolver", "dep:tracing"] -context = ["dep:tokio", "dep:tokio-util"] -prelude = ["dep:tokio"] -signal = [] -macros = [] -config = ["dep:config", "dep:serde", "logging"] - -default = ["logging", "rmq", "grpc", "context", "prelude", "signal", "macros", "config"] - -[dependencies] -log = { version = "0", optional = true } -http = { version = "0", optional = true } -tower = { version = "0", optional = true } -config = { path = "../config/config", optional = true } -anyhow = { version = "1", optional = true } -futures = { version = "0", optional = true } -tracing = { version = "0", optional = true } -arc-swap = { version = "1", optional = true } -tokio-util = { version = "0", optional = true } -async-trait = { version = "0", optional = true } -async-stream = { version = "0", optional = true } -tonic = { version = "0", features = ["tls"], optional = true } -tokio = { version = "1", features = ["sync", "rt"], optional = true } -serde = { version = "1", features = ["derive"], optional = true } -lapin = { version = "2.0.3", features = ["native-tls"], optional = true } -tracing-log = { version = "0", features = ["env_logger"], optional = true } -once_cell = { version = "1", optional = true } -trust-dns-resolver = { version = "0", features = ["tokio-runtime"], optional = true } -tracing-subscriber = { version = "0", features = ["fmt", "env-filter", "json"], optional = true } -thiserror = { version = "1", optional = true } - -[dev-dependencies] -prost = "0" -tempfile = "3" -portpicker = "0" - -[build-dependencies] -tonic-build = "0" diff --git a/common/LICENSE.md b/common/LICENSE.md deleted file mode 120000 index 7eabdb1c2..000000000 --- a/common/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.md \ No newline at end of file diff --git a/common/build.rs b/common/build.rs deleted file mode 100644 index a091e9483..000000000 --- a/common/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tonic_build::compile_protos("proto/test.proto").unwrap(); -} diff --git a/common/src/config.rs b/common/src/config.rs deleted file mode 100644 index f2ba9033c..000000000 --- a/common/src/config.rs +++ /dev/null @@ -1,156 +0,0 @@ -use std::sync::Arc; - -use anyhow::Result; - -use crate::logging; - -#[derive(Debug, Clone, Default, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct TlsConfig { - /// Domain name to use for TLS - /// Only used for gRPC TLS connections - pub domain: Option, - - /// The path to the TLS certificate - pub cert: String, - - /// The path to the TLS private key - pub key: String, - - /// The path to the TLS CA certificate - pub ca_cert: String, -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct LoggingConfig { - /// The log level to use, this is a tracing env filter - pub level: String, - - /// What logging mode we should use - pub mode: logging::Mode, -} - -impl ::config::Config for logging::Mode { - fn graph() -> Arc<::config::KeyGraph> { - Arc::new(::config::KeyGraph::String) - } -} - -impl Default for LoggingConfig { - fn default() -> Self { - Self { - level: "info".to_string(), - mode: logging::Mode::Default, - } - } -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct RedisConfig { - /// The address of the Redis server - pub addresses: Vec, - - /// Number of connections to keep in the pool - pub pool_size: usize, - - /// The username to use for authentication - pub username: Option, - - /// The password to use for authentication - pub password: Option, - - /// The database to use - pub database: u8, - - /// The TLS configuration - pub tls: Option, - - /// To use Redis Sentinel - pub sentinel: Option, -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct RedisSentinelConfig { - /// The master group name - pub service_name: String, -} - -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] -#[serde(default)] -pub struct RmqConfig { - /// The URI to use for connecting to RabbitMQ - pub uri: String, -} - -impl Default for RmqConfig { - fn default() -> Self { - Self { - uri: "amqp://rabbitmq:rabbitmq@localhost:5672/scuffle".to_string(), - } - } -} - -impl Default for RedisSentinelConfig { - fn default() -> Self { - Self { - service_name: "myservice".to_string(), - } - } -} - -impl Default for RedisConfig { - fn default() -> Self { - Self { - addresses: vec!["localhost:6379".to_string()], - pool_size: 10, - username: None, - password: None, - database: 0, - tls: None, - sentinel: None, - } - } -} - -pub fn parse( - enable_cli: bool, - config_file: Option, -) -> Result<(C, Option)> { - let mut builder = config::ConfigBuilder::new(); - - if enable_cli { - builder.add_source_with_priority(config::sources::CliSource::new()?, 3); - } - - builder.add_source_with_priority(config::sources::EnvSource::with_prefix("SCUF")?, 2); - - let key = builder.parse_key::>("config_file")?; - - let key_provided = key.is_some(); - - let mut config_path = None; - - if let Some(path) = key.or(config_file) { - match config::sources::FileSource::with_path(path) { - Ok(source) => { - config_path = Some(source.location().to_string()); - builder.add_source_with_priority(source, 1); - } - Err(err) => { - if key_provided || !err.is_io() { - return Err(err.into()); - } - - tracing::debug!("failed to load config file: {}", err); - } - } - } - - Ok(( - builder.build()?, - config_path.map(|p| std::fs::canonicalize(p).unwrap().display().to_string()), - )) -} diff --git a/common/src/context.rs b/common/src/context.rs deleted file mode 100644 index 59aaddf87..000000000 --- a/common/src/context.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - future::Future, - pin::Pin, - sync::{Arc, Weak}, -}; - -use tokio::{sync::oneshot, time::Instant}; -use tokio_util::sync::{CancellationToken, DropGuard}; - -struct RawContext { - _sender: oneshot::Sender<()>, - _weak: Weak<()>, - deadline: Option, - parent: Option, - cancel_receiver: CancellationToken, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum CancelReason { - Parent, - Deadline, - Cancel, -} - -impl Display for CancelReason { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Parent => write!(f, "Parent"), - Self::Deadline => write!(f, "Deadline"), - Self::Cancel => write!(f, "Cancel"), - } - } -} - -impl RawContext { - #[must_use] - fn new() -> (Self, Handler) { - let (sender, recv) = oneshot::channel(); - let strong = Arc::new(()); - let token = CancellationToken::new(); - let child = token.child_token(); - - ( - Self { - deadline: None, - parent: None, - cancel_receiver: child, - _sender: sender, - _weak: Arc::downgrade(&strong), - }, - Handler { - recv, - _token: token.drop_guard(), - _strong: strong, - }, - ) - } - - #[must_use] - fn with_deadline(deadline: Instant) -> (Self, Handler) { - let (mut ctx, handler) = Self::new(); - ctx.deadline = Some(deadline); - (ctx, handler) - } - - #[must_use] - fn with_parent(parent: Context, deadline: Option) -> (Self, Handler) { - let (mut ctx, handler) = Self::new(); - ctx.parent = Some(parent); - ctx.deadline = deadline; - (ctx, handler) - } - - fn done(&self) -> Pin + '_ + Send + Sync>> { - Box::pin(async move { - match (&self.parent, self.deadline) { - (Some(parent), Some(deadline)) => { - tokio::select! { - _ = parent.done() => CancelReason::Parent, - _ = tokio::time::sleep_until(deadline) => CancelReason::Deadline, - _ = self.cancel_receiver.cancelled() => CancelReason::Cancel, - } - } - (Some(parent), None) => { - tokio::select! { - _ = parent.done() => CancelReason::Parent, - _ = self.cancel_receiver.cancelled() => CancelReason::Cancel, - } - } - (None, Some(deadline)) => { - tokio::select! { - _ = tokio::time::sleep_until(deadline) => CancelReason::Deadline, - _ = self.cancel_receiver.cancelled() => CancelReason::Cancel, - } - } - (None, None) => { - self.cancel_receiver.cancelled().await; - CancelReason::Cancel - } - } - }) - } - - fn is_done(&self) -> bool { - self._weak.upgrade().is_none() - } -} - -pub struct Handler { - _strong: Arc<()>, - _token: DropGuard, - - recv: oneshot::Receiver<()>, -} - -impl Handler { - pub async fn done(&mut self) { - let _ = (&mut self.recv).await; - } - - pub fn cancel(self) -> Pin + Send + Sync>> { - let recv = self.recv; - Box::pin(async move { - let _ = recv.await; - }) - } -} - -#[derive(Clone)] -pub struct Context(Arc); - -impl From for Context { - fn from(ctx: RawContext) -> Self { - Self(Arc::new(ctx)) - } -} - -impl Context { - pub fn new() -> (Self, Handler) { - let (ctx, handler) = RawContext::new(); - (ctx.into(), handler) - } - - pub fn with_deadline(deadline: Instant) -> (Self, Handler) { - let (ctx, handler) = RawContext::with_deadline(deadline); - (ctx.into(), handler) - } - - pub fn with_timeout(timeout: std::time::Duration) -> (Self, Handler) { - let deadline = Instant::now() + timeout; - Self::with_deadline(deadline) - } - - pub fn with_parent(parent: Context, deadline: Option) -> (Self, Handler) { - let (ctx, handler) = RawContext::with_parent(parent, deadline); - (ctx.into(), handler) - } - - pub fn done(&self) -> Pin + '_ + Send + Sync>> { - self.0.done() - } - - pub fn is_done(&self) -> bool { - self.0.is_done() - } -} diff --git a/common/src/grpc.rs b/common/src/grpc.rs deleted file mode 100644 index 4cc6e5a9c..000000000 --- a/common/src/grpc.rs +++ /dev/null @@ -1,386 +0,0 @@ -use std::{collections::HashSet, fmt, net::SocketAddr, time::Duration}; - -use anyhow::Result; -use async_trait::async_trait; -use futures::future; -use http::Uri; -use tokio::sync::mpsc::Sender; -use tonic::transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity}; -use tower::discover::Change; -use trust_dns_resolver::{ - error::ResolveError, - lookup::Lookup, - proto::rr::{RData, RecordType}, - TokioAsyncResolver, -}; - -#[derive(Clone, Debug)] -/// Options for creating a gRPC channel. -pub struct ChannelOpts { - /// A list of addresses to connect to. If this is empty, will return an error. - /// Can be a hostname or an IP address. - pub addresses: Vec, - /// If true, will try to resolve CNAME records for the hostname. - /// Useful for headless services. If false, will only resolve A/AAAA records. - pub try_cname: bool, - /// If true, will try to resolve IPv6 addresses. - pub enable_ipv6: bool, - /// If true, will try to resolve IPv4 addresses. - pub enable_ipv4: bool, - /// How often to re-resolve the hostnames. If this is 0, will only resolve once. - pub interval: Duration, - /// TLS settings. Is None if TLS is disabled. If this is Some, will use TLS. - pub tls: Option, -} - -#[derive(Clone, Debug)] -pub struct TlsSettings { - /// The domain on the certificate. - pub domain: String, - /// The client certificate. - pub identity: Identity, - /// The CA certificate to verify the server. - pub ca_cert: Certificate, -} - -/// Internal struct for controlling the channel. -/// Automatically resolves hostnames and handles DNS changes. -struct ChannelController { - last_addresses: HashSet, - resolver: R, - try_cname: bool, - enable_ipv6: bool, - enable_ipv4: bool, - sender: Sender>, - interval: Duration, - hostnames: HashSet<(String, u16)>, - static_ips: HashSet, - tls: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -/// A wrapper around SocketAddr and CNAME records. -/// Hashable to be used in a HashSet. -enum EndpointType { - Ip(SocketAddr), - CName(String, u16), -} - -impl fmt::Display for EndpointType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Ip(addr) => write!(f, "{}", addr), - Self::CName(name, port) => write!(f, "{}:{}", name, port), - } - } -} - -#[async_trait] -pub trait DnsResolver: Send + Sync + 'static { - async fn lookup(&self, hostname: &str, record_type: RecordType) - -> Result; -} - -#[async_trait] -impl DnsResolver for TokioAsyncResolver { - #[inline(always)] - async fn lookup( - &self, - hostname: &str, - record_type: RecordType, - ) -> Result { - self.lookup(hostname, record_type).await - } -} - -struct ChannelControllerOpts { - sender: Sender>, - interval: Duration, - hostnames: HashSet<(String, u16)>, - static_ips: HashSet, - tls: Option, - try_cname: bool, - enable_ipv6: bool, - enable_ipv4: bool, -} - -impl ChannelController { - fn new(resolver: R, opts: ChannelControllerOpts) -> Result { - Ok(Self { - last_addresses: HashSet::new(), - resolver, - sender: opts.sender, - interval: opts.interval, - hostnames: opts.hostnames, - static_ips: opts.static_ips, - try_cname: opts.try_cname, - enable_ipv6: opts.enable_ipv6, - enable_ipv4: opts.enable_ipv4, - tls: opts.tls, - }) - } - - /// Starts the controller - pub async fn start(mut self) { - // We start by running the first lookup. - while self.run().await { - // if the interval is 0, we only run once. - if self.interval == Duration::from_secs(0) { - break; - } - - tokio::time::sleep(self.interval).await; - } - } - - /// Runs a single lookup. - async fn run(&mut self) -> bool { - let mut addresses = self - .static_ips - .clone() - .into_iter() - .map(EndpointType::Ip) - .collect::>(); - - let futures = self.hostnames.iter().map(|(hostname, port)| { - let resolver = &self.resolver; - let try_cname = self.try_cname; - let port = *port; - - let enable_ipv4 = self.enable_ipv4; - let enable_ipv6 = self.enable_ipv6; - - // This needs to be a move, because we need the port and hostname. - async move { - if try_cname { - let cname = resolver.lookup(hostname, RecordType::CNAME).await; - if let Ok(cname) = cname { - return Ok((cname, port)); - } - } - - if enable_ipv4 { - let lookup = resolver.lookup(hostname, RecordType::A).await; - if let Ok(lookup) = lookup { - return Ok((lookup, port)); - } - } - - if enable_ipv6 { - let lookup = resolver.lookup(hostname, RecordType::AAAA).await; - if let Ok(lookup) = lookup { - return Ok((lookup, port)); - } - } - - Err(anyhow::anyhow!("Failed to resolve hostname: {}", hostname)) - } - }); - - future::join_all(futures) - .await - .into_iter() - .for_each(|result| match result { - // If the lookup was successful, we add all the addresses to the total list. - Ok((lookup, port)) => { - lookup - .into_iter() - // We convert the IpAddr to a SocketAddr, so we can add it to the HashSet. - // Since we are using a filter_map here, we can also filter out any records that we don't care about. - .filter_map(move |record| { - match record { - // If we get an A record back, we convert it to an SocketAddr with the port and then into a EndpointType::Ip. - RData::A(a) => { - Some(EndpointType::Ip(SocketAddr::new(a.into(), port))) - } - // If we get an AAAA record back, we convert it to an SocketAddr with the port and then into a EndpointType::Ip. - RData::AAAA(aaaa) => { - Some(EndpointType::Ip(SocketAddr::new(aaaa.into(), port))) - } - // If we get a CNAME record back, we convert it to an EndpointType::CName with the port. - RData::CNAME(cname) => { - Some(EndpointType::CName(cname.to_string(), port)) - } - // This should never happen, but we have to handle it. We just ignore it. - _ => None, - } - }) - // Now for all the records we got back, we add them to the HashSet. - .for_each(|endpoint| { - // This is a HashSet, so we don't have to worry about duplicates. - addresses.insert(endpoint); - }); - } - Err(e) => { - // If the lookup failed, we log the error. - tracing::debug!("failed to lookup address: {}", e); - } - }); - - // Now we check if there are any new addresses. - let added = addresses - .difference(&self.last_addresses) - // If we have a new address, we need to construct a Change to add it to the channel. - .filter_map(|addr| { - // First we need to make a Endpoint from the EndpointType. - let endpoint = if self.tls.is_some() { - Endpoint::from_shared(format!("https://{}", addr)) - } else { - Endpoint::from_shared(format!("http://{}", addr)) - }; - - // If we failed to make a Endpoint, we log the error and return None. - let endpoint = match endpoint { - Ok(endpoint) => endpoint, - Err(e) => { - tracing::warn!("invalid address: {}, {}", addr, e); - return None; - } - }; - - // If TLS is enabled, we need to add the TLS config to the Endpoint. - let endpoint = if self.tls.is_some() { - let tls = self.tls.as_ref().unwrap(); - let tls = ClientTlsConfig::new() - .domain_name(tls.domain.clone()) - .ca_certificate(tls.ca_cert.clone()) - .identity(tls.identity.clone()); - - match endpoint.tls_config(tls) { - Ok(endpoint) => endpoint, - Err(e) => { - tracing::warn!("invalid tls config: {}: {}", addr, e); - return None; - } - } - } else { - endpoint - }; - - // We now construct the Change and return it. - Some(Change::Insert(addr.clone(), endpoint)) - }); - - // Now we check if there are any addresses that have been removed. - let removed = self - .last_addresses - .difference(&addresses) - // We construct a Change to remove the address from the channel. - .map(|addr| Change::Remove(addr.clone())); - - // We combine the 2 streams into one. - let changes = added.chain(removed); - - // Now we send all the changes to the channel. - for change in changes { - // If this fails, it means the receiver has been dropped, so we can stop the loop. - if self.sender.send(change).await.is_err() { - tracing::debug!("channel controller stopped"); - return false; - } - } - - // We then update the last_addresses HashSet with the new addresses. - self.last_addresses = addresses; - - // We return true, so the loop will continue. - true - } -} - -/// Make a new gRPC transport channel which is backed by a DNS resolver. -/// This will create a new channel which will automatically update the endpoints -/// when the DNS records change. Allowing for a more dynamic way of connecting -/// to services. -#[inline(always)] -pub fn make_channel( - addresses: Vec, - interval: Duration, - tls: Option, -) -> Result { - make_channel_with_opts(ChannelOpts { - addresses, - tls, - interval, - enable_ipv4: true, - enable_ipv6: true, - try_cname: true, - }) -} - -/// Make a new gRPC transport channel which is backed by a DNS resolver. -/// This will create a new channel which will automatically update the endpoints -/// when the DNS records change. Allowing for a more dynamic way of connecting -/// to services. This funtion allows you to provide your own options. -#[inline(always)] -pub fn make_channel_with_opts(opts: ChannelOpts) -> Result { - make_channel_with_resolver(TokioAsyncResolver::tokio_from_system_conf()?, opts) -} - -/// Make a new gRPC transport channel which is backed by a DNS resolver. -/// This will create a new channel which will automatically update the endpoints -/// when the DNS records change. Allowing for a more dynamic way of connecting -/// to services. This function allows you to provide your own DNS resolver. -/// This is useful if you want to use a different DNS resolver, or if you want -/// to unit test this function. -pub fn make_channel_with_resolver( - resolver: R, - opts: ChannelOpts, -) -> Result { - // We first check if any addresses were provided. - if opts.addresses.is_empty() { - return Err(anyhow::anyhow!("no addresses provided")); - } - - // 128 is an arbitrary number, but it should be enough for most use cases. - let (channel, sender) = Channel::balance_channel(128); - - let mut static_ips = HashSet::new(); - let mut hostnames = HashSet::new(); - - // We iterate over the provided addresses and parse them into a Uri. - // So we can check if the address is a hostname or an IP address. - for address in opts.addresses { - let uri = address.parse::()?; - - // Get the port from the Uri, or use the default port. - let port = uri - .port_u16() - .unwrap_or(if opts.tls.is_some() { 443 } else { 80 }); - - // Get the host from the Uri, or return an error if it doesn't exist. - let Some(address) = uri.host() else { - return Err(anyhow::anyhow!("invalid address: {}", address)); - }; - - // If the address is an IP address, we add it to the ip_addresses HashSet. - if let Ok(addr) = address.parse::() { - static_ips.insert(SocketAddr::new(addr, port)); - } else { - hostnames.insert((address.to_string(), port)); - } - } - - // We now create a new ChannelController - // The channel controller will handle the DNS lookups and updating the channel. - let controller = ChannelController::new( - resolver, - ChannelControllerOpts { - sender, - interval: opts.interval, - hostnames, - static_ips, - tls: opts.tls, - try_cname: opts.try_cname, - enable_ipv6: opts.enable_ipv6, - enable_ipv4: opts.enable_ipv4, - }, - )?; - - // We spawn the controller on a new task. - tokio::spawn(controller.start()); - - // We return the channel. - // The channel will be updated by the controller. - Ok(channel) -} diff --git a/common/src/lib.rs b/common/src/lib.rs deleted file mode 100644 index 4b32a44a3..000000000 --- a/common/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -#![forbid(unsafe_code)] - -#[cfg(feature = "config")] -pub mod config; -#[cfg(feature = "context")] -pub mod context; -#[cfg(feature = "grpc")] -pub mod grpc; -#[cfg(feature = "logging")] -pub mod logging; -#[cfg(feature = "prelude")] -pub mod prelude; -#[cfg(feature = "rmq")] -pub mod rmq; -#[cfg(feature = "signal")] -pub mod signal; - -#[cfg(feature = "macros")] -#[macro_use] -pub mod macros; - -#[cfg(test)] -mod tests; diff --git a/common/src/logging.rs b/common/src/logging.rs deleted file mode 100644 index 965f88f65..000000000 --- a/common/src/logging.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::str::FromStr; - -use once_cell::sync::OnceCell; -use tracing_subscriber::{prelude::*, reload::Handle, EnvFilter}; - -static RELOAD_HANDLE: OnceCell> = OnceCell::new(); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)] -#[serde(rename_all = "lowercase")] -pub enum Mode { - Default, - Json, - Pretty, - Compact, -} - -#[derive(Debug, thiserror::Error)] -pub enum LoggingError { - #[error("invalid logging mode: {0}")] - InvalidMode(String), - #[error("failed to init logger: {0}")] - Init(#[from] tracing_subscriber::util::TryInitError), - #[error("failed to reload logger: {0}")] - Reload(#[from] tracing_subscriber::reload::Error), -} - -pub fn init(level: &str, mode: Mode) -> Result<(), LoggingError> { - let reload = RELOAD_HANDLE.get_or_try_init(|| { - let env_filter = EnvFilter::from_str(level).expect("failed to parse log level"); - - let filter = tracing_subscriber::fmt() - .with_line_number(true) - .with_file(true) - .with_env_filter(env_filter) - .with_filter_reloading(); - - let handle = filter.reload_handle(); - - match mode { - Mode::Default => filter.finish().try_init(), - Mode::Json => filter.json().finish().try_init(), - Mode::Pretty => filter.pretty().finish().try_init(), - Mode::Compact => filter.compact().finish().try_init(), - } - .map(|_| handle) - })?; - - reload.reload(level)?; - - Ok(()) -} diff --git a/common/src/macros.rs b/common/src/macros.rs deleted file mode 100644 index ab9573600..000000000 --- a/common/src/macros.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[macro_export] -macro_rules! vec_of_strings { - ($($x:expr),* $(,)?) => (vec![$($x.into()),*] as Vec); -} diff --git a/common/src/prelude/futures.rs b/common/src/prelude/futures.rs deleted file mode 100644 index 521689601..000000000 --- a/common/src/prelude/futures.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::time::Duration; - -use futures::Future; -use tokio::time::Timeout; - -pub trait FutureTimeout: Future { - #[inline(always)] - fn timeout(self, duration: Duration) -> Timeout - where - Self: Sized, - { - tokio::time::timeout(duration, self) - } -} - -impl FutureTimeout for F {} diff --git a/common/src/rmq.rs b/common/src/rmq.rs deleted file mode 100644 index c8cbc8993..000000000 --- a/common/src/rmq.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::{ - sync::{atomic::AtomicUsize, Arc}, - time::Duration, -}; - -use anyhow::{anyhow, Result}; -use arc_swap::ArcSwap; -use async_stream::stream; -use futures::{Stream, StreamExt}; -use lapin::{ - options::BasicConsumeOptions, topology::TopologyDefinition, types::FieldTable, Channel, - Connection, ConnectionProperties, -}; -use tokio::sync::{broadcast, mpsc, Mutex}; -use tracing::{info_span, Instrument}; - -use crate::prelude::FutureTimeout; - -pub struct ConnectionPool { - uri: String, - timeout: Duration, - properties: ConnectionProperties, - error_queue: mpsc::Sender, - error_queue_rx: Mutex>, - new_connection_waker: broadcast::Sender<()>, - connections: Vec>, - aquire_idx: AtomicUsize, -} - -impl ConnectionPool { - pub async fn connect( - uri: String, - properties: ConnectionProperties, - timeout: Duration, - pool_size: usize, - ) -> Result { - let connections = Vec::with_capacity(pool_size); - let (tx, rx) = mpsc::channel(pool_size); - - let mut pool = Self { - uri, - properties, - timeout, - connections, - error_queue: tx, - error_queue_rx: Mutex::new(rx), - new_connection_waker: broadcast::channel(1).0, - aquire_idx: AtomicUsize::new(0), - }; - - for i in 0..pool_size { - let conn = pool.new_connection(i, None).await?; - pool.connections.push(ArcSwap::from(Arc::new(conn))); - } - - Ok(pool) - } - - pub async fn handle_reconnects(&self) -> Result<()> { - loop { - let idx = self - .error_queue_rx - .lock() - .await - .recv() - .await - .expect("error queue closed"); - let conn = async { - loop { - let conn = match self - .new_connection(idx, Some(self.connections[idx].load().topology())) - .await - { - Ok(conn) => conn, - Err(err) => { - tracing::error!("failed to reconnect: {}", err); - tokio::time::sleep(Duration::from_secs(1)).await; - continue; - } - }; - - tracing::info!("reconnected to rabbitmq"); - break conn; - } - } - .instrument(info_span!("reconnect rmq", idx)) - .timeout(self.timeout) - .await?; - - self.connections[idx].store(Arc::new(conn)); - self.new_connection_waker.send(()).ok(); - } - } - - pub async fn new_connection( - &self, - idx: usize, - topology: Option, - ) -> Result { - let conn = Connection::connect(&self.uri, self.properties.clone()) - .timeout(self.timeout) - .await??; - - if let Some(topology) = topology { - conn.restore(topology).await?; - } - - let sender = self.error_queue.clone(); - conn.on_error(move |e| { - tracing::error!("rabbitmq error: {:?}", e); - - if let Err(err) = sender.try_send(idx) { - tracing::error!("failed to reload connection: {}", err); - } - }); - - Ok(conn) - } - - pub fn basic_consume( - &self, - queue_name: impl ToString, - connection_name: impl ToString, - options: BasicConsumeOptions, - table: FieldTable, - ) -> impl Stream> + '_ { - let queue_name = queue_name.to_string(); - let connection_name = connection_name.to_string(); - - stream!({ - 'connection_loop: loop { - let channel = self.aquire().await?; - let mut consumer = channel - .basic_consume(&queue_name, &connection_name, options, table.clone()) - .await?; - loop { - let m = consumer.next().await; - match m { - Some(Ok(m)) => { - yield Ok(m); - } - Some(Err(e)) => match e { - lapin::Error::IOError(e) => { - if e.kind() == std::io::ErrorKind::ConnectionReset { - continue 'connection_loop; - } - } - _ => { - yield Err(anyhow!("failed to get message: {}", e)); - } - }, - None => { - continue 'connection_loop; - } - } - } - } - }) - } - - pub async fn aquire(&self) -> Result { - let mut done = false; - loop { - let mut conn = None; - let start_idx = self - .aquire_idx - .fetch_add(1, std::sync::atomic::Ordering::Relaxed) - % self.connections.len(); - for c in self.connections[start_idx..] - .iter() - .chain(self.connections[..start_idx].iter()) - { - let loaded = c.load(); - if loaded.status().connected() { - conn = Some(loaded.clone()); - break; - } - } - - if let Some(conn) = conn { - let channel = conn.create_channel().await?; - return Ok(channel); - } - - if done { - return Err(anyhow!("no connections available")); - } - - done = true; - self.new_connection_waker - .subscribe() - .recv() - .timeout(self.timeout) - .await??; - } - } -} diff --git a/common/src/signal.rs b/common/src/signal.rs deleted file mode 100644 index 53163ad6d..000000000 --- a/common/src/signal.rs +++ /dev/null @@ -1,45 +0,0 @@ -use tokio::{signal::unix::SignalKind, sync::mpsc}; - -pub struct SignalHandler { - signal_send: mpsc::Sender, - signal_recv: mpsc::Receiver, -} - -impl Default for SignalHandler { - fn default() -> Self { - let (signal_send, signal_recv) = mpsc::channel(1); - Self { - signal_send, - signal_recv, - } - } -} - -impl SignalHandler { - pub fn new() -> Self { - Self::default() - } - - pub fn with_signal(self, kind: SignalKind) -> Self { - let mut signal = tokio::signal::unix::signal(kind).expect("failed to create signal"); - - let send = self.signal_send.clone(); - tokio::spawn(async move { - loop { - signal.recv().await; - if send.send(kind).await.is_err() { - break; - } - } - }); - - self - } - - pub async fn recv(&mut self) -> SignalKind { - self.signal_recv - .recv() - .await - .expect("failed to receive signal") - } -} diff --git a/common/src/tests/context.rs b/common/src/tests/context.rs deleted file mode 100644 index 4bb508dec..000000000 --- a/common/src/tests/context.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::time::Duration; - -use tokio::time::Instant; - -use crate::{ - context::{CancelReason, Context}, - prelude::FutureTimeout, -}; - -#[tokio::test] -async fn test_context_cancel() { - let (ctx, handler) = Context::new(); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Cancel); - }); - - handler - .cancel() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); -} - -#[tokio::test] -async fn test_context_deadline() { - let (ctx, mut handler) = Context::with_deadline(Instant::now() + Duration::from_millis(100)); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Deadline); - }); - - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); - handler - .done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); -} - -#[tokio::test] -async fn test_context_is_done() { - let (ctx, handler) = Context::new(); - - let handle = tokio::spawn(async move { - assert!(!ctx.is_done()); - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Cancel); - assert!(ctx.is_done()); - }); - - tokio::time::sleep(Duration::from_millis(100)).await; - - drop(handler); - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); -} - -#[tokio::test] -async fn test_context_timeout() { - let (ctx, mut handler) = Context::with_timeout(Duration::from_millis(100)); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Deadline); - }); - - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); - handler - .done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); -} - -#[tokio::test] -async fn test_context_parent() { - let (parent, parent_handler) = Context::new(); - let (ctx, mut handler) = Context::with_parent(parent, None); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Parent); - }); - - parent_handler - .cancel() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); - handler - .done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); -} - -#[tokio::test] -async fn test_context_parent_deadline() { - let (parent, mut parent_handler) = Context::new(); - let (ctx, mut handler) = - Context::with_parent(parent, Some(Instant::now() + Duration::from_millis(100))); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Deadline); - }); - - parent_handler - .done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - handler - .done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); -} - -#[tokio::test] -async fn test_context_parent_deadline_cancel() { - let (parent, mut parent_handler) = Context::new(); - let (ctx, handler) = - Context::with_parent(parent, Some(Instant::now() + Duration::from_millis(100))); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Cancel); - }); - - handler - .cancel() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - parent_handler - .done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); -} - -#[tokio::test] -async fn test_context_parent_deadline_parent_cancel() { - let (parent, parent_handler) = Context::new(); - let (ctx, mut handler) = - Context::with_parent(parent, Some(Instant::now() + Duration::from_millis(100))); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Parent); - }); - - parent_handler - .cancel() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - handler - .done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); -} - -#[tokio::test] -async fn test_context_cancel_cloned() { - let (ctx, handler) = Context::new(); - let ctx2 = ctx.clone(); - - let handle = tokio::spawn(async move { - let reason = ctx.done().await; - assert_eq!(reason, CancelReason::Cancel); - }); - - handler - .cancel() - .timeout(Duration::from_millis(300)) - .await - .expect_err("task should block because a clone exists"); - handle - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled") - .expect("panic in task"); - ctx2.done() - .timeout(Duration::from_millis(300)) - .await - .expect("task should be cancelled"); -} - -#[test] -fn test_fmt_reason() { - assert_eq!(format!("{}", CancelReason::Cancel), "Cancel"); - assert_eq!(format!("{}", CancelReason::Deadline), "Deadline"); - assert_eq!(format!("{}", CancelReason::Parent), "Parent"); -} diff --git a/common/src/tests/grpc.rs b/common/src/tests/grpc.rs deleted file mode 100644 index 34f5945b2..000000000 --- a/common/src/tests/grpc.rs +++ /dev/null @@ -1,566 +0,0 @@ -use std::{ - net::{IpAddr, SocketAddr}, - sync::Arc, - time::Duration, -}; - -use async_trait::async_trait; -use tokio::sync::Mutex; -use tonic::transport::Server; -use trust_dns_resolver::{ - error::ResolveError, - lookup::Lookup, - proto::{ - op::Query, - rr::{RData, Record, RecordType}, - }, - Name, -}; - -use crate::grpc::{ - make_channel, make_channel_with_opts, make_channel_with_resolver, ChannelOpts, DnsResolver, -}; - -mod pb { - tonic::include_proto!("test"); -} - -struct TestImpl { - name: String, -} - -#[async_trait] -impl pb::test_server::Test for TestImpl { - async fn test( - &self, - request: tonic::Request, - ) -> Result, tonic::Status> { - Ok(tonic::Response::new(pb::TestResponse { - message: request.into_inner().message, - server: self.name.clone(), - })) - } -} - -#[tokio::test] -async fn test_static_ip_resolve() { - let addr_1 = SocketAddr::from(( - [127, 0, 0, 1], - portpicker::pick_unused_port().expect("failed to pick port"), - )); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server1".to_string(), - })) - .serve(addr_1), - ); - - let addr_2 = SocketAddr::from(( - [127, 0, 0, 1], - portpicker::pick_unused_port().expect("failed to pick port"), - )); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server2".to_string(), - })) - .serve(addr_2), - ); - - let channel = make_channel( - vec![addr_1.to_string(), addr_2.to_string()], - Duration::from_secs(0), - None, - ) - .unwrap(); - let mut client = pb::test_client::TestClient::new(channel); - - let mut server_1 = 0; - let mut server_2 = 0; - - const NUM_REQUESTS: usize = 1000; - - for _ in 0..NUM_REQUESTS { - let response = client - .test(tonic::Request::new(pb::TestRequest { - message: "test".to_string(), - })) - .await - .unwrap() - .into_inner(); - - assert_eq!(response.message, "test"); - - if response.server == "server1" { - server_1 += 1; - } else if response.server == "server2" { - server_2 += 1; - } else { - panic!("unknown server"); - } - } - - // The distribution is not perfect, but it should be close to 50/50 - // If it's not, then the load balancer is not working - // This allows for a 10% error margin - assert!(server_1 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert!(server_2 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert_eq!(server_1 + server_2, NUM_REQUESTS); -} - -#[tokio::test] -async fn test_dns_resolve_v4() { - let addr_1 = SocketAddr::from(( - [127, 0, 0, 1], - portpicker::pick_unused_port().expect("failed to pick port"), - )); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server1".to_string(), - })) - .serve(addr_1), - ); - - let addr_2 = SocketAddr::from(( - [127, 0, 0, 1], - portpicker::pick_unused_port().expect("failed to pick port"), - )); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server2".to_string(), - })) - .serve(addr_2), - ); - - let channel = make_channel_with_opts(ChannelOpts { - addresses: vec![ - format!("localhost:{}", addr_1.port()), - format!("localhost:{}", addr_2.port()), - ], - try_cname: false, - enable_ipv6: false, - enable_ipv4: true, - interval: Duration::from_secs(0), - tls: None, - }) - .unwrap(); - let mut client = pb::test_client::TestClient::new(channel); - - let mut server_1 = 0; - let mut server_2 = 0; - - const NUM_REQUESTS: usize = 1000; - - for _ in 0..NUM_REQUESTS { - let response = client - .test(tonic::Request::new(pb::TestRequest { - message: "test".to_string(), - })) - .await - .unwrap() - .into_inner(); - - assert_eq!(response.message, "test"); - - if response.server == "server1" { - server_1 += 1; - } else if response.server == "server2" { - server_2 += 1; - } else { - panic!("unknown server"); - } - } - - // The distribution is not perfect, but it should be close to 50/50 - // If it's not, then the load balancer is not working - // This allows for a 10% error margin - assert!(server_1 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert!(server_2 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert_eq!(server_1 + server_2, NUM_REQUESTS); -} - -#[tokio::test] -async fn test_dns_resolve_v6() { - let addr_1 = SocketAddr::from(( - [0, 0, 0, 0, 0, 0, 0, 1], - portpicker::pick_unused_port().expect("failed to pick port"), - )); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server1".to_string(), - })) - .serve(addr_1), - ); - - let addr_2 = SocketAddr::from(( - [0, 0, 0, 0, 0, 0, 0, 1], - portpicker::pick_unused_port().expect("failed to pick port"), - )); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server2".to_string(), - })) - .serve(addr_2), - ); - - let channel = make_channel_with_opts(ChannelOpts { - addresses: vec![ - format!("localhost:{}", addr_1.port()), - format!("localhost:{}", addr_2.port()), - ], - try_cname: false, - enable_ipv6: true, - enable_ipv4: false, - interval: Duration::from_secs(0), - tls: None, - }) - .unwrap(); - let mut client = pb::test_client::TestClient::new(channel); - - let mut server_1 = 0; - let mut server_2 = 0; - - const NUM_REQUESTS: usize = 1000; - - for _ in 0..NUM_REQUESTS { - let response = client - .test(tonic::Request::new(pb::TestRequest { - message: "test".to_string(), - })) - .await - .unwrap() - .into_inner(); - - assert_eq!(response.message, "test"); - - if response.server == "server1" { - server_1 += 1; - } else if response.server == "server2" { - server_2 += 1; - } else { - panic!("unknown server"); - } - } - - // The distribution is not perfect, but it should be close to 50/50 - // If it's not, then the load balancer is not working - // This allows for a 10% error margin - assert!(server_1 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert!(server_2 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert_eq!(server_1 + server_2, NUM_REQUESTS); -} - -#[tokio::test] -async fn test_dns_resolve_cname() { - struct Dns; - - #[async_trait] - impl DnsResolver for Dns { - async fn lookup( - &self, - hostname: &str, - record_type: RecordType, - ) -> Result { - assert_eq!(hostname, "localhost"); - assert_eq!(record_type, RecordType::CNAME); - - Ok(Lookup::new_with_max_ttl( - Query::new(), - Arc::from([Record::from_rdata( - Name::default(), - 0, - RData::CNAME(Name::from_utf8("localhost").unwrap()), - )]), - )) - } - } - - let addr = SocketAddr::from(( - [127, 0, 0, 1], - portpicker::pick_unused_port().expect("failed to pick port"), - )); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server1".to_string(), - })) - .serve(addr), - ); - - let channel = make_channel_with_resolver( - Dns, - ChannelOpts { - addresses: vec![format!("localhost:{}", addr.port())], - enable_ipv4: false, - enable_ipv6: false, - try_cname: true, - interval: Duration::from_millis(0), - tls: None, - }, - ) - .unwrap(); - - let mut client = pb::test_client::TestClient::new(channel); - - let response = client - .test(tonic::Request::new(pb::TestRequest { - message: "test".to_string(), - })) - .await - .unwrap() - .into_inner(); - - assert_eq!(response.message, "test"); - assert_eq!(response.server, "server1"); -} - -#[tokio::test] -async fn test_headless_dns_resolve() { - struct Dns { - addresses: Vec, - } - - #[async_trait] - impl DnsResolver for Dns { - async fn lookup( - &self, - hostname: &str, - record_type: RecordType, - ) -> Result { - assert_eq!(hostname, "localhost"); - assert_eq!(record_type, RecordType::CNAME); - - let records = self - .addresses - .iter() - .map(|addr| { - Record::from_rdata( - Name::default(), - 0, - match addr.ip() { - IpAddr::V4(addr) => RData::A(addr), - IpAddr::V6(addr) => RData::AAAA(addr), - }, - ) - }) - .collect::>(); - - Ok(Lookup::new_with_max_ttl(Query::new(), Arc::from(records))) - } - } - - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let addr_1 = SocketAddr::from(([127, 0, 0, 1], port)); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server1".to_string(), - })) - .serve(addr_1), - ); - - let addr_2 = SocketAddr::from(([127, 0, 0, 2], port)); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server2".to_string(), - })) - .serve(addr_2), - ); - - let resolver = Dns { - addresses: vec![addr_1, addr_2], - }; - - let channel = make_channel_with_resolver( - resolver, - ChannelOpts { - addresses: vec![format!("localhost:{}", port)], - enable_ipv4: true, - enable_ipv6: true, - try_cname: true, - interval: Duration::from_secs(0), - tls: None, - }, - ) - .unwrap(); - let mut client = pb::test_client::TestClient::new(channel); - - let mut server_1 = 0; - let mut server_2 = 0; - - const NUM_REQUESTS: usize = 1000; - - for _ in 0..NUM_REQUESTS { - let response = client - .test(tonic::Request::new(pb::TestRequest { - message: "test".to_string(), - })) - .await - .unwrap() - .into_inner(); - - assert_eq!(response.message, "test"); - - if response.server == "server1" { - server_1 += 1; - } else if response.server == "server2" { - server_2 += 1; - } else { - panic!("unknown server"); - } - } - - // The distribution is not perfect, but it should be close to 50/50 - // If it's not, then the load balancer is not working - // This allows for a 10% error margin - assert!(server_1 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert!(server_2 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert_eq!(server_1 + server_2, NUM_REQUESTS); -} - -#[tokio::test] -async fn test_dns_resolve_change() { - struct Dns { - addresses: Arc>>, - } - - #[async_trait] - impl DnsResolver for Dns { - async fn lookup( - &self, - hostname: &str, - record_type: RecordType, - ) -> Result { - assert_eq!(hostname, "localhost"); - assert_eq!(record_type, RecordType::CNAME); - - let records = self - .addresses - .lock() - .await - .iter() - .map(|addr| { - Record::from_rdata( - Name::default(), - 0, - match addr.ip() { - IpAddr::V4(addr) => RData::A(addr), - IpAddr::V6(addr) => RData::AAAA(addr), - }, - ) - }) - .collect::>(); - - Ok(Lookup::new_with_max_ttl(Query::new(), Arc::from(records))) - } - } - - let port = portpicker::pick_unused_port().expect("failed to pick port"); - - let addr_1 = SocketAddr::from(([127, 0, 0, 1], port)); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server1".to_string(), - })) - .serve(addr_1), - ); - - let addr_2 = SocketAddr::from(([127, 0, 0, 2], port)); - tokio::spawn( - Server::builder() - .add_service(pb::test_server::TestServer::new(TestImpl { - name: "server2".to_string(), - })) - .serve(addr_2), - ); - - let addresses = Arc::new(Mutex::new(vec![addr_1, addr_2])); - - let resolver = Dns { - addresses: addresses.clone(), - }; - - let channel = make_channel_with_resolver( - resolver, - ChannelOpts { - addresses: vec![format!("localhost:{}", port)], - enable_ipv4: true, - enable_ipv6: true, - try_cname: true, - interval: Duration::from_millis(100), // very fast poll interval - tls: None, - }, - ) - .unwrap(); - let mut client = pb::test_client::TestClient::new(channel); - - let mut server_1 = 0; - let mut server_2 = 0; - - const NUM_REQUESTS: usize = 1000; - - for _ in 0..NUM_REQUESTS { - let response = client - .test(tonic::Request::new(pb::TestRequest { - message: "test".to_string(), - })) - .await - .unwrap() - .into_inner(); - - assert_eq!(response.message, "test"); - - if response.server == "server1" { - server_1 += 1; - } else if response.server == "server2" { - server_2 += 1; - } else { - panic!("unknown server"); - } - } - - // The distribution is not perfect, but it should be close to 50/50 - // If it's not, then the load balancer is not working - // This allows for a 10% error margin - assert!(server_1 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert!(server_2 > NUM_REQUESTS / 2 - NUM_REQUESTS / 10); - assert_eq!(server_1 + server_2, NUM_REQUESTS); - - // Now remove the second server - addresses.lock().await.remove(1); - - // Wait for the server to be removed - tokio::time::sleep(Duration::from_millis(150)).await; - - let mut server_1 = 0; - - for _ in 0..NUM_REQUESTS { - let response = client - .test(tonic::Request::new(pb::TestRequest { - message: "test".to_string(), - })) - .await - .unwrap() - .into_inner(); - - assert_eq!(response.message, "test"); - - if response.server == "server1" { - server_1 += 1; - } else { - panic!("unknown server"); - } - } - - // The distribution is not perfect, but it should be close to 100/0 - // If it's not, then the load balancer is not working - assert_eq!(server_1, NUM_REQUESTS); -} diff --git a/common/src/tests/logging.rs b/common/src/tests/logging.rs deleted file mode 100644 index c011d5a51..000000000 --- a/common/src/tests/logging.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::logging::{self, init}; - -#[test] -fn test_init() { - init("info", logging::Mode::Compact).expect("Failed to init logger"); -} diff --git a/common/src/tests/mod.rs b/common/src/tests/mod.rs deleted file mode 100644 index 0258ab1f4..000000000 --- a/common/src/tests/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[cfg(feature = "context")] -mod context; -#[cfg(feature = "grpc")] -mod grpc; -#[cfg(feature = "logging")] -mod logging; -#[cfg(feature = "signal")] -mod signal; diff --git a/common/src/tests/signal.rs b/common/src/tests/signal.rs deleted file mode 100644 index 95f3dff76..000000000 --- a/common/src/tests/signal.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::time::Duration; - -use tokio::{process::Command, signal::unix::SignalKind}; - -use crate::{prelude::FutureTimeout, signal::SignalHandler}; - -#[tokio::test] -async fn test_signal() { - let mut handler = SignalHandler::new() - .with_signal(SignalKind::interrupt()) - .with_signal(SignalKind::terminate()); - - // Send a SIGINT to the process - // We need to get the current pid and send the signal to it - let pid = std::process::id(); - - Command::new("kill") - .arg("-s") - .arg("SIGINT") - .arg(pid.to_string()) - .status() - .await - .expect("failed to send SIGINT"); - - handler - .recv() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to receive signal"); - - // Send a SIGTERM to the process - Command::new("kill") - .arg("-s") - .arg("SIGTERM") - .arg(pid.to_string()) - .status() - .await - .expect("failed to send SIGINT"); - - handler - .recv() - .timeout(Duration::from_secs(1)) - .await - .expect("failed to receive signal"); -} diff --git a/config/APACHE2_LICENSE b/config/APACHE2_LICENSE new file mode 120000 index 000000000..f7ae1b662 --- /dev/null +++ b/config/APACHE2_LICENSE @@ -0,0 +1 @@ +../licenses/APACHE2_LICENSE \ No newline at end of file diff --git a/config/Cargo.toml b/config/Cargo.toml new file mode 100644 index 000000000..94910cb9a --- /dev/null +++ b/config/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "scuffle-config" +version = "0.0.1" +edition = "2021" +authors = ["Scuffle "] +description = "Extensible config solution" +repository = "https://github.com/ScuffleTV/scuffle" +categories = ["config"] +keywords = ["config", "cli", "proc-macro"] +license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/scuffle-config" + +[lib] +name = "config" +path = "src/lib.rs" + +[dependencies] +thiserror = "1.0" +serde = { version = "1.0", features = ["derive", "rc"] } +tracing = "0.1" +serde_ignored = "0.1" +serde-value = "0.7" +serde_path_to_error = "0.1" +humantime = "2.1" +num-order = "1.2" +uuid = { version = "1.6", features = ["serde"] } +ulid = { version = "1.1", features = ["serde", "uuid"] } + +# Parsing files +serde_json = "1.0" +serde_yaml = "0.9" +toml = "0.8" + +# CLI +clap = { version = "4.4", features = ["cargo", "string"] } +convert_case = "0.6" + +# Derive macro +config_derive = { path = "derive", package = "scuffle_config_derive", version = "0.0.1"} + +[[example]] +name = "derive" +path = "examples/derive.rs" diff --git a/config/MIT_LICENSE b/config/MIT_LICENSE new file mode 120000 index 000000000..b0845139e --- /dev/null +++ b/config/MIT_LICENSE @@ -0,0 +1 @@ +../licenses/MIT_LICENSE \ No newline at end of file diff --git a/config/config/README.md b/config/README.md similarity index 100% rename from config/config/README.md rename to config/README.md diff --git a/config/config/Cargo.toml b/config/config/Cargo.toml deleted file mode 100644 index fea4e5f8d..000000000 --- a/config/config/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "config" -version = "0.1.0" -edition = "2021" -authors = ["Scuffle "] -description = "Extensible config solution" -repository = "https://github.com/ScuffleTV/scuffle" -categories = ["config"] -keywords = ["config", "cli", "proc-macro"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -thiserror = "1.0.40" -serde = { version = "1", features = ["derive", "rc"] } -tracing = { version = "0" } -serde_ignored = "0" -serde-value = "0" -serde_path_to_error = "0" -humantime = "2" -num-order = "1" - -# Parsing files -serde_json = "1" -serde_yaml = "0" -toml = "0" - -# CLI -clap = { version = "4", features = ["cargo", "string"] } -convert_case = "0" - -# Derive macro -config_derive = { path = "../config_derive" } - -[[example]] -name = "derive" -path = "examples/derive.rs" diff --git a/config/config/examples/derive.rs b/config/config/examples/derive.rs deleted file mode 100644 index 9c0ba86d4..000000000 --- a/config/config/examples/derive.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Run with: `cargo run --example derive` -//! Look at the generated code with: `cargo expand --example derive` - -use config::{sources, Config, ConfigBuilder, ConfigError}; - -type TypeAlias = bool; - -#[derive(config::Config, Debug, PartialEq, serde::Deserialize, Default)] -#[serde(default)] -struct AppConfig { - enabled: TypeAlias, - logging: LoggingConfig, - #[config(cli(skip), env(skip))] - count: Vec>, -} - -#[derive(config::Config, Debug, PartialEq, serde::Deserialize)] -#[serde(default)] -struct LoggingConfig { - level: String, - json: bool, -} - -impl Default for LoggingConfig { - fn default() -> Self { - Self { - level: "INFO".to_string(), - json: false, - } - } -} - -fn main() { - match parse() { - Ok(config) => println!("{:#?}", config), - Err(err) => println!("{:#}", err), - } -} - -fn parse() -> Result { - dbg!(AppConfig::graph()); - - let mut builder = ConfigBuilder::new(); - builder.add_source(sources::CliSource::new()?); - builder.add_source(sources::EnvSource::with_prefix("TEST")?); - builder.add_source(sources::FileSource::json( - br#" - { - "enabled": "on", - "count": [[2], [122]], - "logging": { - "level": "DEBUG", - "json": true - } - } - "# - .as_slice(), - )?); - - builder.overwrite("logging.level", "TEST")?; - builder.overwrite("logging.json", "off")?; - - let config: AppConfig = builder.build()?; - - Ok(config) -} diff --git a/config/config/src/error.rs b/config/config/src/error.rs deleted file mode 100644 index dc0604d66..000000000 --- a/config/config/src/error.rs +++ /dev/null @@ -1,181 +0,0 @@ -use std::{io, sync::Arc}; - -use serde_value::Value; - -use crate::{KeyGraph, KeyPath}; - -pub type Result = std::result::Result; - -/// Config error type -#[derive(Debug, thiserror::Error)] -pub enum ConfigErrorType { - #[error("unsupported file format: {0}")] - UnsupportedFileFormat(String), - #[error("deserialize: {0}")] - Deserialize(#[from] serde_value::DeserializerError), - #[error("unsupported type: {0:?}")] - UnsupportedType(Arc), - #[error("io: {0}")] - Io(#[from] io::Error), - #[error("toml: {0}")] - Toml(#[from] toml::de::Error), - #[error("yaml: {0}")] - Yaml(#[from] serde_yaml::Error), - #[error("json: {0}")] - Json(#[from] serde_json::Error), - #[error("multiple errors: {0}")] - Multiple(MultiError), - #[error("subkey on non-map: {0:?}")] - SubkeyOnNonMap(Value), - #[error("subkey on non-seq: {0:?}")] - SubIndexOnNonSeq(Value), - #[error("validation error: {0}")] - ValidationError(String), - #[error("invalid reference: {0}")] - InvalidReference(&'static str), - #[error("deserialized type not supported: {0}")] - DeserializedTypeNotSupported(String), - #[error("serialize: {0}")] - Serialize(#[from] serde_value::SerializerError), -} - -#[derive(Debug)] -pub struct MultiError(Vec); - -impl MultiError { - pub fn into_inner(self) -> Vec { - self.0 - } - - pub fn inner(&self) -> &[ConfigError] { - &self.0 - } -} - -impl std::fmt::Display for MultiError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let errors = self - .0 - .iter() - .map(|e| format!("{}", e)) - .collect::>() - .join(", "); - - write!(f, "{}", errors) - } -} - -/// The source of an error. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ErrorSource { - Cli, - Env, - File(String), - Manual, -} - -/// General config error -#[derive(Debug, Clone)] -pub struct ConfigError { - error: Arc, - path: Option, - source: Option, -} - -impl ConfigError { - pub(crate) fn new(error: ConfigErrorType) -> Self { - Self { - error: Arc::new(error), - path: None, - source: None, - } - } - - pub(crate) fn with_path(mut self, path: KeyPath) -> Self { - self.path = Some(path); - self - } - - pub(crate) fn with_source(mut self, source: ErrorSource) -> Self { - self.source = Some(source); - self - } - - /// Returns true if the error is an IO error. - pub fn is_io(&self) -> bool { - match self.error() { - ConfigErrorType::Io(_) => true, - ConfigErrorType::Multiple(MultiError(errors)) => errors.iter().any(|e| e.is_io()), - _ => false, - } - } - - /// Returns the path of the error. - pub fn path(&self) -> Option<&KeyPath> { - self.path.as_ref() - } - - /// Returns the source of the error. - pub fn source(&self) -> Option<&ErrorSource> { - self.source.as_ref() - } - - /// Returns the error type. - pub fn error(&self) -> &ConfigErrorType { - &self.error - } - - /// Creates a new multi error from this error and another. - pub fn multi(self, other: Self) -> Self { - let mut errors = Vec::new(); - match self.error() { - ConfigErrorType::Multiple(MultiError(errors1)) => { - errors.extend(errors1.clone()); - } - _ => { - errors.push(self); - } - } - - match other.error() { - ConfigErrorType::Multiple(MultiError(errors2)) => { - errors.extend(errors2.clone()); - } - _ => { - errors.push(other); - } - } - - Self::new(ConfigErrorType::Multiple(MultiError(errors))) - } -} - -impl From for ConfigError { - fn from(value: ConfigErrorType) -> Self { - Self::new(value) - } -} - -impl std::fmt::Display for ConfigError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Some errors will have a path and or source, so we should include those in the output. - // We should also write better errors for the enum variants. - if let Some(source) = self.source() { - write!(f, "{:?}: ", source)?; - } - - self.error().fmt(f)?; - - if let Some(path) = &self.path { - write!(f, " (path .{})", path)?; - } - - Ok(()) - } -} - -impl std::error::Error for ConfigError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&*self.error) - } -} diff --git a/config/config/src/key.rs b/config/config/src/key.rs deleted file mode 100644 index 8c26e4a6c..000000000 --- a/config/config/src/key.rs +++ /dev/null @@ -1,500 +0,0 @@ -use std::{ - any::TypeId, - cell::RefCell, - collections::{BTreeMap, HashMap}, - fmt::Display, - ptr::NonNull, - sync::{Arc, Weak}, -}; - -use crate::{Result, Value}; - -/// A path to a key. -/// -/// The path is represented as a list of [`KeyPathSegment`](KeyPathSegment)s. -/// -/// It is iterable and can be created from a string. -/// -/// ## Example -/// -/// `test.foo[0].bar` -/// is represented as -/// ``` -/// # use config::{KeyPathSegment, Value}; -/// # fn main() { -/// # let repr = -/// [ -/// KeyPathSegment::Map { key: Value::String("test".to_string()) }, -/// KeyPathSegment::Seq { index: 0 }, -/// KeyPathSegment::Map { key: Value::String("bar".to_string()) } -/// ] -/// # ; -/// # } -/// ``` -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] -pub struct KeyPath { - segments: Vec, -} - -impl From<&str> for KeyPath { - fn from(s: &str) -> Self { - // We need to parse the string for the following cases: - // - map: foo.bar - // - seq: foo[0] - // - sub-seq: foo[0][0] - // - sub-map: foo.bar.baz - - let mut segments = vec![]; - let mut chars = s.chars().peekable(); - - while let Some(c) = chars.next() { - if c == '[' { - let mut index = String::new(); - for c in chars.by_ref() { - if c == ']' { - break; - } else { - index.push(c); - } - } - - segments.push(KeyPathSegment::Seq { - index: index.parse().unwrap(), - }); - } else if c == '.' { - continue; - } else { - let mut field = String::new(); - field.push(c); - while let Some(c) = chars.peek() { - if *c == '.' || *c == '[' { - break; - } else { - field.push(*c); - chars.next(); - } - } - - segments.push(KeyPathSegment::Map { - key: Value::String(field), - }); - } - } - - Self { segments } - } -} - -impl From for KeyPath { - fn from(s: String) -> Self { - s.as_str().into() - } -} - -impl IntoIterator for KeyPath { - type Item = KeyPathSegment; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.segments.into_iter() - } -} - -impl<'a> IntoIterator for &'a KeyPath { - type Item = &'a KeyPathSegment; - type IntoIter = std::slice::Iter<'a, KeyPathSegment>; - - fn into_iter(self) -> Self::IntoIter { - self.segments.iter() - } -} - -impl KeyPath { - pub fn iter(&self) -> std::slice::Iter<'_, KeyPathSegment> { - self.segments.iter() - } -} - -/// A segment of a [`KeyPath`](KeyPath). -/// -/// Either a map or a sequence. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum KeyPathSegment { - Map { key: Value }, - Seq { index: usize }, -} - -impl KeyPathSegment { - /// Returns true if the segment is a map. - fn is_map(&self) -> bool { - matches!(self, Self::Map { .. }) - } - - /// Returns true if the segment is a sequence. - fn is_seq(&self) -> bool { - matches!(self, Self::Seq { .. }) - } -} - -impl std::fmt::Display for KeyPathSegment { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Map { key } => write!( - f, - "{}", - match key { - Value::String(s) => s.to_string(), - Value::I8(i) => i.to_string(), - Value::I16(i) => i.to_string(), - Value::I32(i) => i.to_string(), - Value::I64(i) => i.to_string(), - Value::U8(i) => i.to_string(), - Value::U16(i) => i.to_string(), - Value::U32(i) => i.to_string(), - Value::U64(i) => i.to_string(), - Value::F32(i) => i.to_string(), - Value::F64(i) => i.to_string(), - Value::Bool(i) => i.to_string(), - Value::Char(i) => i.to_string(), - Value::Unit => "".to_string(), - Value::Option(_) => "