chore(deps): upgrade all dependencies, toolchains and images (#2825)

This commit is contained in:
Hampus
2026-09-17 21:08:56 +02:00
committed by GitHub
parent 56e04e7b53
commit 3b552e00ef
510 changed files with 23614 additions and 37473 deletions
+9 -11
View File
@@ -1,14 +1,14 @@
FROM chrislusf/seaweedfs:4.31 AS seaweedfs FROM chrislusf/seaweedfs:4.47 AS seaweedfs
FROM erlang:28.5.0.1 FROM erlang:28.5.0.6
ARG USERNAME=vscode ARG USERNAME=vscode
ARG USER_UID=1000 ARG USER_UID=1000
ARG USER_GID=1000 ARG USER_GID=1000
ARG NODE_MAJOR=24 ARG NODE_MAJOR=26
ARG ELP_VERSION=2026-02-27 ARG ELP_VERSION=2026-08-10
ARG PNPM_VERSION=10.29.3 ARG PNPM_VERSION=12.4.2
ARG WASM_BINDGEN_VERSION=0.2.123 ARG WASM_BINDGEN_VERSION=0.2.128
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
@@ -131,7 +131,8 @@ RUN apt-get update \
RUN curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \ RUN curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \ && apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& corepack enable && npm install -g "pnpm@${PNPM_VERSION}" \
&& pnpm --version
RUN python3 -m pip install --break-system-packages --no-cache-dir awscli RUN python3 -m pip install --break-system-packages --no-cache-dir awscli
@@ -167,7 +168,7 @@ RUN ARCH="$(dpkg --print-architecture)" \
arm64) ELP_ARCH="aarch64" ;; \ arm64) ELP_ARCH="aarch64" ;; \
*) echo "Unsupported architecture for ELP: $ARCH" >&2; exit 1 ;; \ *) echo "Unsupported architecture for ELP: $ARCH" >&2; exit 1 ;; \
esac \ esac \
&& curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL "https://github.com/WhatsApp/erlang-language-platform/releases/download/${ELP_VERSION}/elp-linux-${ELP_ARCH}-unknown-linux-gnu-otp-28.tar.gz" -o /tmp/elp.tgz \ && curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL "https://github.com/WhatsApp/erlang-language-platform/releases/download/${ELP_VERSION}/elp-linux-${ELP_ARCH}-unknown-linux-gnu-otp-28.5.tar.gz" -o /tmp/elp.tgz \
&& tar -C /usr/local/bin -xzf /tmp/elp.tgz elp \ && tar -C /usr/local/bin -xzf /tmp/elp.tgz elp \
&& chmod +x /usr/local/bin/elp \ && chmod +x /usr/local/bin/elp \
&& rm /tmp/elp.tgz && rm /tmp/elp.tgz
@@ -194,7 +195,4 @@ RUN curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL https://sh.rustup.rs
&& cargo install wasm-bindgen-cli --version "${WASM_BINDGEN_VERSION}" --locked \ && cargo install wasm-bindgen-cli --version "${WASM_BINDGEN_VERSION}" --locked \
&& rm -rf "/home/${USERNAME}/.cargo/registry" "/home/${USERNAME}/.cargo/git" && rm -rf "/home/${USERNAME}/.cargo/registry" "/home/${USERNAME}/.cargo/git"
RUN corepack prepare "pnpm@${PNPM_VERSION}" --activate \
&& pnpm --version
WORKDIR /workspaces/fluxer WORKDIR /workspaces/fluxer
+6 -5
View File
@@ -9,7 +9,7 @@ services:
init: true init: true
environment: environment:
DOCKER_HOST: unix:///var/run/docker.sock DOCKER_HOST: unix:///var/run/docker.sock
npm_config_store_dir: /home/vscode/.local/share/pnpm/store pnpm_config_store_dir: /home/vscode/.local/share/pnpm/store
FLUXER_PUBLIC_PORT: "${FLUXER_DEV_PROXY_PORT:-8088}" FLUXER_PUBLIC_PORT: "${FLUXER_DEV_PROXY_PORT:-8088}"
FLUXER_PUBLIC_URL: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}" FLUXER_PUBLIC_URL: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}"
FLUXER_API_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/api" FLUXER_API_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/api"
@@ -292,13 +292,13 @@ services:
start_period: 5s start_period: 5s
valkey: valkey:
image: valkey/valkey:8.1.7-alpine image: valkey/valkey:9.1.2-alpine
command: ["valkey-server", "--save", "", "--appendonly", "no"] command: ["valkey-server", "--save", "", "--appendonly", "no"]
ports: ports:
- "127.0.0.1:${FLUXER_DEV_VALKEY_PORT:-6379}:6379" - "127.0.0.1:${FLUXER_DEV_VALKEY_PORT:-6379}:6379"
nats: nats:
image: nats:2.14.2-alpine image: nats:2.14.7-alpine
command: ["-js", "-sd", "/data", "-m", "8222"] command: ["-js", "-sd", "/data", "-m", "8222"]
volumes: volumes:
- nats-data:/data - nats-data:/data
@@ -321,9 +321,10 @@ services:
- "127.0.0.1:${FLUXER_DEV_LIVEKIT_UDP_PORT:-7882}:${FLUXER_DEV_LIVEKIT_UDP_PORT:-7882}/udp" - "127.0.0.1:${FLUXER_DEV_LIVEKIT_UDP_PORT:-7882}:${FLUXER_DEV_LIVEKIT_UDP_PORT:-7882}/udp"
meilisearch: meilisearch:
image: getmeili/meilisearch:v1.12 image: getmeili/meilisearch:v1.53
environment: environment:
MEILI_NO_ANALYTICS: "true" MEILI_NO_ANALYTICS: "true"
MEILI_UPGRADE_DB: "true"
MEILI_MASTER_KEY: fluxer-dev-meilisearch MEILI_MASTER_KEY: fluxer-dev-meilisearch
volumes: volumes:
- meilisearch-data:/meili_data - meilisearch-data:/meili_data
@@ -337,7 +338,7 @@ services:
start_period: 5s start_period: 5s
mailpit: mailpit:
image: axllent/mailpit:v1.30 image: axllent/mailpit:v1.31
environment: environment:
MP_DATABASE: /data/mailpit.db MP_DATABASE: /data/mailpit.db
MP_MAX_MESSAGES: 5000 MP_MAX_MESSAGES: 5000
+12 -12
View File
@@ -58,13 +58,13 @@ jobs:
outputs: outputs:
build_version: ${{ steps.vars.outputs.build_version }} build_version: ${{ steps.vars.outputs.build_version }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Create token - name: Create token
id: create-token id: create-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
@@ -101,19 +101,19 @@ jobs:
- platform: arm64 - platform: arm64
runner: ubuntu-24.04-arm runner: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: resolve source date - name: resolve source date
id: source id: source
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ github.token }} password: ${{ github.token }}
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc
with: with:
context: ${{ inputs.context }} context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }} file: ${{ inputs.dockerfile }}
@@ -141,15 +141,15 @@ jobs:
contents: write contents: write
packages: write packages: write
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -43,13 +43,13 @@ jobs:
outputs: outputs:
build_version: ${{ steps.vars.outputs.build_version }} build_version: ${{ steps.vars.outputs.build_version }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: set variables - name: set variables
id: vars id: vars
run: >- run: >-
@@ -73,18 +73,18 @@ jobs:
BUNDLE_LOCAL_ASSETS: "true" BUNDLE_LOCAL_ASSETS: "true"
FLUXER_APP_PROXY_TIME_FREEZE_ENABLED: "false" FLUXER_APP_PROXY_TIME_FREEZE_ENABLED: "false"
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: prepare docker config - name: prepare docker config
run: >- run: >-
tools/ci/run.sh build-app-proxy tools/ci/run.sh build-app-proxy
--step prepare_docker_config --step prepare_docker_config
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- name: configure ghcr auth - name: configure ghcr auth
env: env:
GHCR_USERNAME: ${{ github.actor }} GHCR_USERNAME: ${{ github.actor }}
@@ -131,19 +131,19 @@ jobs:
- platform: arm64 - platform: arm64
runner: ubuntu-24.04-arm runner: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: resolve source date - name: resolve source date
id: source id: source
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc
with: with:
context: . context: .
file: fluxer_app_proxy/Dockerfile file: fluxer_app_proxy/Dockerfile
@@ -173,15 +173,15 @@ jobs:
contents: write contents: write
packages: write packages: write
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
+20 -20
View File
@@ -43,13 +43,13 @@ jobs:
outputs: outputs:
build_version: ${{ steps.vars.outputs.build_version }} build_version: ${{ steps.vars.outputs.build_version }}
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: set variables - name: set variables
id: vars id: vars
run: >- run: >-
@@ -67,18 +67,18 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: prepare docker config - name: prepare docker config
run: >- run: >-
tools/ci/run.sh build-app-proxy tools/ci/run.sh build-app-proxy
--step prepare_docker_config --step prepare_docker_config
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- name: configure ghcr auth - name: configure ghcr auth
env: env:
GHCR_USERNAME: ${{ github.actor }} GHCR_USERNAME: ${{ github.actor }}
@@ -131,13 +131,13 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: resolve source date - name: resolve source date
id: source id: source
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
@@ -145,7 +145,7 @@ jobs:
run: >- run: >-
tools/ci/run.sh build-app-proxy tools/ci/run.sh build-app-proxy
--step prepare_docker_config --step prepare_docker_config
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- name: configure ghcr auth - name: configure ghcr auth
env: env:
GHCR_USERNAME: ${{ github.actor }} GHCR_USERNAME: ${{ github.actor }}
@@ -178,19 +178,19 @@ jobs:
contents: read contents: read
packages: write packages: write
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: resolve source date - name: resolve source date
id: source id: source
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT" run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf - uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc
with: with:
context: . context: .
file: fluxer_app_proxy/Dockerfile file: fluxer_app_proxy/Dockerfile
@@ -219,15 +219,15 @@ jobs:
contents: write contents: write
packages: write packages: write
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
+28 -28
View File
@@ -58,14 +58,14 @@ jobs:
source_sha: ${{ steps.meta.outputs.source_sha }} source_sha: ${{ steps.meta.outputs.source_sha }}
steps: steps:
- name: Checkout source - name: Checkout source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
ref: main ref: main
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Create token - name: Create token
id: create-token id: create-token
@@ -98,12 +98,12 @@ jobs:
matrix: ${{ steps.set-matrix.outputs.matrix }} matrix: ${{ steps.set-matrix.outputs.matrix }}
steps: steps:
- name: Checkout source - name: Checkout source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Build platform matrix - name: Build platform matrix
id: set-matrix id: set-matrix
@@ -151,27 +151,27 @@ jobs:
ELECTRON_ARCH: ${{ matrix.electron_arch }} ELECTRON_ARCH: ${{ matrix.electron_arch }}
steps: steps:
- name: Checkout CI helpers - name: Checkout CI helpers
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
ref: ${{ needs.meta.outputs.source_sha }} ref: ${{ needs.meta.outputs.source_sha }}
path: _ci path: _ci
- name: Checkout source - name: Checkout source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
ref: ${{ needs.meta.outputs.source_sha }} ref: ${{ needs.meta.outputs.source_sha }}
path: source path: source
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Set up Python (Windows) - name: Set up Python (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with: with:
python-version: "3.13" python-version: "3.14"
- name: Ensure python3 command (Windows) - name: Ensure python3 command (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
@@ -196,14 +196,14 @@ jobs:
--step set_workdir_unix --step set_workdir_unix
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: 24 node-version: 26
- name: Set up pnpm via corepack - name: Set up pnpm
run: >- run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step setup_pnpm_corepack --step setup_pnpm
- name: Resolve pnpm store path (Windows) - name: Resolve pnpm store path (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
@@ -247,9 +247,9 @@ jobs:
- name: Set up Rust toolchain (Unix) - name: Set up Rust toolchain (Unix)
if: matrix.platform != 'windows' if: matrix.platform != 'windows'
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
targets: ${{ matrix.platform == 'macos' && 'aarch64-apple-darwin,x86_64-apple-darwin' || (matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu') }} targets: ${{ matrix.platform == 'macos' && 'aarch64-apple-darwin,x86_64-apple-darwin' || (matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu') }}
- name: Install MSVC ARM64 build tools - name: Install MSVC ARM64 build tools
@@ -260,7 +260,7 @@ jobs:
- name: Set up MSVC env (Windows) - name: Set up MSVC env (Windows)
if: matrix.platform == 'windows' if: matrix.platform == 'windows'
uses: TheMrMilchmann/setup-msvc-dev@79dac248aac9d0059f86eae9d8b5bfab4e95e97c uses: TheMrMilchmann/setup-msvc-dev@368ef7d1ee4d1171b31d4a7f67f4d954f903f5a9
with: with:
arch: ${{ matrix.arch == 'arm64' && 'amd64_arm64' || 'amd64' }} arch: ${{ matrix.arch == 'arm64' && 'amd64_arm64' || 'amd64' }}
@@ -302,9 +302,9 @@ jobs:
- name: Set up .NET SDK (Windows) - name: Set up .NET SDK (Windows)
if: matrix.platform == 'windows' if: matrix.platform == 'windows'
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68
with: with:
dotnet-version: "8.0.x" dotnet-version: "10.0.x"
- name: Install Velopack CLI - name: Install Velopack CLI
if: matrix.platform == 'windows' if: matrix.platform == 'windows'
@@ -363,7 +363,7 @@ jobs:
- name: Azure login for Artifact Signing - name: Azure login for Artifact Signing
if: matrix.platform == 'windows' if: matrix.platform == 'windows'
uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906
with: with:
client-id: ${{ secrets.AZURE_CLIENT_ID }} client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }}
@@ -533,14 +533,14 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOADS_AWS_SECRET_ACCESS_KEY || secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOADS_AWS_SECRET_ACCESS_KEY || secrets.AWS_SECRET_ACCESS_KEY }}
steps: steps:
- name: Checkout source - name: Checkout source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
ref: ${{ needs.meta.outputs.source_sha }} ref: ${{ needs.meta.outputs.source_sha }}
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Download S3 handoff artifacts - name: Download S3 handoff artifacts
run: >- run: >-
@@ -612,14 +612,14 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOADS_AWS_SECRET_ACCESS_KEY || secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOADS_AWS_SECRET_ACCESS_KEY || secrets.AWS_SECRET_ACCESS_KEY }}
steps: steps:
- name: Checkout source - name: Checkout source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
ref: ${{ needs.meta.outputs.source_sha }} ref: ${{ needs.meta.outputs.source_sha }}
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Download GitHub release assets - name: Download GitHub release assets
run: >- run: >-
+6 -6
View File
@@ -19,22 +19,22 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout fluxer - name: Checkout fluxer
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
persist-credentials: false persist-credentials: false
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
+6 -6
View File
@@ -35,24 +35,24 @@ jobs:
permission-pull-requests: write permission-pull-requests: write
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
token: ${{ steps.create-token.outputs.token }} token: ${{ steps.create-token.outputs.token }}
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Set up Rust toolchain - name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: "24" node-version: "26"
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies
+6 -6
View File
@@ -40,7 +40,7 @@ jobs:
permission-pull-requests: write permission-pull-requests: write
- name: Checkout Weblate branch - name: Checkout Weblate branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with: with:
token: ${{ steps.create-token.outputs.token }} token: ${{ steps.create-token.outputs.token }}
ref: ${{ env.WEBLATE_BRANCH }} ref: ${{ env.WEBLATE_BRANCH }}
@@ -48,17 +48,17 @@ jobs:
persist-credentials: false persist-credentials: false
- name: Set up Rust toolchain - name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: "24" node-version: "26"
cache: "pnpm" cache: "pnpm"
- name: Install dependencies - name: Install dependencies
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
permission-pull-requests: write permission-pull-requests: write
- name: Label pull request - name: Label pull request
uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13
with: with:
repo-token: ${{ steps.create-token.outputs.token }} repo-token: ${{ steps.create-token.outputs.token }}
configuration-path: .github/labeller.yaml configuration-path: .github/labeller.yaml
+5 -5
View File
@@ -56,15 +56,15 @@ jobs:
contents: write contents: write
packages: read packages: read
steps: steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
env: env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
+56 -56
View File
@@ -28,12 +28,12 @@ jobs:
FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
targets: wasm32-unknown-unknown targets: wasm32-unknown-unknown
- name: Restore ci helper - name: Restore ci helper
@@ -42,7 +42,7 @@ jobs:
with: with:
path: target/debug/fluxer-ci path: target/debug/fluxer-ci
key: >- key: >-
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml', fluxer-ci-bin-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'Cargo.toml',
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }} 'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
- name: Build ci helper - name: Build ci helper
@@ -55,16 +55,16 @@ jobs:
with: with:
path: target/debug/fluxer-ci path: target/debug/fluxer-ci
key: >- key: >-
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml', fluxer-ci-bin-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'Cargo.toml',
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }} 'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
@@ -83,12 +83,12 @@ jobs:
PNPM_TEST_WORKSPACE_CONCURRENCY: '2' PNPM_TEST_WORKSPACE_CONCURRENCY: '2'
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
targets: wasm32-unknown-unknown targets: wasm32-unknown-unknown
- name: Restore ci helper - name: Restore ci helper
@@ -97,7 +97,7 @@ jobs:
with: with:
path: target/debug/fluxer-ci path: target/debug/fluxer-ci
key: >- key: >-
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml', fluxer-ci-bin-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'Cargo.toml',
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }} 'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
- name: Build ci helper - name: Build ci helper
@@ -105,12 +105,12 @@ jobs:
run: cargo build --locked --package fluxer-ci run: cargo build --locked --package fluxer-ci
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
@@ -125,7 +125,7 @@ jobs:
fluxer_app/pkgs/libfluxcore fluxer_app/pkgs/libfluxcore
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
key: >- key: >-
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs', app-wasm-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts', 'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock', 'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**', 'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
@@ -144,7 +144,7 @@ jobs:
fluxer_app/pkgs/libfluxcore fluxer_app/pkgs/libfluxcore
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
key: >- key: >-
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs', app-wasm-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts', 'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock', 'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**', 'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
@@ -156,21 +156,21 @@ jobs:
timeout-minutes: 45 timeout-minutes: 45
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Install Rust toolchain - name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
components: clippy, rustfmt components: clippy, rustfmt
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Cache cargo - name: Cache cargo
@@ -185,7 +185,7 @@ jobs:
rust-${{ runner.os }}-${{ hashFiles('fluxer_media_proxy/tools/install-native-deps.sh') }}- rust-${{ runner.os }}-${{ hashFiles('fluxer_media_proxy/tools/install-native-deps.sh') }}-
- name: Install cargo-deny - name: Install cargo-deny
run: cargo install cargo-deny --version 0.19.6 --locked run: cargo install cargo-deny --version 0.20.2 --locked
- name: Check Rust dependencies - name: Check Rust dependencies
run: cargo deny --locked check -D warnings run: cargo deny --locked check -D warnings
@@ -273,12 +273,12 @@ jobs:
FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
- name: Cache cargo (gateway NIFs) - name: Cache cargo (gateway NIFs)
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
@@ -294,7 +294,7 @@ jobs:
with: with:
path: target/debug/fluxer-ci path: target/debug/fluxer-ci
key: >- key: >-
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml', fluxer-ci-bin-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'Cargo.toml',
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }} 'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
- name: Build ci helper - name: Build ci helper
@@ -305,7 +305,7 @@ jobs:
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124
with: with:
otp-version: '28' otp-version: '28'
rebar3-version: '3.24.0' rebar3-version: '3.27.0'
- name: Restore rebar3 dependencies - name: Restore rebar3 dependencies
id: rebar3-cache id: rebar3-cache
@@ -317,10 +317,10 @@ jobs:
!fluxer_gateway/_build/default/lib/fluxer_gateway/** !fluxer_gateway/_build/default/lib/fluxer_gateway/**
!fluxer_gateway/_build/test/lib/fluxer_gateway/** !fluxer_gateway/_build/test/lib/fluxer_gateway/**
key: >- key: >-
rebar3-${{ runner.os }}-otp28-rebar3.24.0-${{ hashFiles('fluxer_gateway/rebar.lock', rebar3-${{ runner.os }}-otp28-rebar3.27.0-${{ hashFiles('fluxer_gateway/rebar.lock',
'fluxer_gateway/rebar.config') }} 'fluxer_gateway/rebar.config') }}
restore-keys: | restore-keys: |
rebar3-${{ runner.os }}-otp28-rebar3.24.0- rebar3-${{ runner.os }}-otp28-rebar3.27.0-
- name: Check formatting - name: Check formatting
run: | run: |
@@ -348,7 +348,7 @@ jobs:
!fluxer_gateway/_build/default/lib/fluxer_gateway/** !fluxer_gateway/_build/default/lib/fluxer_gateway/**
!fluxer_gateway/_build/test/lib/fluxer_gateway/** !fluxer_gateway/_build/test/lib/fluxer_gateway/**
key: >- key: >-
rebar3-${{ runner.os }}-otp28-rebar3.24.0-${{ hashFiles('fluxer_gateway/rebar.lock', rebar3-${{ runner.os }}-otp28-rebar3.27.0-${{ hashFiles('fluxer_gateway/rebar.lock',
'fluxer_gateway/rebar.config') }} 'fluxer_gateway/rebar.config') }}
knip: knip:
@@ -358,12 +358,12 @@ jobs:
FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Rust toolchain (CI helpers) - name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
with: with:
toolchain: "1.93.0" toolchain: "1.98.1"
targets: wasm32-unknown-unknown targets: wasm32-unknown-unknown
- name: Restore ci helper - name: Restore ci helper
@@ -372,7 +372,7 @@ jobs:
with: with:
path: target/debug/fluxer-ci path: target/debug/fluxer-ci
key: >- key: >-
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml', fluxer-ci-bin-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'Cargo.toml',
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }} 'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
- name: Build ci helper - name: Build ci helper
@@ -380,12 +380,12 @@ jobs:
run: cargo build --locked --package fluxer-ci run: cargo build --locked --package fluxer-ci
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
@@ -400,7 +400,7 @@ jobs:
fluxer_app/pkgs/libfluxcore fluxer_app/pkgs/libfluxcore
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
key: >- key: >-
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs', app-wasm-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts', 'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock', 'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**', 'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
@@ -419,7 +419,7 @@ jobs:
fluxer_app/pkgs/libfluxcore fluxer_app/pkgs/libfluxcore
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
key: >- key: >-
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs', app-wasm-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts', 'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock', 'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**', 'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
@@ -431,15 +431,15 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
@@ -456,15 +456,15 @@ jobs:
timeout-minutes: 25 timeout-minutes: 25
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
@@ -490,15 +490,15 @@ jobs:
timeout-minutes: 15 timeout-minutes: 15
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
with: with:
node-version: '24' node-version: '26'
cache: 'pnpm' cache: 'pnpm'
- name: Install dependencies - name: Install dependencies
@@ -515,12 +515,12 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Set up Python - name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with: with:
python-version: "3.13" python-version: "3.14"
- name: Install font tooling - name: Install font tooling
run: python3 -m pip install -r tools/fonts/requirements.txt run: python3 -m pip install -r tools/fonts/requirements.txt
Generated
+827 -846
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -48,7 +48,7 @@
"linter": { "linter": {
"enabled": true, "enabled": true,
"rules": { "rules": {
"recommended": true, "preset": "recommended",
"complexity": { "complexity": {
"noForEach": "off", "noForEach": "off",
"noImportantStyles": "off", "noImportantStyles": "off",
@@ -83,6 +83,7 @@
} }
}, },
"useConst": "error", "useConst": "error",
"noDescendingSpecificity": "off",
"noNonNullAssertion": "off", "noNonNullAssertion": "off",
"noParameterAssign": "off", "noParameterAssign": "off",
"noRestrictedImports": { "noRestrictedImports": {
@@ -98,7 +99,7 @@
} }
}, },
"a11y": { "a11y": {
"recommended": true, "preset": "recommended",
"useAriaPropsForRole": "error", "useAriaPropsForRole": "error",
"useValidAriaRole": "error", "useValidAriaRole": "error",
"useValidAriaValues": "error", "useValidAriaValues": "error",
+18 -10
View File
@@ -79,34 +79,42 @@ deny = [
{ crate = "fuse-sys", reason = "libfuse2 FFI crate; Fluxer AppImages must not reintroduce libfuse2 through native Rust dependencies" }, { crate = "fuse-sys", reason = "libfuse2 FFI crate; Fluxer AppImages must not reintroduce libfuse2 through native Rust dependencies" },
] ]
skip = [ skip = [
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" }, { crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" }, { crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older crypto API" }, { crate = "[email protected]", reason = "transitive dependency requires the older crypto API" },
{ crate = "[email protected].7", reason = "transitive dependency requires the older digest API" }, { crate = "[email protected].6", reason = "transitive dependency requires the older digest API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" }, { crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older hashbrown API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older randomness API" }, { crate = "[email protected]", reason = "transitive dependency requires the older randomness API" },
{ crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" }, { crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older hashbrown API" }, { crate = "[email protected]", reason = "transitive dependency requires the older hashbrown API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older hashbrown API" },
{ crate = "[email protected]", reason = "transitive dependency requires the prior hashbrown API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" }, { crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older HTTP API" }, { crate = "[email protected]", reason = "transitive dependency requires the older HTTP API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older HTTP body API" }, { crate = "[email protected]", reason = "transitive dependency requires the older HTTP body API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older WASI API" }, { crate = "[email protected]", reason = "transitive dependency requires the older WASI API" },
{ crate = "[email protected].6", reason = "transitive dependency requires the older randomness API" }, { crate = "[email protected].8", reason = "transitive dependency requires the older randomness API" },
{ crate = "[email protected].4", reason = "transitive dependency requires the prior randomness API" }, { crate = "[email protected].5", reason = "transitive dependency requires the prior randomness API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older randomness API" }, { crate = "[email protected]", reason = "transitive dependency requires the older randomness API" },
{ crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" }, { crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older randomness API" }, { crate = "[email protected]", reason = "transitive dependency requires the older randomness API" },
{ crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" }, { crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" },
{ crate = "[email protected].6", reason = "transitive dependency requires the older digest API" }, { crate = "[email protected].7", reason = "transitive dependency requires the older digest API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" }, { crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
{ crate = "s[email protected]0", reason = "transitive dependency requires the older socket API" }, { crate = "s[email protected].0", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]+wasi-snapshot-preview1", reason = "transitive dependency requires the legacy WASI API" }, { crate = "[email protected]+wasi-snapshot-preview1", reason = "transitive dependency requires the legacy WASI API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
{ crate = "[email protected]", reason = "transitive dependency requires the older Windows API" }, { crate = "[email protected]", reason = "transitive dependency requires the older Windows API" },
{ crate = "[email protected]", reason = "transitive dependency requires the prior Windows API" },
{ crate = "[email protected]", reason = "transitive dependency requires the older WASI binding API" },
] ]
skip-tree = [] skip-tree = []
+7 -6
View File
@@ -122,7 +122,7 @@ x-fluxer-svc-healthcheck: &fluxer-svc-healthcheck
services: services:
edge: edge:
image: caddy:2.10-alpine image: caddy:2.11-alpine
deploy: deploy:
resources: resources:
limits: limits:
@@ -200,7 +200,7 @@ services:
retries: 10 retries: 10
valkey: valkey:
image: valkey/valkey:8.1-alpine image: valkey/valkey:9.1-alpine
deploy: deploy:
resources: resources:
limits: limits:
@@ -236,7 +236,7 @@ services:
retries: 10 retries: 10
meilisearch: meilisearch:
image: getmeili/meilisearch:v1.12 image: getmeili/meilisearch:v1.53
deploy: deploy:
resources: resources:
limits: limits:
@@ -246,6 +246,7 @@ services:
environment: environment:
MEILI_ENV: production MEILI_ENV: production
MEILI_NO_ANALYTICS: "true" MEILI_NO_ANALYTICS: "true"
MEILI_UPGRADE_DB: "true"
MEILI_MAX_INDEXING_MEMORY: ${FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY:-384mb} MEILI_MAX_INDEXING_MEMORY: ${FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY:-384mb}
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?set MEILI_MASTER_KEY in .env} MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?set MEILI_MASTER_KEY in .env}
volumes: volumes:
@@ -257,7 +258,7 @@ services:
retries: 10 retries: 10
seaweedfs: seaweedfs:
image: chrislusf/seaweedfs:4.34 image: chrislusf/seaweedfs:4.47
deploy: deploy:
resources: resources:
limits: limits:
@@ -266,7 +267,7 @@ services:
networks: [fluxer] networks: [fluxer]
environment: environment:
GOMEMLIMIT: ${FLUXER_SEAWEEDFS_GOMEMLIMIT:-1536MiB} GOMEMLIMIT: ${FLUXER_SEAWEEDFS_GOMEMLIMIT:-1536MiB}
command: ["server", "-s3", "-dir=/data"] command: ["server", "-s3", "-dir=/data", "-master.telemetry=false"]
volumes: volumes:
- seaweedfs-data:/data - seaweedfs-data:/data
healthcheck: healthcheck:
@@ -277,7 +278,7 @@ services:
start_period: 60s start_period: 60s
seaweedfs-init: seaweedfs-init:
image: chrislusf/seaweedfs:4.34 image: chrislusf/seaweedfs:4.47
deploy: deploy:
resources: resources:
limits: limits:
+13 -13
View File
@@ -9,32 +9,32 @@ build = "build.rs"
[dependencies] [dependencies]
anyhow = "1.0.104" anyhow = "1.0.104"
axum = { version = "0.8.9", features = ["macros"] } axum = { version = "0.8.9", features = ["macros"] }
base64 = "0.22.1" base64 = "0.23.1"
chrono = { version = "0.4", default-features = false, features = ["serde"] } chrono = { version = "0.4", default-features = false, features = ["serde"] }
cookie = "0.18.1" cookie = "0.18.2"
fluxer_common = { path = "../fluxer_common" } fluxer_common = { path = "../fluxer_common" }
hmac = "0.13.0" hmac = "0.13.0"
maud = { version = "0.27.0", features = ["axum"] } maud = { version = "0.27.0", features = ["axum"] }
rand = "0.10" rand = "0.10"
regress = "0.11" regress = "0.12"
reqwest = { version = "0.13.4", default-features = false, features = ["json", "rustls"] } reqwest = { version = "0.13.5", default-features = false, features = ["json", "rustls"] }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.150" serde_json = "1.0.151"
sha2 = "0.11.0" sha2 = "0.11.0"
time = { version = "0.3.47", features = ["formatting", "parsing"] } time = { version = "0.3.55", features = ["formatting", "parsing"] }
tokio = { version = "1.52.3", features = ["macros", "net", "rt-multi-thread", "signal"] } tokio = { version = "1.53.1", features = ["macros", "net", "rt-multi-thread", "signal"] }
tower = { version = "0.5.3", features = ["util"] } tower = { version = "0.5.3", features = ["util"] }
tower-http = { version = "0.6.11", features = ["compression-gzip", "trace"] } tower-http = { version = "0.7.1", features = ["compression-gzip", "trace"] }
tracing = "0.1.44" tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
url = "2.5" url = "2.5"
urlencoding = "2.1.3" urlencoding = "2.1.3"
progenitor-client = { version = "0.14.0", default-features = false } progenitor-client = { version = "0.15.0", default-features = false }
[build-dependencies] [build-dependencies]
openapiv3 = "2.2.0" openapiv3 = "2.2.0"
prettyplease = "0.2" prettyplease = "0.3"
progenitor = { version = "0.14.0", default-features = false } progenitor = { version = "0.15.0", default-features = false }
serde_json = "1" serde_json = "1"
sha2 = "0.11.0" sha2 = "0.11.0"
syn = "2" syn = "3"
+3 -3
View File
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
FROM rust:1-bookworm AS builder FROM rust:1-trixie AS builder
ARG BUILD_VERSION="" ARG BUILD_VERSION=""
ARG TARGETARCH ARG TARGETARCH
@@ -9,7 +9,7 @@ WORKDIR /usr/src/app
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates nodejs npm pkg-config \ && apt-get install -y --no-install-recommends ca-certificates nodejs npm pkg-config \
&& npm install -g pnpm@10.29.3 \ && npm install -g pnpm@12.4.2 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN npm install --no-audit --no-fund @tailwindcss/[email protected] [email protected] RUN npm install --no-audit --no-fund @tailwindcss/[email protected] [email protected]
@@ -57,7 +57,7 @@ RUN test "$(ls target/release/build/fluxer_admin-*/out/static/fonts/*.woff2 | wc
&& ls target/release/build/fluxer_admin-*/out/static/fonts/fonts.*.css \ && ls target/release/build/fluxer_admin-*/out/static/fonts/fonts.*.css \
&& echo "Latin-core fonts bundled successfully" && echo "Latin-core fonts bundled successfully"
FROM debian:bookworm-slim AS runtime FROM debian:trixie-slim AS runtime
ARG BUILD_VERSION="" ARG BUILD_VERSION=""
ARG SOURCE_SHA="" ARG SOURCE_SHA=""
+12 -2
View File
@@ -54,9 +54,9 @@ fn generate_admin_api(manifest_dir: &Path, out_dir: &Path) {
.generate_tokens(&spec) .generate_tokens(&spec)
.expect("failed to generate admin API client"); .expect("failed to generate admin API client");
let content = prettyplease::unparse( let content = relax_required_nullable_fields(&prettyplease::unparse(
&syn::parse2::<syn::File>(tokens).expect("failed to parse generated tokens"), &syn::parse2::<syn::File>(tokens).expect("failed to parse generated tokens"),
); ));
let output_path = out_dir.join("admin_api_generated.rs"); let output_path = out_dir.join("admin_api_generated.rs");
fs::write(&output_path, content).expect("failed to write generated API code"); fs::write(&output_path, content).expect("failed to write generated API code");
@@ -190,6 +190,16 @@ fn object_schema_mut<'a>(
const MAX_SCHEMA_REFERENCE_DEPTH: usize = 32; const MAX_SCHEMA_REFERENCE_DEPTH: usize = 32;
fn relax_required_nullable_fields(generated: &str) -> String {
const PRESENCE_CHECK: &str =
"#[serde(deserialize_with = \"::std::option::Option::deserialize\")]";
generated
.lines()
.filter(|line| line.trim() != PRESENCE_CHECK)
.flat_map(|line| [line, "\n"])
.collect()
}
fn relax_progenitor_schema_strictness(spec: &mut openapiv3::OpenAPI) { fn relax_progenitor_schema_strictness(spec: &mut openapiv3::OpenAPI) {
let registry = spec.components.clone().unwrap_or_default(); let registry = spec.components.clone().unwrap_or_default();
+54 -30
View File
@@ -10541,13 +10541,13 @@
"created_at": { "created_at": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"expires_at": { "expires_at": {
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"max_uses": {"nullable": true, "type": "integer", "minimum": 1, "maximum": 9007199254740991}, "max_uses": {"nullable": true, "type": "integer", "minimum": 1, "maximum": 9007199254740991},
"use_count": {"type": "integer", "minimum": 0, "maximum": 9007199254740991}, "use_count": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
@@ -10555,14 +10555,14 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"approval_required": {"type": "boolean"}, "approval_required": {"type": "boolean"},
"last_used_at": { "last_used_at": {
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"last_used_by_user_id": { "last_used_by_user_id": {
"nullable": true, "nullable": true,
@@ -10598,7 +10598,7 @@
"requested_at": { "requested_at": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"registration_url_id": {"nullable": true, "type": "string", "minLength": 1, "maxLength": 128}, "registration_url_id": {"nullable": true, "type": "string", "minLength": 1, "maxLength": 128},
"client_ip": {"nullable": true, "type": "string"} "client_ip": {"nullable": true, "type": "string"}
@@ -10855,12 +10855,14 @@
"min_lifetime_days": { "min_lifetime_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
"max_lifetime_days": { "max_lifetime_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
@@ -10868,12 +10870,14 @@
"renew_threshold_days": { "renew_threshold_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
"renew_window_days": { "renew_window_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
@@ -10884,15 +10888,31 @@
"min_size_mb": {"type": "number", "minimum": 0, "exclusiveMinimum": true}, "min_size_mb": {"type": "number", "minimum": 0, "exclusiveMinimum": true},
"max_size_mb": {"type": "number", "minimum": 0, "exclusiveMinimum": true}, "max_size_mb": {"type": "number", "minimum": 0, "exclusiveMinimum": true},
"max_eligible_size_mb": {"type": "number", "minimum": 0, "exclusiveMinimum": true}, "max_eligible_size_mb": {"type": "number", "minimum": 0, "exclusiveMinimum": true},
"min_lifetime_days": {"type": "integer", "exclusiveMinimum": true, "maximum": 9007199254740991}, "min_lifetime_days": {
"max_lifetime_days": {"type": "integer", "exclusiveMinimum": true, "maximum": 9007199254740991},
"curve": {"type": "number", "minimum": 0, "maximum": 1},
"renew_threshold_days": {
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
"renew_window_days": {"type": "integer", "exclusiveMinimum": true, "maximum": 9007199254740991} "max_lifetime_days": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true,
"maximum": 9007199254740991
},
"curve": {"type": "number", "minimum": 0, "maximum": 1},
"renew_threshold_days": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true,
"maximum": 9007199254740991
},
"renew_window_days": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true,
"maximum": 9007199254740991
}
}, },
"required": [ "required": [
"enabled", "enabled",
@@ -10949,7 +10969,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"max_uses": {"nullable": true, "type": "integer", "minimum": 1, "maximum": 1000000}, "max_uses": {"nullable": true, "type": "integer", "minimum": 1, "maximum": 1000000},
"approval_required": {"default": false, "type": "boolean"} "approval_required": {"default": false, "type": "boolean"}
@@ -10967,13 +10987,13 @@
"created_at": { "created_at": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"expires_at": { "expires_at": {
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"max_uses": {"nullable": true, "type": "integer", "minimum": 1, "maximum": 9007199254740991}, "max_uses": {"nullable": true, "type": "integer", "minimum": 1, "maximum": 9007199254740991},
"use_count": {"type": "integer", "minimum": 0, "maximum": 9007199254740991}, "use_count": {"type": "integer", "minimum": 0, "maximum": 9007199254740991},
@@ -10981,14 +11001,14 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"approval_required": {"type": "boolean"}, "approval_required": {"type": "boolean"},
"last_used_at": { "last_used_at": {
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"last_used_by_user_id": { "last_used_by_user_id": {
"nullable": true, "nullable": true,
@@ -11222,12 +11242,14 @@
"min_lifetime_days": { "min_lifetime_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
"max_lifetime_days": { "max_lifetime_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
@@ -11235,12 +11257,14 @@
"renew_threshold_days": { "renew_threshold_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
}, },
"renew_window_days": { "renew_window_days": {
"nullable": true, "nullable": true,
"type": "integer", "type": "integer",
"minimum": 0,
"exclusiveMinimum": true, "exclusiveMinimum": true,
"maximum": 9007199254740991 "maximum": 9007199254740991
} }
@@ -11272,7 +11296,7 @@
"properties": { "properties": {
"enabled": {"type": "boolean"}, "enabled": {"type": "boolean"},
"window_hours": {"type": "number", "minimum": 0, "exclusiveMinimum": true, "maximum": 8760}, "window_hours": {"type": "number", "minimum": 0, "exclusiveMinimum": true, "maximum": 8760},
"member_threshold": {"type": "integer", "exclusiveMinimum": true, "maximum": 1000000} "member_threshold": {"type": "integer", "minimum": 0, "exclusiveMinimum": true, "maximum": 1000000}
} }
} }
} }
@@ -12989,14 +13013,14 @@
"description": "ISO 8601 timestamp when the bot token was created", "description": "ISO 8601 timestamp when the bot token was created",
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"client_secret_created_at": { "client_secret_created_at": {
"nullable": true, "nullable": true,
"description": "ISO 8601 timestamp when the client secret was created", "description": "ISO 8601 timestamp when the client secret was created",
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"version": { "version": {
"description": "The optimistic locking version of the application record", "description": "The optimistic locking version of the application record",
@@ -13424,7 +13448,7 @@
"timestamp": { "timestamp": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
"description": "The ISO 8601 timestamp of when the message was created" "description": "The ISO 8601 timestamp of when the message was created"
}, },
"edited_timestamp": { "edited_timestamp": {
@@ -13432,7 +13456,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"pinned": {"type": "boolean", "description": "Whether the message is pinned"}, "pinned": {"type": "boolean", "description": "Whether the message is pinned"},
"mention_everyone": {"type": "boolean", "description": "Whether the message mentions @everyone"}, "mention_everyone": {"type": "boolean", "description": "Whether the message mentions @everyone"},
@@ -13532,7 +13556,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
} }
}, },
"required": ["participants"], "required": ["participants"],
@@ -13569,7 +13593,7 @@
"timestamp": { "timestamp": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
"description": "The ISO 8601 timestamp of when the message was created" "description": "The ISO 8601 timestamp of when the message was created"
}, },
"edited_timestamp": { "edited_timestamp": {
@@ -13577,7 +13601,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"pinned": {"type": "boolean", "description": "Whether the message is pinned"}, "pinned": {"type": "boolean", "description": "Whether the message is pinned"},
"mention_everyone": {"type": "boolean", "description": "Whether the message mentions @everyone"}, "mention_everyone": {"type": "boolean", "description": "Whether the message mentions @everyone"},
@@ -13677,7 +13701,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
} }
}, },
"required": ["participants"], "required": ["participants"],
@@ -13841,7 +13865,7 @@
"timestamp": { "timestamp": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
"description": "The ISO 8601 timestamp of when the original message was created" "description": "The ISO 8601 timestamp of when the original message was created"
}, },
"edited_timestamp": { "edited_timestamp": {
@@ -13849,7 +13873,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"mentions": { "mentions": {
"description": "The user IDs mentioned in the snapshot", "description": "The user IDs mentioned in the snapshot",
@@ -14018,7 +14042,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"description": {"description": "The description of the embed", "nullable": true, "type": "string"}, "description": {"description": "The description of the embed", "nullable": true, "type": "string"},
"author": { "author": {
@@ -14230,7 +14254,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"description": {"description": "The description of the embed", "nullable": true, "type": "string"}, "description": {"description": "The description of the embed", "nullable": true, "type": "string"},
"author": { "author": {
@@ -15102,7 +15126,7 @@
"joined_at": { "joined_at": {
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$", "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$",
"description": "ISO8601 timestamp of when the user joined the guild" "description": "ISO8601 timestamp of when the user joined the guild"
}, },
"mute": {"type": "boolean", "description": "Whether the member is muted in voice channels"}, "mute": {"type": "boolean", "description": "Whether the member is muted in voice channels"},
@@ -15112,7 +15136,7 @@
"nullable": true, "nullable": true,
"type": "string", "type": "string",
"format": "date-time", "format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$" "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
}, },
"profile_flags": {"nullable": true, "allOf": [{"$ref": "#/components/schemas/GuildMemberProfileFlags"}]}, "profile_flags": {"nullable": true, "allOf": [{"$ref": "#/components/schemas/GuildMemberProfileFlags"}]},
"mention_flags": { "mention_flags": {
+7 -5
View File
@@ -14,8 +14,8 @@ impl AdminApiClient {
audit_log_reason: Option<&str>, audit_log_reason: Option<&str>,
) -> ApiResult<BulkJobResponse> { ) -> ApiResult<BulkJobResponse> {
let body = generated_types::AdminBulkJobCreateRequest::UpdateUserFlags { let body = generated_types::AdminBulkJobCreateRequest::UpdateUserFlags {
add_flags: user_flags(add_flags), add_flags: user_flags(add_flags)?,
remove_flags: user_flags(remove_flags), remove_flags: user_flags(remove_flags)?,
user_ids: snowflakes(user_ids), user_ids: snowflakes(user_ids),
}; };
self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason) self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason)
@@ -112,11 +112,13 @@ fn snowflakes(values: &[String]) -> Vec<generated_types::SnowflakeType> {
values.iter().map(|value| snowflake(value)).collect() values.iter().map(|value| snowflake(value)).collect()
} }
fn user_flags(values: &[String]) -> Vec<generated_types::UserFlags> { fn user_flags(values: &[String]) -> ApiResult<Vec<generated_types::UserFlags>> {
values values
.iter() .iter()
.cloned() .map(|value| {
.map(generated_types::UserFlags::from) generated_types::UserFlags::try_from(value.as_str())
.map_err(|error| ApiError::Parse(error.to_string()))
})
.collect() .collect()
} }
+7 -5
View File
@@ -95,8 +95,8 @@ impl AdminApiClient {
remove_flags: &[String], remove_flags: &[String],
) -> ApiResult<AdminUser> { ) -> ApiResult<AdminUser> {
let body = generated_types::AdminUserFlagsUpdateRequest { let body = generated_types::AdminUserFlagsUpdateRequest {
add_flags: user_flags(add_flags), add_flags: user_flags(add_flags)?,
remove_flags: user_flags(remove_flags), remove_flags: user_flags(remove_flags)?,
}; };
let response = self let response = self
.generated() .generated()
@@ -567,11 +567,13 @@ fn bool_param(value: bool) -> &'static str {
if value { "true" } else { "false" } if value { "true" } else { "false" }
} }
fn user_flags(values: &[String]) -> Vec<generated_types::UserFlags> { fn user_flags(values: &[String]) -> ApiResult<Vec<generated_types::UserFlags>> {
values values
.iter() .iter()
.cloned() .map(|value| {
.map(generated_types::UserFlags::from) generated_types::UserFlags::try_from(value.as_str())
.map_err(|error| ApiError::Parse(error.to_string()))
})
.collect() .collect()
} }
+8 -12
View File
@@ -1,11 +1,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
ARG BUILD_VERSION ARG BUILD_VERSION
FROM node:24-bookworm-slim AS base FROM node:26-trixie-slim AS base
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN corepack enable && corepack prepare [email protected] --activate RUN npm install -g [email protected]
FROM base AS deploy FROM base AS deploy
@@ -23,9 +23,9 @@ COPY . .
RUN pnpm install --frozen-lockfile RUN pnpm install --frozen-lockfile
RUN pnpm --filter fluxer_api run build RUN pnpm --filter fluxer_api run build
RUN pnpm deploy --legacy --filter=fluxer_api --prod --config.allowUnusedPatches=true /out RUN pnpm deploy --legacy --filter=fluxer_api --prod --config.allow-unused-patches=true /out
FROM node:24-bookworm-slim FROM node:26-trixie-slim
ARG BUILD_VERSION ARG BUILD_VERSION
ARG SOURCE_SHA ARG SOURCE_SHA
@@ -45,32 +45,28 @@ LABEL app.fluxer.build-version="${BUILD_VERSION}"
WORKDIR /usr/src/app/fluxer_api WORKDIR /usr/src/app/fluxer_api
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' > /etc/apt/sources.list.d/backports.list && \ RUN apt-get update && apt-get install -y --no-install-recommends \
apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
ffmpeg \ ffmpeg \
libimage-exiftool-perl \ libimage-exiftool-perl \
libwebp7 \ libwebp7 \
libwebpmux3 \ libwebpmux3 \
libheif1 \ libheif1 \
libvips42 && \
apt-get install -y --no-install-recommends -t bookworm-backports \
libheif-plugin-libde265 \ libheif-plugin-libde265 \
libheif-plugin-dav1d && \ libheif-plugin-dav1d \
libvips42t64 && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
RUN corepack enable && corepack prepare [email protected] --activate RUN npm install -g [email protected]
COPY --from=deploy /out ./ COPY --from=deploy /out ./
COPY --from=deploy /usr/src/app/fluxer_api/dist ./dist COPY --from=deploy /usr/src/app/fluxer_api/dist ./dist
COPY --from=deploy /usr/src/app/tsconfigs /usr/src/app/tsconfigs COPY --from=deploy /usr/src/app/tsconfigs /usr/src/app/tsconfigs
RUN rm -rf pkgs && \ RUN rm -rf pkgs && \
mkdir -p /usr/src/app/.cache/corepack && \
chown -R 65532:65532 /usr/src/app chown -R 65532:65532 /usr/src/app
ENV HOME=/usr/src/app ENV HOME=/usr/src/app
ENV COREPACK_HOME=/usr/src/app/.cache/corepack
ENV NODE_ENV=production ENV NODE_ENV=production
ENV NODE_OPTIONS="--enable-source-maps" ENV NODE_OPTIONS="--enable-source-maps"
ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt ENV NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
+8 -7
View File
@@ -5,19 +5,19 @@
"scripts": { "scripts": {
"build": "node scripts/build.mjs", "build": "node scripts/build.mjs",
"test": "vitest run", "test": "vitest run",
"typecheck": "tsgo --noEmit", "typecheck": "tsc --noEmit",
"dev": "tsx watch --clear-screen=false src/AppEntrypoint.ts", "dev": "tsx watch --clear-screen=false src/AppEntrypoint.ts",
"start": "tsx src/AppEntrypoint.ts", "start": "tsx src/AppEntrypoint.ts",
"start:worker": "tsx src/WorkerEntrypoint.ts" "start:worker": "tsx src/WorkerEntrypoint.ts"
}, },
"dependencies": { "dependencies": {
"@atproto/api": "catalog:", "@atproto/api": "catalog:",
"@atproto/jwk-jose": "catalog:",
"@atproto/oauth-client-node": "catalog:",
"@aws-sdk/client-s3": "catalog:", "@aws-sdk/client-s3": "catalog:",
"@aws-sdk/lib-storage": "catalog:", "@aws-sdk/lib-storage": "catalog:",
"@aws-sdk/s3-request-presigner": "catalog:", "@aws-sdk/s3-request-presigner": "catalog:",
"@bluesky-social/jwk-jose": "catalog:", "@bufbuild/protobuf": "^2.15.0",
"@bluesky-social/oauth-client-node": "catalog:",
"@bufbuild/protobuf": "^2.12.0",
"@elastic/elasticsearch": "catalog:", "@elastic/elasticsearch": "catalog:",
"@fluxer/config": "workspace:*", "@fluxer/config": "workspace:*",
"@fluxer/constants": "workspace:*", "@fluxer/constants": "workspace:*",
@@ -34,6 +34,8 @@
"@hono/node-server": "catalog:", "@hono/node-server": "catalog:",
"@messageformat/core": "catalog:", "@messageformat/core": "catalog:",
"@messageformat/parser": "catalog:", "@messageformat/parser": "catalog:",
"@nats-io/jetstream": "catalog:",
"@nats-io/transport-node": "catalog:",
"@pkgs/cache": "workspace:*", "@pkgs/cache": "workspace:*",
"@pkgs/captcha": "workspace:*", "@pkgs/captcha": "workspace:*",
"@pkgs/cassandra": "workspace:*", "@pkgs/cassandra": "workspace:*",
@@ -71,7 +73,6 @@
"lodash": "catalog:", "lodash": "catalog:",
"maxmind": "catalog:", "maxmind": "catalog:",
"mime": "catalog:", "mime": "catalog:",
"nats": "catalog:",
"nodemailer": "catalog:", "nodemailer": "catalog:",
"pg": "catalog:", "pg": "catalog:",
"pino": "catalog:", "pino": "catalog:",
@@ -88,10 +89,10 @@
"devDependencies": { "devDependencies": {
"@types/archiver": "catalog:", "@types/archiver": "catalog:",
"@types/lodash": "catalog:", "@types/lodash": "catalog:",
"@typescript/native-preview": "catalog:",
"esbuild": "catalog:", "esbuild": "catalog:",
"msw": "catalog:", "msw": "catalog:",
"typescript": "catalog:ts7",
"vitest": "catalog:" "vitest": "catalog:"
}, },
"packageManager": "pnpm@10.29.3" "packageManager": "pnpm@12.4.2"
} }
+2 -2
View File
@@ -9,14 +9,14 @@
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@pkgs/kv_client": "workspace:*" "@pkgs/kv_client": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:", "typescript": "catalog:ts7",
"vitest": "catalog:" "vitest": "catalog:"
} }
} }
+2 -2
View File
@@ -7,7 +7,7 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/logger": "workspace:*", "@fluxer/logger": "workspace:*",
@@ -15,6 +15,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
+2 -2
View File
@@ -7,13 +7,13 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"cassandra-driver": "catalog:" "cassandra-driver": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
@@ -7,7 +7,7 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@elastic/elasticsearch": "catalog:", "@elastic/elasticsearch": "catalog:",
@@ -15,6 +15,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
+2 -3
View File
@@ -9,7 +9,7 @@
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/i18n": "workspace:*", "@fluxer/i18n": "workspace:*",
@@ -19,8 +19,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@types/nodemailer": "catalog:", "typescript": "catalog:ts7",
"@typescript/native-preview": "catalog:",
"vitest": "catalog:" "vitest": "catalog:"
} }
} }
@@ -2,7 +2,7 @@
import {createLogger} from '@fluxer/logger/src/Logger'; import {createLogger} from '@fluxer/logger/src/Logger';
import type {EmailMessage, IEmailProvider} from '@pkgs/email/src/EmailProviderTypes'; import type {EmailMessage, IEmailProvider} from '@pkgs/email/src/EmailProviderTypes';
import nodemailer from 'nodemailer'; import nodemailer, {type Transporter} from 'nodemailer';
const logger = createLogger('@pkgs/email/src/SmtpEmailProvider'); const logger = createLogger('@pkgs/email/src/SmtpEmailProvider');
@@ -18,7 +18,7 @@ interface SmtpEmailConfig {
} }
export class SmtpEmailProvider implements IEmailProvider { export class SmtpEmailProvider implements IEmailProvider {
private readonly transporter: nodemailer.Transporter; private readonly transporter: Transporter;
constructor(config: SmtpEmailConfig) { constructor(config: SmtpEmailConfig) {
this.transporter = nodemailer.createTransport({ this.transporter = nodemailer.createTransport({
+2 -2
View File
@@ -7,7 +7,7 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "catalog:", "@aws-sdk/client-s3": "catalog:",
@@ -21,6 +21,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
+1 -1
View File
@@ -67,7 +67,7 @@ interface GeoipRuntimePathOptions {
} }
export function parseGeoipSourceConfig(rawValue: string | undefined): GeoipSourceConfig { export function parseGeoipSourceConfig(rawValue: string | undefined): GeoipSourceConfig {
if (!rawValue || !rawValue.startsWith('s3://')) { if (!rawValue?.startsWith('s3://')) {
return createGeoipFilesystemSourceConfig(rawValue); return createGeoipFilesystemSourceConfig(rawValue);
} }
return parseGeoipS3SourceConfig(rawValue); return parseGeoipS3SourceConfig(rawValue);
+2 -3
View File
@@ -9,7 +9,7 @@
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/constants": "workspace:*", "@fluxer/constants": "workspace:*",
@@ -17,8 +17,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:", "typescript": "catalog:ts7",
"undici-types": "catalog:",
"vitest": "catalog:" "vitest": "catalog:"
} }
} }
@@ -16,6 +16,7 @@ import {
} from '@pkgs/http_client/src/HttpClientRequestInternals'; } from '@pkgs/http_client/src/HttpClientRequestInternals';
import type {HttpClientMetrics, HttpClientTelemetry} from '@pkgs/http_client/src/HttpClientTelemetryTypes'; import type {HttpClientMetrics, HttpClientTelemetry} from '@pkgs/http_client/src/HttpClientTelemetryTypes';
import type { import type {
FetchDispatcher,
HttpClient, HttpClient,
HttpClientFactoryOptions, HttpClientFactoryOptions,
HttpMethod, HttpMethod,
@@ -26,7 +27,6 @@ import type {
StreamResponse, StreamResponse,
} from '@pkgs/http_client/src/HttpClientTypes'; } from '@pkgs/http_client/src/HttpClientTypes';
import {HttpError} from '@pkgs/http_client/src/HttpError'; import {HttpError} from '@pkgs/http_client/src/HttpError';
import type {Dispatcher} from 'undici-types';
const DEFAULT_SERVICE_NAME = 'unknown'; const DEFAULT_SERVICE_NAME = 'unknown';
@@ -79,7 +79,7 @@ function createFetchInit(
headers: Headers, headers: Headers,
body: string | undefined, body: string | undefined,
signal: AbortSignal, signal: AbortSignal,
dispatcher: Dispatcher | undefined, dispatcher: FetchDispatcher | undefined,
): RequestInit { ): RequestInit {
return { return {
method, method,
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import type {HttpClientTelemetry} from '@pkgs/http_client/src/HttpClientTelemetryTypes'; import type {HttpClientTelemetry} from '@pkgs/http_client/src/HttpClientTelemetryTypes';
import type {Dispatcher} from 'undici-types';
export type ResponseStream = ReadableStream<Uint8Array> | null; export type ResponseStream = ReadableStream<Uint8Array> | null;
export type FetchDispatcher = NonNullable<RequestInit['dispatcher']>;
export type HttpMethod = 'GET' | 'POST' | 'HEAD' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'; export type HttpMethod = 'GET' | 'POST' | 'HEAD' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
export type RequestUrlValidationPhase = 'initial' | 'redirect'; export type RequestUrlValidationPhase = 'initial' | 'redirect';
@@ -14,7 +14,7 @@ export interface RequestUrlValidationContext {
} }
export interface RequestUrlPolicy { export interface RequestUrlPolicy {
readonly dispatcher?: Dispatcher; readonly dispatcher?: FetchDispatcher;
validate(url: URL, context: RequestUrlValidationContext): Promise<void>; validate(url: URL, context: RequestUrlValidationContext): Promise<void>;
} }
@@ -5,10 +5,13 @@ import dns from 'node:dns';
import type {LookupFunction} from 'node:net'; import type {LookupFunction} from 'node:net';
import {BlockList, isIP} from 'node:net'; import {BlockList, isIP} from 'node:net';
import {formatUrlForDiagnostics} from '@pkgs/http_client/src/HttpClientDiagnostics'; import {formatUrlForDiagnostics} from '@pkgs/http_client/src/HttpClientDiagnostics';
import type {RequestUrlPolicy, RequestUrlValidationContext} from '@pkgs/http_client/src/HttpClientTypes'; import type {
FetchDispatcher,
RequestUrlPolicy,
RequestUrlValidationContext,
} from '@pkgs/http_client/src/HttpClientTypes';
import {HttpError} from '@pkgs/http_client/src/HttpError'; import {HttpError} from '@pkgs/http_client/src/HttpError';
import {Agent} from 'undici'; import {Agent, Dispatcher1Wrapper} from 'undici';
import type {Dispatcher} from 'undici-types';
const DEFAULT_DNS_CACHE_TTL_MS = 60000; const DEFAULT_DNS_CACHE_TTL_MS = 60000;
const DNS_CACHE_MAX_ENTRIES = 10000; const DNS_CACHE_MAX_ENTRIES = 10000;
@@ -223,7 +226,7 @@ async function defaultLookupHost(hostname: string): Promise<Array<string>> {
return addresses.map((addressEntry) => addressEntry.address); return addresses.map((addressEntry) => addressEntry.address);
} }
function createBlocklistDispatcher(allowPrivateAddresses: boolean): Dispatcher { function createBlocklistDispatcher(allowPrivateAddresses: boolean): FetchDispatcher {
const lookup: LookupFunction = (hostname, options, callback) => { const lookup: LookupFunction = (hostname, options, callback) => {
dns.lookup(hostname, {...options, all: true, order: options.order ?? 'verbatim'}, (error, addresses) => { dns.lookup(hostname, {...options, all: true, order: options.order ?? 'verbatim'}, (error, addresses) => {
if (error) { if (error) {
@@ -246,15 +249,18 @@ function createBlocklistDispatcher(allowPrivateAddresses: boolean): Dispatcher {
callback(null, primary.address, primary.family); callback(null, primary.address, primary.family);
}); });
}; };
return new Agent({ return new Dispatcher1Wrapper(
connect: { new Agent({
lookup, allowH2: false,
}, connect: {
}) as unknown as Dispatcher; lookup,
},
}),
) as unknown as FetchDispatcher;
} }
interface PublicInternetRequestUrlPolicy extends RequestUrlPolicy { interface PublicInternetRequestUrlPolicy extends RequestUrlPolicy {
readonly dispatcher: Dispatcher; readonly dispatcher: FetchDispatcher;
} }
export function createPublicInternetRequestUrlPolicy( export function createPublicInternetRequestUrlPolicy(
+2 -2
View File
@@ -9,7 +9,7 @@
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/constants": "workspace:*", "@fluxer/constants": "workspace:*",
@@ -18,7 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:", "typescript": "catalog:ts7",
"vitest": "catalog:" "vitest": "catalog:"
} }
} }
@@ -295,6 +295,7 @@ export class KVClient implements IKVProvider {
connectTimeout: this.timeoutMs, connectTimeout: this.timeoutMs,
commandTimeout: this.timeoutMs, commandTimeout: this.timeoutMs,
maxRetriesPerRequest: 1, maxRetriesPerRequest: 1,
protocol: 2,
retryStrategy: createRetryStrategy(), retryStrategy: createRetryStrategy(),
}); });
} }
@@ -311,6 +312,7 @@ export class KVClient implements IKVProvider {
connectTimeout: clusterConfig.timeoutMs, connectTimeout: clusterConfig.timeoutMs,
commandTimeout: clusterConfig.timeoutMs, commandTimeout: clusterConfig.timeoutMs,
maxRetriesPerRequest: 1, maxRetriesPerRequest: 1,
protocol: 2,
}, },
scaleReads: 'master', scaleReads: 'master',
...(hasNatMap ? {natMap} : {}), ...(hasNatMap ? {natMap} : {}),
@@ -72,6 +72,7 @@ export class KVSubscription implements IKVSubscription {
connectTimeout: this.timeoutMs, connectTimeout: this.timeoutMs,
commandTimeout: this.timeoutMs, commandTimeout: this.timeoutMs,
maxRetriesPerRequest: 1, maxRetriesPerRequest: 1,
protocol: 2,
retryStrategy: createRetryStrategy(), retryStrategy: createRetryStrategy(),
}; };
const connection = this.mode === 'cluster' ? resolveKVClusterConnection(this.url, this.clusterNodes) : null; const connection = this.mode === 'cluster' ? resolveKVClusterConnection(this.url, this.clusterNodes) : null;
+2 -2
View File
@@ -9,14 +9,14 @@
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/constants": "workspace:*" "@fluxer/constants": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:", "typescript": "catalog:ts7",
"vitest": "catalog:" "vitest": "catalog:"
} }
} }
@@ -31,13 +31,13 @@
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@types/node": "catalog:" "@types/node": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@typescript/native-preview": "catalog:", "typescript": "catalog:ts7",
"vitest": "catalog:" "vitest": "catalog:"
} }
} }
+2 -2
View File
@@ -10,13 +10,13 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"mime": "catalog:" "mime": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
+4 -3
View File
@@ -7,13 +7,14 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"nats": "catalog:" "@nats-io/jetstream": "catalog:",
"@nats-io/transport-node": "catalog:"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import type {NatsConnection} from 'nats'; import type {NatsConnection} from '@nats-io/transport-node';
export interface INatsConnectionManager { export interface INatsConnectionManager {
connect(): Promise<void>; connect(): Promise<void>;
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {type JetStreamClient, type JetStreamManager, jetstream, jetstreamManager} from '@nats-io/jetstream';
import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager'; import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager';
import type {JetStreamClient, JetStreamManager} from 'nats';
export class JetStreamConnectionManager extends NatsConnectionManager { export class JetStreamConnectionManager extends NatsConnectionManager {
getJetStreamClient(): JetStreamClient { getJetStreamClient(): JetStreamClient {
return this.getConnection().jetstream(); return jetstream(this.getConnection());
} }
async getJetStreamManager(): Promise<JetStreamManager> { async getJetStreamManager(): Promise<JetStreamManager> {
return this.getConnection().jetstreamManager(); return jetstreamManager(this.getConnection());
} }
} }
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {connect, DrainingConnectionError, type NatsConnection} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import type {NatsConnectionOptions} from '@pkgs/nats/src/NatsConnectionOptions'; import type {NatsConnectionOptions} from '@pkgs/nats/src/NatsConnectionOptions';
import {connect, ErrorCode, type NatsConnection, NatsError} from 'nats';
const DEFAULT_MAX_RECONNECT_ATTEMPTS = -1; const DEFAULT_MAX_RECONNECT_ATTEMPTS = -1;
const DEFAULT_RECONNECT_TIME_WAIT_MS = 500; const DEFAULT_RECONNECT_TIME_WAIT_MS = 500;
@@ -46,7 +46,7 @@ export class NatsConnectionManager implements INatsConnectionManager {
}); });
await this.connectPromise; await this.connectPromise;
if (generation !== this.drainGeneration) { if (generation !== this.drainGeneration) {
throw NatsError.errorForCode(ErrorCode.ConnectionDraining); throw new DrainingConnectionError();
} }
} }
@@ -139,7 +139,7 @@ export class NatsConnectionManager implements INatsConnectionManager {
private assertNotDraining(): void { private assertNotDraining(): void {
if (this.drainPromise !== null) { if (this.drainPromise !== null) {
throw NatsError.errorForCode(ErrorCode.ConnectionDraining); throw new DrainingConnectionError();
} }
} }
+2 -2
View File
@@ -7,7 +7,7 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"pg": "catalog:" "pg": "catalog:"
@@ -15,6 +15,6 @@
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@types/pg": "catalog:", "@types/pg": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
+2 -2
View File
@@ -7,13 +7,13 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@pkgs/kv_client": "workspace:*" "@pkgs/kv_client": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
+2 -2
View File
@@ -9,7 +9,7 @@
"scripts": { "scripts": {
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest", "test:watch": "vitest",
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/constants": "workspace:*", "@fluxer/constants": "workspace:*",
@@ -18,7 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:", "typescript": "catalog:ts7",
"vitest": "catalog:" "vitest": "catalog:"
} }
} }
@@ -38,7 +38,7 @@ describe('TwilioSmsProvider', () => {
const fetchStub: typeof fetch = async (_input, init) => { const fetchStub: typeof fetch = async (_input, init) => {
capturedRequest = { capturedRequest = {
url: String(_input), url: String(_input),
authHeader: (init?.headers as Record<string, string>).Authorization, authHeader: (init?.headers as Record<string, string>)?.Authorization,
body: init?.body as string, body: init?.body as string,
}; };
return new Response(JSON.stringify({success: true}), {status: 200}); return new Response(JSON.stringify({success: true}), {status: 200});
@@ -65,7 +65,7 @@ describe('TwilioSmsProvider', () => {
const fetchStub: typeof fetch = async (_input, init) => { const fetchStub: typeof fetch = async (_input, init) => {
capturedRequest = { capturedRequest = {
url: String(_input), url: String(_input),
authHeader: (init?.headers as Record<string, string>).Authorization, authHeader: (init?.headers as Record<string, string>)?.Authorization,
body: init?.body as string, body: init?.body as string,
}; };
return new Response(JSON.stringify({channel: 'auto'}), {status: 200}); return new Response(JSON.stringify({channel: 'auto'}), {status: 200});
@@ -232,7 +232,7 @@ describe('TwilioSmsProvider', () => {
capturedRequest = { capturedRequest = {
url: String(input), url: String(input),
method: init?.method, method: init?.method,
authHeader: (init?.headers as Record<string, string>).Authorization, authHeader: (init?.headers as Record<string, string>)?.Authorization,
}; };
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
-1
View File
@@ -1,7 +1,6 @@
{ {
"extends": "../../../tsconfigs/package.json", "extends": "../../../tsconfigs/package.json",
"compilerOptions": { "compilerOptions": {
"types": ["node"],
"paths": { "paths": {
"@fluxer/*": ["../../../packages/*", "../../../packages/*/src/index.ts"], "@fluxer/*": ["../../../packages/*", "../../../packages/*/src/index.ts"],
"@pkgs/*": ["../*"] "@pkgs/*": ["../*"]
+2 -2
View File
@@ -7,7 +7,7 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/logger": "workspace:*", "@fluxer/logger": "workspace:*",
@@ -15,6 +15,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
+2 -2
View File
@@ -51,7 +51,7 @@
"./*": "./*" "./*": "./*"
}, },
"scripts": { "scripts": {
"typecheck": "tsgo --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@fluxer/constants": "workspace:*", "@fluxer/constants": "workspace:*",
@@ -60,6 +60,6 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "catalog:", "@types/node": "catalog:",
"@typescript/native-preview": "catalog:" "typescript": "catalog:ts7"
} }
} }
@@ -84,7 +84,7 @@ export class AdminAuditService {
}): Promise<AuditLogsListResponse> { }): Promise<AuditLogsListResponse> {
const auditLogSearchService = getAuditLogSearchService(); const auditLogSearchService = getAuditLogSearchService();
const targetIdBigInt = data.target_id ? BigInt(data.target_id) : undefined; const targetIdBigInt = data.target_id ? BigInt(data.target_id) : undefined;
if (!auditLogSearchService || !auditLogSearchService.isAvailable()) { if (!auditLogSearchService?.isAvailable()) {
return this.listAuditLogsFromDatabase({ return this.listAuditLogsFromDatabase({
adminUserId: data.admin_user_id, adminUserId: data.admin_user_id,
targetType: data.target_type, targetType: data.target_type,
@@ -129,7 +129,7 @@ export class AdminAuditService {
}): Promise<AuditLogsListResponse> { }): Promise<AuditLogsListResponse> {
const auditLogSearchService = getAuditLogSearchService(); const auditLogSearchService = getAuditLogSearchService();
const targetIdBigInt = data.target_id ? BigInt(data.target_id) : undefined; const targetIdBigInt = data.target_id ? BigInt(data.target_id) : undefined;
if (!auditLogSearchService || !auditLogSearchService.isAvailable()) { if (!auditLogSearchService?.isAvailable()) {
return this.listAuditLogsFromDatabase({ return this.listAuditLogsFromDatabase({
adminUserId: data.admin_user_id, adminUserId: data.admin_user_id,
targetType: data.target_type, targetType: data.target_type,
+2
View File
@@ -121,6 +121,7 @@ export async function generateWebAuthnRegistrationOptions(ctx: ApiContext, userI
userName: user.username!, userName: user.username!,
userDisplayName: user.username!, userDisplayName: user.username!,
attestationType: 'none', attestationType: 'none',
supportedAlgorithmIDs: [-8, -7, -257],
excludeCredentials: existingCredentials.map((cred) => ({ excludeCredentials: existingCredentials.map((cred) => ({
id: cred.credentialId, id: cred.credentialId,
transports: cred.transports transports: cred.transports
@@ -174,6 +175,7 @@ export async function verifyWebAuthnRegistration(
expectedOrigin, expectedOrigin,
expectedRPID: rpID, expectedRPID: rpID,
requireUserVerification: false, requireUserVerification: false,
supportedAlgorithmIDs: [-8, -7, -257],
}); });
} catch (error) { } catch (error) {
Logger.error({error, userId, expectedChallenge, rpID, expectedOrigin}, 'WebAuthn verification failed'); Logger.error({error, userId, expectedChallenge, rpID, expectedOrigin}, 'WebAuthn verification failed');
@@ -135,19 +135,14 @@ describe('reject reasons reaching the caller through the real gate', () => {
REJECT_REASON_CODES.invalid_number, REJECT_REASON_CODES.invalid_number,
); );
}); });
it.each([ it.each(['landline', 'tollFree', 'premium', 'sharedCost', 'uan', 'voicemail', 'pager'] as const)(
'landline', 'line_type_hard_rejected for %s says it is not a mobile',
'tollFree', async (lineType) => {
'premium', expect(await codeFromVerify(MOBILE_US, {lookupResult: lookup({lineType})})).toBe(
'sharedCost', REJECT_REASON_CODES.line_type_hard_rejected,
'uan', );
'voicemail', },
'pager', );
] as const)('line_type_hard_rejected for %s says it is not a mobile', async (lineType) => {
expect(await codeFromVerify(MOBILE_US, {lookupResult: lookup({lineType})})).toBe(
REJECT_REASON_CODES.line_type_hard_rejected,
);
});
it('sms_pumping_risk_high routes to human review', async () => { it('sms_pumping_risk_high routes to human review', async () => {
expect(await codeFromVerify(MOBILE_US, {lookupResult: lookup({smsPumpingRiskScore: 100})})).toBe( expect(await codeFromVerify(MOBILE_US, {lookupResult: lookup({smsPumpingRiskScore: 100})})).toBe(
REJECT_REASON_CODES.sms_pumping_risk_high, REJECT_REASON_CODES.sms_pumping_risk_high,
@@ -20,7 +20,7 @@ import type {
import type {BlueskyOAuthConfig, BlueskyOAuthKeyConfig} from '@app/api/config/APIConfig'; import type {BlueskyOAuthConfig, BlueskyOAuthKeyConfig} from '@app/api/config/APIConfig';
import {ConnectionCredentialRepository} from '@app/api/connection/ConnectionCredentialRepository'; import {ConnectionCredentialRepository} from '@app/api/connection/ConnectionCredentialRepository';
import {Agent} from '@atproto/api'; import {Agent} from '@atproto/api';
import {JoseKey} from '@bluesky-social/jwk-jose'; import {JoseKey} from '@atproto/jwk-jose';
import { import {
FetchError, FetchError,
NodeOAuthClient, NodeOAuthClient,
@@ -28,7 +28,7 @@ import {
type OAuthClientMetadataInput, type OAuthClientMetadataInput,
OAuthResponseError, OAuthResponseError,
requestLocalLock, requestLocalLock,
} from '@bluesky-social/oauth-client-node'; } from '@atproto/oauth-client-node';
import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider'; import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider';
interface BlueskyClientConfiguration { interface BlueskyClientConfiguration {
@@ -14,7 +14,7 @@ import {
type NodeSavedSessionStore, type NodeSavedSessionStore,
type NodeSavedState, type NodeSavedState,
type NodeSavedStateStore, type NodeSavedStateStore,
} from '@bluesky-social/oauth-client-node'; } from '@atproto/oauth-client-node';
import {SnowflakeType} from '@fluxer/schema/src/primitives/SchemaPrimitives'; import {SnowflakeType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider'; import type {IKVProvider} from '@pkgs/kv_client/src/IKVProvider';
import {z} from 'zod'; import {z} from 'zod';
@@ -107,7 +107,7 @@ export class MessageInteractionRepository extends IMessageInteractionRepository
async removeChannelPin(channelId: ChannelID, messageId: MessageID): Promise<void> { async removeChannelPin(channelId: ChannelID, messageId: MessageID): Promise<void> {
const message = await this.messageRepository.getMessage(channelId, messageId); const message = await this.messageRepository.getMessage(channelId, messageId);
if (!message || !message.pinnedTimestamp) { if (!message?.pinnedTimestamp) {
return; return;
} }
await deleteOneOrMany( await deleteOneOrMany(
@@ -617,7 +617,7 @@ export class ChannelOperationsService {
auditLogReason: string | null; auditLogReason: string | null;
}): Promise<void> { }): Promise<void> {
const channel = await this.channelRepository.channelData.findUnique(params.channelId); const channel = await this.channelRepository.channelData.findUnique(params.channelId);
if (!channel || !channel.guildId) throw new UnknownChannelError(); if (!channel?.guildId) throw new UnknownChannelError();
const canManageRoles = await this.gatewayService.checkPermission({ const canManageRoles = await this.gatewayService.checkPermission({
guildId: channel.guildId, guildId: channel.guildId,
userId: params.userId, userId: params.userId,
@@ -690,7 +690,7 @@ export class ChannelOperationsService {
auditLogReason: string | null; auditLogReason: string | null;
}): Promise<void> { }): Promise<void> {
const channel = await this.channelRepository.channelData.findUnique(params.channelId); const channel = await this.channelRepository.channelData.findUnique(params.channelId);
if (!channel || !channel.guildId) throw new UnknownChannelError(); if (!channel?.guildId) throw new UnknownChannelError();
const canManageRoles = await this.gatewayService.checkPermission({ const canManageRoles = await this.gatewayService.checkPermission({
guildId: channel.guildId, guildId: channel.guildId,
userId: params.userId, userId: params.userId,
@@ -115,7 +115,7 @@ export class MessageDeleteService {
}): Promise<void> { }): Promise<void> {
const channelId = webhook.channelId!; const channelId = webhook.channelId!;
const channel = await this.deps.channelRepository.channelData.findUnique(channelId); const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
if (!channel || !channel.guildId) { if (!channel?.guildId) {
throw new CannotExecuteOnDmError(); throw new CannotExecuteOnDmError();
} }
const message = await this.deps.channelRepository.messages.getMessage(channelId, messageId); const message = await this.deps.channelRepository.messages.getMessage(channelId, messageId);
@@ -312,7 +312,7 @@ function collectEmbedReferencedAttachmentCdnKeys(message: Message, ownKeys: Read
const mediaPrefix = `${Config.endpoints.media}/`; const mediaPrefix = `${Config.endpoints.media}/`;
const keys = new Set<string>(); const keys = new Set<string>();
const consider = (url: string | null | undefined): void => { const consider = (url: string | null | undefined): void => {
if (!url || !url.startsWith(mediaPrefix)) { if (!url?.startsWith(mediaPrefix)) {
return; return;
} }
const key = url.slice(mediaPrefix.length); const key = url.slice(mediaPrefix.length);
@@ -94,7 +94,7 @@ export class MessageMentionService {
referencedMessage?.authorId && referencedMessage?.authorId &&
referencedMessage.authorId !== message.authorId && referencedMessage.authorId !== message.authorId &&
!isDMChannel && !isDMChannel &&
(!allowedMentions || allowedMentions.replied_user !== false); allowedMentions?.replied_user !== false;
if (shouldAddReferencedUser) { if (shouldAddReferencedUser) {
userMentions.add(referencedMessage!.authorId!); userMentions.add(referencedMessage!.authorId!);
} }
@@ -569,7 +569,7 @@ export class MessagePersistenceService {
} }
const updatedSnapshots = message.messageSnapshots.map((snapshot, index) => { const updatedSnapshots = message.messageSnapshots.map((snapshot, index) => {
const edit = snapshotEdits[index]; const edit = snapshotEdits[index];
if (!edit || !edit.attachments || edit.attachments.length === 0) { if (!edit?.attachments || edit.attachments.length === 0) {
return snapshot.toMessageSnapshot(); return snapshot.toMessageSnapshot();
} }
const snapshotRow = snapshot.toMessageSnapshot(); const snapshotRow = snapshot.toMessageSnapshot();
@@ -80,7 +80,7 @@ export class MessageProcessingService {
requestCache: RequestCache; requestCache: RequestCache;
}): Promise<void> { }): Promise<void> {
if (channel.guildId || channel.type !== ChannelTypes.DM) return; if (channel.guildId || channel.type !== ChannelTypes.DM) return;
if (!channel.recipientIds || channel.recipientIds.size !== 2) return; if (channel.recipientIds?.size !== 2) return;
const recipientIds = Array.from(channel.recipientIds); const recipientIds = Array.from(channel.recipientIds);
const openStates = await this.batchCheckDmChannelOpen(recipientIds, channelId); const openStates = await this.batchCheckDmChannelOpen(recipientIds, channelId);
const closedRecipients = openStates.filter((state) => !state.isOpen); const closedRecipients = openStates.filter((state) => !state.isOpen);
@@ -7,8 +7,8 @@ import {
} from '@app/api/channel/services/message/MessageResponseDataService'; } from '@app/api/channel/services/message/MessageResponseDataService';
import {Message} from '@app/api/models/Message'; import {Message} from '@app/api/models/Message';
import {MessageTypes} from '@fluxer/constants/src/ChannelConstants'; import {MessageTypes} from '@fluxer/constants/src/ChannelConstants';
import type {NatsConnection} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import type {NatsConnection} from 'nats';
import {describe, expect, it} from 'vitest'; import {describe, expect, it} from 'vitest';
const encoder = new TextEncoder(); const encoder = new TextEncoder();
@@ -11,7 +11,9 @@ import {isJsonRecord, parseJsonRecord, parseJsonWithGuard} from '@app/api/utils/
import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas'; import type {MessageResponse} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager'; import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager';
import {StringCodec} from 'nats';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const MESSAGE_RESPONSE_SERVICE_SUBJECT = 'svc.messages'; const MESSAGE_RESPONSE_SERVICE_SUBJECT = 'svc.messages';
const MESSAGE_RESPONSE_SERVICE_TIMEOUT_MS = 6000; const MESSAGE_RESPONSE_SERVICE_TIMEOUT_MS = 6000;
@@ -79,8 +81,6 @@ function isMessageServiceResponse(value: unknown): value is MessageServiceRespon
} }
export class MessageResponseDataService { export class MessageResponseDataService {
private readonly codec = StringCodec();
constructor(private readonly connectionManager: INatsConnectionManager) {} constructor(private readonly connectionManager: INatsConnectionManager) {}
async listMessages(params: { async listMessages(params: {
@@ -307,10 +307,10 @@ export class MessageResponseDataService {
const connection = this.connectionManager.getConnection(); const connection = this.connectionManager.getConnection();
const response = await connection.request( const response = await connection.request(
MESSAGE_RESPONSE_SERVICE_SUBJECT, MESSAGE_RESPONSE_SERVICE_SUBJECT,
this.codec.encode(JSON.stringify(payload)), textEncoder.encode(JSON.stringify(payload)),
{timeout: MESSAGE_RESPONSE_SERVICE_TIMEOUT_MS}, {timeout: MESSAGE_RESPONSE_SERVICE_TIMEOUT_MS},
); );
const decoded = this.codec.decode(response.data); const decoded = textDecoder.decode(response.data);
const parsed = parseJsonWithGuard(decoded, isMessageServiceResponse); const parsed = parseJsonWithGuard(decoded, isMessageServiceResponse);
if (!parsed) { if (!parsed) {
throwForSvcErrorReply('message-response-service', parseJsonRecord(decoded)); throwForSvcErrorReply('message-response-service', parseJsonRecord(decoded));
@@ -1059,7 +1059,7 @@ export class MessageSendService {
}): Promise<Message> { }): Promise<Message> {
const channelId = webhook.channelId!; const channelId = webhook.channelId!;
const channel = await this.deps.channelRepository.channelData.findUnique(channelId); const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
if (!channel || !channel.guildId) { if (!channel?.guildId) {
throw new CannotExecuteOnDmError(); throw new CannotExecuteOnDmError();
} }
const guild = await this.deps.gatewayService.getGuildData({ const guild = await this.deps.gatewayService.getGuildData({
@@ -1245,7 +1245,7 @@ export class MessageSendService {
}): Promise<Message> { }): Promise<Message> {
const channelId = webhook.channelId!; const channelId = webhook.channelId!;
const channel = await this.deps.channelRepository.channelData.findUnique(channelId); const channel = await this.deps.channelRepository.channelData.findUnique(channelId);
if (!channel || !channel.guildId) { if (!channel?.guildId) {
throw new CannotExecuteOnDmError(); throw new CannotExecuteOnDmError();
} }
const existingMessage = await this.deps.channelRepository.messages.getMessage(channelId, messageId); const existingMessage = await this.deps.channelRepository.messages.getMessage(channelId, messageId);
@@ -13,7 +13,7 @@ import {executeConditional, fetchMany} from '@app/api/database/CassandraQueryExe
import {type ConditionalWriteEntry, Db, validateTtlSeconds} from '@app/api/database/CassandraTypes'; import {type ConditionalWriteEntry, Db, validateTtlSeconds} from '@app/api/database/CassandraTypes';
import {USER_CONNECTION_CREDENTIAL_TYPE, type UserConnectionStorageRow} from '@app/api/database/types/ConnectionTypes'; import {USER_CONNECTION_CREDENTIAL_TYPE, type UserConnectionStorageRow} from '@app/api/database/types/ConnectionTypes';
import {UserConnections} from '@app/api/Tables'; import {UserConnections} from '@app/api/Tables';
import {isAtprotoDid} from '@bluesky-social/oauth-client-node'; import {isAtprotoDid} from '@atproto/oauth-client-node';
import {z} from 'zod'; import {z} from 'zod';
type CredentialWrite = ConditionalWriteEntry<UserConnectionStorageRow, 'user_id' | 'connection_type' | 'connection_id'>; type CredentialWrite = ConditionalWriteEntry<UserConnectionStorageRow, 'user_id' | 'connection_type' | 'connection_id'>;
@@ -377,7 +377,7 @@ export class NcmecSubmissionService {
async finalizeAttachmentReport(attachmentId: AttachmentID, requeueCount = 0): Promise<void> { async finalizeAttachmentReport(attachmentId: AttachmentID, requeueCount = 0): Promise<void> {
const submission = await this.deps.ncmecRepository.getAttachmentSubmission(attachmentId); const submission = await this.deps.ncmecRepository.getAttachmentSubmission(attachmentId);
if (!submission || submission.status !== 'submitted' || submission.content_deleted_at) { if (submission?.status !== 'submitted' || submission.content_deleted_at) {
return; return;
} }
if (submission.user_id === null) { if (submission.user_id === null) {
@@ -439,7 +439,7 @@ export class NcmecSubmissionService {
submissionUserId, submissionUserId,
refreshedWorkflow.archive_id, refreshedWorkflow.archive_id,
); );
if (!archive || !archive.completed_at) { if (!archive?.completed_at) {
await this.requeueFinalizer(attachmentId, requeueCount + 1); await this.requeueFinalizer(attachmentId, requeueCount + 1);
return; return;
} }
@@ -113,8 +113,7 @@ export function parseDesktopReleaseDescriptor(value: unknown): DesktopReleaseDes
for (const rawAsset of value.assets) { for (const rawAsset of value.assets) {
const asset = parseDesktopReleaseAsset(rawAsset); const asset = parseDesktopReleaseAsset(rawAsset);
if ( if (
!asset || !asset?.storage_key.startsWith(expectedStoragePrefix) ||
!asset.storage_key.startsWith(expectedStoragePrefix) ||
!asset.release_asset.startsWith(expectedReleasePrefix) || !asset.release_asset.startsWith(expectedReleasePrefix) ||
storageKeys.has(asset.storage_key) storageKeys.has(asset.storage_key)
) { ) {
@@ -228,19 +228,22 @@ describe('desktop release readiness', () => {
it.each([ it.each([
['descriptor', `${RELEASES_PREFIX}/${V908}.json`], ['descriptor', `${RELEASES_PREFIX}/${V908}.json`],
['readiness marker', `${RELEASES_PREFIX}/${V908}.ready.json`], ['readiness marker', `${RELEASES_PREFIX}/${V908}.ready.json`],
])('offers the manifest version when reading its release %s fails with a storage error', async (_name, failingKey) => { ])(
const {service} = createService(incidentObjects(), (key) => { 'offers the manifest version when reading its release %s fails with a storage error',
if (key === failingKey) { async (_name, failingKey) => {
throw new S3ServiceException({ const {service} = createService(incidentObjects(), (key) => {
name: 'SlowDown', if (key === failingKey) {
$fault: 'server', throw new S3ServiceException({
$metadata: {httpStatusCode: 503}, name: 'SlowDown',
message: 'Please reduce your request rate.', $fault: 'server',
}); $metadata: {httpStatusCode: 503},
} message: 'Please reduce your request rate.',
}); });
await expect(resolveLatest(service)).resolves.toEqual(latestOf(V908)); }
}); });
await expect(resolveLatest(service)).resolves.toEqual(latestOf(V908));
},
);
it('still resolves the unpublished version through versioned routes', async () => { it('still resolves the unpublished version through versioned routes', async () => {
const objects: StoredObjects = new Map(); const objects: StoredObjects = new Map();
+5 -4
View File
@@ -15,7 +15,9 @@ import {
} from '@fluxer/schema/src/domains/gif/GifSchemas'; } from '@fluxer/schema/src/domains/gif/GifSchemas';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager'; import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager';
import {StringCodec} from 'nats';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const GIF_SERVICE_SUBJECT = process.env.FLUXER_GIF_SERVICE_SUBJECT || 'svc.gifs'; const GIF_SERVICE_SUBJECT = process.env.FLUXER_GIF_SERVICE_SUBJECT || 'svc.gifs';
const DEFAULT_GIF_SERVICE_TIMEOUT_MS = 12_000; const DEFAULT_GIF_SERVICE_TIMEOUT_MS = 12_000;
@@ -137,7 +139,6 @@ function readResolved(value: unknown): GifResponse | null {
class NatsGifProvider implements IGifProvider { class NatsGifProvider implements IGifProvider {
readonly meta = GIF_PROVIDER_META; readonly meta = GIF_PROVIDER_META;
private readonly codec = StringCodec();
constructor( constructor(
private readonly connectionManager: INatsConnectionManager, private readonly connectionManager: INatsConnectionManager,
@@ -256,8 +257,8 @@ class NatsGifProvider implements IGifProvider {
await this.connectionManager.connect(); await this.connectionManager.connect();
} }
const connection = this.connectionManager.getConnection(); const connection = this.connectionManager.getConnection();
const response = await connection.request(this.subject, this.codec.encode(JSON.stringify(payload)), {timeout}); const response = await connection.request(this.subject, textEncoder.encode(JSON.stringify(payload)), {timeout});
const decoded = this.codec.decode(response.data); const decoded = textDecoder.decode(response.data);
const parsed = parseJsonUnknown(decoded); const parsed = parseJsonUnknown(decoded);
const failedMessage = readFailedMessage(parsed); const failedMessage = readFailedMessage(parsed);
if (failedMessage) { if (failedMessage) {
@@ -99,7 +99,7 @@ export function GuildMemberSearchController(app: HonoApp) {
return ctx.json(createEmptySearchResponse(guildIdString, false)); return ctx.json(createEmptySearchResponse(guildIdString, false));
} }
const searchService = getGuildMemberSearchService(); const searchService = getGuildMemberSearchService();
if (!searchService || !searchService.isAvailable()) { if (!searchService?.isAvailable()) {
return ctx.json(createEmptySearchResponse(guildIdString, false)); return ctx.json(createEmptySearchResponse(guildIdString, false));
} }
const needsIndexing = guildMembersNeedReindexing(guild.membersIndexedAt); const needsIndexing = guildMembersNeedReindexing(guild.membersIndexedAt);
@@ -157,7 +157,7 @@ export class EmojiService {
const sourceEmoji = await this.guildRepository.getEmojiById(sourceEmojiId); const sourceEmoji = await this.guildRepository.getEmojiById(sourceEmojiId);
if (!sourceEmoji) throw new UnknownGuildEmojiError(); if (!sourceEmoji) throw new UnknownGuildEmojiError();
const sourceGuild = await this.guildRepository.findUnique(sourceEmoji.guildId); const sourceGuild = await this.guildRepository.findUnique(sourceEmoji.guildId);
if (!sourceGuild || !sourceGuild.features.has(GuildFeatures.CLONE_EMOJI_ENABLED)) { if (!sourceGuild?.features.has(GuildFeatures.CLONE_EMOJI_ENABLED)) {
throw new MissingAccessError(); throw new MissingAccessError();
} }
const guildData = await this.contentHelpers.getGuildData({userId: user.id, guildId}); const guildData = await this.contentHelpers.getGuildData({userId: user.id, guildId});
@@ -171,7 +171,7 @@ export class StickerService {
const sourceSticker = await this.guildRepository.getStickerById(sourceStickerId); const sourceSticker = await this.guildRepository.getStickerById(sourceStickerId);
if (!sourceSticker) throw new UnknownGuildStickerError(); if (!sourceSticker) throw new UnknownGuildStickerError();
const sourceGuild = await this.guildRepository.findUnique(sourceSticker.guildId); const sourceGuild = await this.guildRepository.findUnique(sourceSticker.guildId);
if (!sourceGuild || !sourceGuild.features.has(GuildFeatures.CLONE_STICKER_ENABLED)) { if (!sourceGuild?.features.has(GuildFeatures.CLONE_STICKER_ENABLED)) {
throw new MissingAccessError(); throw new MissingAccessError();
} }
const guildData = await this.contentHelpers.getGuildData({userId: user.id, guildId}); const guildData = await this.contentHelpers.getGuildData({userId: user.id, guildId});
@@ -343,21 +343,20 @@ describe('collectGuildAuditLogUserIds', () => {
expect(userIdStrings(log)).toEqual([ACTOR_ID]); expect(userIdStrings(log)).toEqual([ACTOR_ID]);
}); });
it.each([ it.each([AuditLogActionType.WEBHOOK_DELETE, AuditLogActionType.EMOJI_DELETE, AuditLogActionType.STICKER_DELETE])(
AuditLogActionType.WEBHOOK_DELETE, 'includes the creator of deleted content for action %i',
AuditLogActionType.EMOJI_DELETE, (actionType) => {
AuditLogActionType.STICKER_DELETE, const log = makeLog({
])('includes the creator of deleted content for action %i', (actionType) => { actionType,
const log = makeLog({ targetId: TARGET_ID,
actionType, changes: [
targetId: TARGET_ID, {key: 'name', old_value: 'blob'},
changes: [ {key: 'creator_id', old_value: OTHER_ID},
{key: 'name', old_value: 'blob'}, ],
{key: 'creator_id', old_value: OTHER_ID}, });
], expect(userIdStrings(log)).toEqual([ACTOR_ID, OTHER_ID]);
}); },
expect(userIdStrings(log)).toEqual([ACTOR_ID, OTHER_ID]); );
});
it.each(OVERWRITE_ACTIONS)('includes member overwrite targets for action %i', (actionType) => { it.each(OVERWRITE_ACTIONS)('includes member overwrite targets for action %i', (actionType) => {
const memberLog = makeLog({actionType, targetId: TARGET_ID, options: {type: '1', channel_id: OTHER_ID}}); const memberLog = makeLog({actionType, targetId: TARGET_ID, options: {type: '1', channel_id: OTHER_ID}});
@@ -99,7 +99,7 @@ function createRoomServiceClient(endpoint: string, apiKey: string, apiSecret: st
const httpUrl = toHttpUrl(endpoint); const httpUrl = toHttpUrl(endpoint);
const parsed = new URL(httpUrl); const parsed = new URL(httpUrl);
const pathPrefix = parsed.pathname.replace(/\/+$/, ''); const pathPrefix = parsed.pathname.replace(/\/+$/, '');
const client = new RoomServiceClient(parsed.origin, apiKey, apiSecret); const client = new RoomServiceClient(parsed.origin, apiKey, apiSecret, {requestTimeout: 60});
if (pathPrefix) { if (pathPrefix) {
const rpc = Reflect.get(client, 'rpc'); const rpc = Reflect.get(client, 'rpc');
if (rpc != null && typeof rpc === 'object' && 'prefix' in rpc) { if (rpc != null && typeof rpc === 'object' && 'prefix' in rpc) {
@@ -3,8 +3,11 @@
import {GatewayRpcMethodError, GatewayRpcMethodErrorCodes} from '@app/api/infrastructure/GatewayRpcError'; import {GatewayRpcMethodError, GatewayRpcMethodErrorCodes} from '@app/api/infrastructure/GatewayRpcError';
import type {IGatewayRpcTransport} from '@app/api/infrastructure/IGatewayRpcTransport'; import type {IGatewayRpcTransport} from '@app/api/infrastructure/IGatewayRpcTransport';
import {Logger} from '@app/api/Logger'; import {Logger} from '@app/api/Logger';
import {type Msg, RequestError, TimeoutError} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {type Msg, StringCodec} from 'nats';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const NATS_REQUEST_TIMEOUT_MS = 5000; const NATS_REQUEST_TIMEOUT_MS = 5000;
const NATS_SUBJECT_PREFIX = 'rpc.gateway.'; const NATS_SUBJECT_PREFIX = 'rpc.gateway.';
@@ -15,26 +18,11 @@ interface NatsRpcResponse {
error?: string; error?: string;
} }
const NATS_NO_RESPONDERS_CODE = '503';
const NATS_TIMEOUT_CODE = 'TIMEOUT';
function getErrorCode(error: Error): string | null {
if (!('code' in error)) {
return null;
}
const code = error.code;
return typeof code === 'string' ? code : null;
}
function mapNatsRpcTransportError(error: unknown): GatewayRpcMethodError | null { function mapNatsRpcTransportError(error: unknown): GatewayRpcMethodError | null {
if (!(error instanceof Error)) { if (error instanceof RequestError && error.isNoResponders()) {
return null;
}
const code = getErrorCode(error);
if (code === NATS_NO_RESPONDERS_CODE || error.message === 'NO_RESPONDERS' || error.name === 'NoRespondersError') {
return new GatewayRpcMethodError(GatewayRpcMethodErrorCodes.NO_RESPONDERS); return new GatewayRpcMethodError(GatewayRpcMethodErrorCodes.NO_RESPONDERS);
} }
if (code === NATS_TIMEOUT_CODE || error.message === 'TIMEOUT' || error.name === 'TimeoutError') { if (error instanceof TimeoutError) {
return new GatewayRpcMethodError(GatewayRpcMethodErrorCodes.TIMEOUT); return new GatewayRpcMethodError(GatewayRpcMethodErrorCodes.TIMEOUT);
} }
return null; return null;
@@ -64,7 +52,6 @@ function decodeNatsRpcResponse(responseText: string): NatsRpcResponse {
export class NatsGatewayRpcTransport implements IGatewayRpcTransport { export class NatsGatewayRpcTransport implements IGatewayRpcTransport {
private readonly connectionManager: INatsConnectionManager; private readonly connectionManager: INatsConnectionManager;
private readonly codec = StringCodec();
constructor(connectionManager: INatsConnectionManager) { constructor(connectionManager: INatsConnectionManager) {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
@@ -72,7 +59,7 @@ export class NatsGatewayRpcTransport implements IGatewayRpcTransport {
async call(method: string, params: Record<string, unknown>): Promise<unknown> { async call(method: string, params: Record<string, unknown>): Promise<unknown> {
const subject = `${NATS_SUBJECT_PREFIX}${method}`; const subject = `${NATS_SUBJECT_PREFIX}${method}`;
const payload = this.codec.encode(JSON.stringify(params)); const payload = textEncoder.encode(JSON.stringify(params));
let responseMsg: Msg; let responseMsg: Msg;
try { try {
if (this.connectionManager.isClosed()) { if (this.connectionManager.isClosed()) {
@@ -90,7 +77,7 @@ export class NatsGatewayRpcTransport implements IGatewayRpcTransport {
} }
throw error; throw error;
} }
const responseText = this.codec.decode(responseMsg.data); const responseText = textDecoder.decode(responseMsg.data);
const response = decodeNatsRpcResponse(responseText); const response = decodeNatsRpcResponse(responseText);
if (!response.ok) { if (!response.ok) {
throw new GatewayRpcMethodError(response.error ?? GatewayRpcMethodErrorCodes.INTERNAL_ERROR); throw new GatewayRpcMethodError(response.error ?? GatewayRpcMethodErrorCodes.INTERNAL_ERROR);
@@ -4,8 +4,8 @@ import {NatsUnfurlerService} from '@app/api/infrastructure/NatsUnfurlerService';
import {BadGatewayError} from '@fluxer/errors/src/domains/core/BadGatewayError'; import {BadGatewayError} from '@fluxer/errors/src/domains/core/BadGatewayError';
import {GatewayTimeoutError} from '@fluxer/errors/src/domains/core/GatewayTimeoutError'; import {GatewayTimeoutError} from '@fluxer/errors/src/domains/core/GatewayTimeoutError';
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError'; import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
import {type NatsConnection, NoRespondersError, RequestError, TimeoutError} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {type NatsConnection, StringCodec} from 'nats';
import {describe, expect, it} from 'vitest'; import {describe, expect, it} from 'vitest';
interface FakeRequest { interface FakeRequest {
@@ -16,12 +16,7 @@ interface FakeRequest {
const RESOLVED_REPLY = JSON.stringify({Resolved: {embeds: [], cache_ttl_seconds: null}}); const RESOLVED_REPLY = JSON.stringify({Resolved: {embeds: [], cache_ttl_seconds: null}});
function natsErrorWithCode(code: string): Error {
return Object.assign(new Error('nats request failed'), {code});
}
class FakeNatsConnectionManager implements INatsConnectionManager { class FakeNatsConnectionManager implements INatsConnectionManager {
private readonly codec = StringCodec();
private closed = true; private closed = true;
readonly requests: Array<FakeRequest> = []; readonly requests: Array<FakeRequest> = [];
connectCalls = 0; connectCalls = 0;
@@ -44,14 +39,14 @@ class FakeNatsConnectionManager implements INatsConnectionManager {
request: async (subject: string, data: Uint8Array, options?: {timeout?: number}) => { request: async (subject: string, data: Uint8Array, options?: {timeout?: number}) => {
this.requests.push({ this.requests.push({
subject, subject,
body: JSON.parse(this.codec.decode(data)) as Record<string, unknown>, body: JSON.parse(new TextDecoder().decode(data)) as Record<string, unknown>,
timeout: options?.timeout, timeout: options?.timeout,
}); });
if (this.requestError) { if (this.requestError) {
throw this.requestError; throw this.requestError;
} }
return { return {
data: this.codec.encode(this.replyText), data: new TextEncoder().encode(this.replyText),
}; };
}, },
} as unknown as NatsConnection; } as unknown as NatsConnection;
@@ -112,14 +107,17 @@ describe('NatsUnfurlerService', () => {
}); });
it('rejects with a gateway timeout error when the request times out', async () => { it('rejects with a gateway timeout error when the request times out', async () => {
const manager = new FakeNatsConnectionManager(RESOLVED_REPLY, natsErrorWithCode('TIMEOUT')); const manager = new FakeNatsConnectionManager(RESOLVED_REPLY, new TimeoutError());
const service = new NatsUnfurlerService(manager); const service = new NatsUnfurlerService(manager);
await expect(service.unfurlWithCachePolicy('https://example.com')).rejects.toBeInstanceOf(GatewayTimeoutError); await expect(service.unfurlWithCachePolicy('https://example.com')).rejects.toBeInstanceOf(GatewayTimeoutError);
}); });
it('rejects with a service unavailable error when no responders answer', async () => { it('rejects with a service unavailable error when no responders answer', async () => {
const manager = new FakeNatsConnectionManager(RESOLVED_REPLY, natsErrorWithCode('503')); const manager = new FakeNatsConnectionManager(
RESOLVED_REPLY,
new RequestError("no responders: 'svc.unfurl'", {cause: new NoRespondersError('svc.unfurl')}),
);
const service = new NatsUnfurlerService(manager); const service = new NatsUnfurlerService(manager);
await expect(service.unfurlWithCachePolicy('https://example.com')).rejects.toBeInstanceOf(ServiceUnavailableError); await expect(service.unfurlWithCachePolicy('https://example.com')).rejects.toBeInstanceOf(ServiceUnavailableError);
@@ -10,14 +10,15 @@ import {GatewayTimeoutError} from '@fluxer/errors/src/domains/core/GatewayTimeou
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError'; import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
import {FluxerError} from '@fluxer/errors/src/FluxerError'; import {FluxerError} from '@fluxer/errors/src/FluxerError';
import type {MessageEmbedResponse} from '@fluxer/schema/src/domains/message/EmbedSchemas'; import type {MessageEmbedResponse} from '@fluxer/schema/src/domains/message/EmbedSchemas';
import {RequestError, TimeoutError} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {StringCodec} from 'nats';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const NATS_UNFURL_SUBJECT = 'svc.unfurl'; const NATS_UNFURL_SUBJECT = 'svc.unfurl';
const NATS_UNFURL_TIMEOUT_MS = 12000; const NATS_UNFURL_TIMEOUT_MS = 12000;
const NATS_UNFURL_CACHE_ONLY_TIMEOUT_MS = 1000; const NATS_UNFURL_CACHE_ONLY_TIMEOUT_MS = 1000;
const NATS_NO_RESPONDERS_CODE = '503';
const NATS_TIMEOUT_CODE = 'TIMEOUT';
interface NatsUnfurlRequest { interface NatsUnfurlRequest {
op: 'Unfurl'; op: 'Unfurl';
@@ -64,14 +65,10 @@ function mapUnfurlTransportError(error: unknown): Error {
if (error instanceof FluxerError) { if (error instanceof FluxerError) {
return error; return error;
} }
if (!(error instanceof Error)) { if (error instanceof RequestError && error.isNoResponders()) {
return new BadGatewayError({message: '[nats-unfurl] request failed'});
}
const code = 'code' in error && typeof error.code === 'string' ? error.code : null;
if (code === NATS_NO_RESPONDERS_CODE || error.message === 'NO_RESPONDERS' || error.name === 'NoRespondersError') {
return new ServiceUnavailableError({message: '[nats-unfurl] no unfurl service is answering'}); return new ServiceUnavailableError({message: '[nats-unfurl] no unfurl service is answering'});
} }
if (code === NATS_TIMEOUT_CODE || error.message === 'TIMEOUT' || error.name === 'TimeoutError') { if (error instanceof TimeoutError) {
return new GatewayTimeoutError({message: '[nats-unfurl] unfurl service did not answer in time'}); return new GatewayTimeoutError({message: '[nats-unfurl] unfurl service did not answer in time'});
} }
return new BadGatewayError({message: '[nats-unfurl] request failed'}); return new BadGatewayError({message: '[nats-unfurl] request failed'});
@@ -79,7 +76,6 @@ function mapUnfurlTransportError(error: unknown): Error {
export class NatsUnfurlerService extends IUnfurlerService { export class NatsUnfurlerService extends IUnfurlerService {
private readonly connectionManager: INatsConnectionManager; private readonly connectionManager: INatsConnectionManager;
private readonly codec = StringCodec();
constructor( constructor(
connectionManager: INatsConnectionManager, connectionManager: INatsConnectionManager,
@@ -109,11 +105,11 @@ export class NatsUnfurlerService extends IUnfurlerService {
await this.connectionManager.connect(); await this.connectionManager.connect();
} }
const connection = this.connectionManager.getConnection(); const connection = this.connectionManager.getConnection();
const payload = this.codec.encode(JSON.stringify(request)); const payload = textEncoder.encode(JSON.stringify(request));
const responseMsg = await connection.request(NATS_UNFURL_SUBJECT, payload, { const responseMsg = await connection.request(NATS_UNFURL_SUBJECT, payload, {
timeout: options.cacheOnly === true ? NATS_UNFURL_CACHE_ONLY_TIMEOUT_MS : NATS_UNFURL_TIMEOUT_MS, timeout: options.cacheOnly === true ? NATS_UNFURL_CACHE_ONLY_TIMEOUT_MS : NATS_UNFURL_TIMEOUT_MS,
}); });
const responseText = this.codec.decode(responseMsg.data); const responseText = textDecoder.decode(responseMsg.data);
const response = parseJsonWithGuard(responseText, isNatsUnfurlResponse); const response = parseJsonWithGuard(responseText, isNatsUnfurlResponse);
if (!response) { if (!response) {
throwForSvcErrorReply('nats-unfurl', parseJsonRecord(responseText)); throwForSvcErrorReply('nats-unfurl', parseJsonRecord(responseText));
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {SnowflakeService} from '@app/api/infrastructure/SnowflakeService'; import {SnowflakeService} from '@app/api/infrastructure/SnowflakeService';
import type {NatsConnection} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {type NatsConnection, StringCodec} from 'nats';
import {afterEach, describe, expect, it} from 'vitest'; import {afterEach, describe, expect, it} from 'vitest';
interface FakeRequest { interface FakeRequest {
@@ -18,7 +18,6 @@ interface FakeRequest {
type FakeBatch = Array<string> | {error: string}; type FakeBatch = Array<string> | {error: string};
class FakeNatsConnectionManager implements INatsConnectionManager { class FakeNatsConnectionManager implements INatsConnectionManager {
private readonly codec = StringCodec();
private closed = true; private closed = true;
private readonly batches: Array<FakeBatch>; private readonly batches: Array<FakeBatch>;
readonly requests: Array<FakeRequest> = []; readonly requests: Array<FakeRequest> = [];
@@ -38,12 +37,12 @@ class FakeNatsConnectionManager implements INatsConnectionManager {
} }
return { return {
request: async (subject: string, data: Uint8Array, options?: {timeout?: number}) => { request: async (subject: string, data: Uint8Array, options?: {timeout?: number}) => {
const body = JSON.parse(this.codec.decode(data)) as FakeRequest['body']; const body = JSON.parse(new TextDecoder().decode(data)) as FakeRequest['body'];
this.requests.push({subject, body, timeout: options?.timeout}); this.requests.push({subject, body, timeout: options?.timeout});
const batch = this.batches.shift() ?? []; const batch = this.batches.shift() ?? [];
const response = Array.isArray(batch) ? {ids: batch} : batch; const response = Array.isArray(batch) ? {ids: batch} : batch;
return { return {
data: this.codec.encode(JSON.stringify(response)), data: new TextEncoder().encode(JSON.stringify(response)),
}; };
}, },
} as unknown as NatsConnection; } as unknown as NatsConnection;
@@ -7,7 +7,9 @@ import {requireIntegerInRange} from '@app/api/utils/IntegerOptions';
import {isJsonRecord, parseJsonWithGuard} from '@app/api/utils/JsonBoundaryUtils'; import {isJsonRecord, parseJsonWithGuard} from '@app/api/utils/JsonBoundaryUtils';
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError'; import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {StringCodec} from 'nats';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const DEFAULT_REMOTE_SUBJECT = 'svc.snowflakes'; const DEFAULT_REMOTE_SUBJECT = 'svc.snowflakes';
const DEFAULT_REMOTE_BATCH_SIZE = 128; const DEFAULT_REMOTE_BATCH_SIZE = 128;
@@ -72,7 +74,6 @@ export class SnowflakeService implements ISnowflakeService {
private readonly lowWatermark: number; private readonly lowWatermark: number;
private readonly requestTimeoutMs: number; private readonly requestTimeoutMs: number;
private readonly maxBufferAgeMs: number; private readonly maxBufferAgeMs: number;
private readonly codec = StringCodec();
private phase: 'idle' | 'starting' | 'ready' | 'stopping' | 'stopped' = 'idle'; private phase: 'idle' | 'starting' | 'ready' | 'stopping' | 'stopped' = 'idle';
private buffer: Array<bigint> = []; private buffer: Array<bigint> = [];
private bufferOffset = 0; private bufferOffset = 0;
@@ -272,14 +273,14 @@ export class SnowflakeService implements ISnowflakeService {
if (routingKey) { if (routingKey) {
request.routing_key = routingKey; request.routing_key = routingKey;
} }
const responseMessage = await connection.request(this.subject, this.codec.encode(JSON.stringify(request)), { const responseMessage = await connection.request(this.subject, textEncoder.encode(JSON.stringify(request)), {
timeout: this.requestTimeoutMs, timeout: this.requestTimeoutMs,
}); });
this.assertActive(); this.assertActive();
if (responseMessage.data.byteLength > MAX_REMOTE_RESPONSE_BYTES) { if (responseMessage.data.byteLength > MAX_REMOTE_RESPONSE_BYTES) {
throw new Error('Snowflake service response exceeds the byte limit'); throw new Error('Snowflake service response exceeds the byte limit');
} }
const response = parseJsonWithGuard(this.codec.decode(responseMessage.data), isRemoteSnowflakeResponse); const response = parseJsonWithGuard(textDecoder.decode(responseMessage.data), isRemoteSnowflakeResponse);
if (!response) { if (!response) {
throw new Error('Snowflake service returned an invalid response'); throw new Error('Snowflake service returned an invalid response');
} }
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {Logger} from '@app/api/Logger'; import {Logger} from '@app/api/Logger';
import {DiscardPolicy, JetStreamApiError, RetentionPolicy, StorageType} from '@nats-io/jetstream';
import {nanos} from '@nats-io/transport-node';
import {JetStreamConnectionManager} from '@pkgs/nats/src/JetStreamConnectionManager'; import {JetStreamConnectionManager} from '@pkgs/nats/src/JetStreamConnectionManager';
import {DiscardPolicy, NatsError, nanos, RetentionPolicy, StorageType} from 'nats';
export type StorageChangeOp = 'put' | 'delete'; export type StorageChangeOp = 'put' | 'delete';
@@ -68,10 +69,7 @@ function subjectToken(bucket: string): string {
} }
function jsErrorCode(error: unknown): number | null { function jsErrorCode(error: unknown): number | null {
if (!(error instanceof NatsError)) { return error instanceof JetStreamApiError ? error.code : null;
return null;
}
return error.jsError()?.err_code ?? null;
} }
export class StorageChangeFeed { export class StorageChangeFeed {
@@ -3,8 +3,8 @@
import {createUserID} from '@app/api/BrandedTypes'; import {createUserID} from '@app/api/BrandedTypes';
import {NatsUsersServiceClient} from '@app/api/infrastructure/UsersServiceClient'; import {NatsUsersServiceClient} from '@app/api/infrastructure/UsersServiceClient';
import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas'; import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import type {NatsConnection} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {type NatsConnection, StringCodec} from 'nats';
import {describe, expect, it} from 'vitest'; import {describe, expect, it} from 'vitest';
interface FakeRequest { interface FakeRequest {
@@ -14,7 +14,6 @@ interface FakeRequest {
} }
class FakeNatsConnectionManager implements INatsConnectionManager { class FakeNatsConnectionManager implements INatsConnectionManager {
private readonly codec = StringCodec();
private closed = true; private closed = true;
private readonly responses: Array<unknown>; private readonly responses: Array<unknown>;
readonly requests: Array<FakeRequest> = []; readonly requests: Array<FakeRequest> = [];
@@ -38,7 +37,7 @@ class FakeNatsConnectionManager implements INatsConnectionManager {
request: async (subject: string, data: Uint8Array, options?: {timeout?: number}) => { request: async (subject: string, data: Uint8Array, options?: {timeout?: number}) => {
this.requests.push({ this.requests.push({
subject, subject,
body: JSON.parse(this.codec.decode(data)) as Record<string, unknown>, body: JSON.parse(new TextDecoder().decode(data)) as Record<string, unknown>,
timeout: options?.timeout, timeout: options?.timeout,
}); });
const response = this.responses.shift(); const response = this.responses.shift();
@@ -46,7 +45,7 @@ class FakeNatsConnectionManager implements INatsConnectionManager {
throw response; throw response;
} }
return { return {
data: this.codec.encode(JSON.stringify(response)), data: new TextEncoder().encode(JSON.stringify(response)),
}; };
}, },
} as unknown as NatsConnection; } as unknown as NatsConnection;
@@ -10,7 +10,9 @@ import {isJsonRecord, parseJsonRecord, parseJsonWithGuard} from '@app/api/utils/
import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas'; import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager'; import {NatsConnectionManager} from '@pkgs/nats/src/NatsConnectionManager';
import {StringCodec} from 'nats';
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
const USERS_SERVICE_SUBJECT = process.env.FLUXER_USERS_SERVICE_SUBJECT || 'svc.users'; const USERS_SERVICE_SUBJECT = process.env.FLUXER_USERS_SERVICE_SUBJECT || 'svc.users';
const DEFAULT_USERS_SERVICE_TIMEOUT_MS = 6000; const DEFAULT_USERS_SERVICE_TIMEOUT_MS = 6000;
@@ -49,7 +51,6 @@ function isUserPartialsResponse(value: unknown): value is UserPartialsResponse {
} }
export class NatsUsersServiceClient implements IUsersServiceClient { export class NatsUsersServiceClient implements IUsersServiceClient {
private readonly codec = StringCodec();
private readonly inflightPartials = new Map<UserID, PendingUserPartials>(); private readonly inflightPartials = new Map<UserID, PendingUserPartials>();
constructor( constructor(
@@ -160,10 +161,10 @@ export class NatsUsersServiceClient implements IUsersServiceClient {
await this.connectionManager.connect(); await this.connectionManager.connect();
} }
const connection = this.connectionManager.getConnection(); const connection = this.connectionManager.getConnection();
const response = await connection.request(this.subject, this.codec.encode(JSON.stringify(payload)), { const response = await connection.request(this.subject, textEncoder.encode(JSON.stringify(payload)), {
timeout: this.requestTimeoutMs, timeout: this.requestTimeoutMs,
}); });
const decoded = this.codec.decode(response.data); const decoded = textDecoder.decode(response.data);
const parsed = parseJsonWithGuard(decoded, responseGuard); const parsed = parseJsonWithGuard(decoded, responseGuard);
if (parsed === null) { if (parsed === null) {
throwForSvcErrorReply('users-service', parseJsonRecord(decoded)); throwForSvcErrorReply('users-service', parseJsonRecord(decoded));
@@ -5,8 +5,8 @@ import {
GatewayRolloutConfigPublisher, GatewayRolloutConfigPublisher,
} from '@app/api/instance/GatewayRolloutConfigPublisher'; } from '@app/api/instance/GatewayRolloutConfigPublisher';
import type {GatewayRolloutConfig} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas'; import type {GatewayRolloutConfig} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas';
import type {NatsConnection} from '@nats-io/transport-node';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {type NatsConnection, StringCodec} from 'nats';
import {describe, expect, it} from 'vitest'; import {describe, expect, it} from 'vitest';
interface FakePublish { interface FakePublish {
@@ -15,7 +15,6 @@ interface FakePublish {
} }
class FakeNatsConnectionManager implements INatsConnectionManager { class FakeNatsConnectionManager implements INatsConnectionManager {
private readonly codec = StringCodec();
private closed = true; private closed = true;
readonly publishes: Array<FakePublish> = []; readonly publishes: Array<FakePublish> = [];
connectCalls = 0; connectCalls = 0;
@@ -34,7 +33,7 @@ class FakeNatsConnectionManager implements INatsConnectionManager {
publish: (subject: string, data: Uint8Array) => { publish: (subject: string, data: Uint8Array) => {
this.publishes.push({ this.publishes.push({
subject, subject,
body: JSON.parse(this.codec.decode(data)) as Record<string, unknown>, body: JSON.parse(new TextDecoder().decode(data)) as Record<string, unknown>,
}); });
}, },
flush: async () => { flush: async () => {
@@ -2,7 +2,8 @@
import type {GatewayRolloutConfig} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas'; import type {GatewayRolloutConfig} from '@fluxer/schema/src/domains/admin/GatewayRolloutSchemas';
import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager'; import type {INatsConnectionManager} from '@pkgs/nats/src/INatsConnectionManager';
import {StringCodec} from 'nats';
const textEncoder = new TextEncoder();
export const GATEWAY_ROLLOUT_CONFIG_NATS_SUBJECT = 'config.gateway.rollout'; export const GATEWAY_ROLLOUT_CONFIG_NATS_SUBJECT = 'config.gateway.rollout';
@@ -12,8 +13,6 @@ interface GatewayRolloutConfigNatsMessage {
} }
export class GatewayRolloutConfigPublisher { export class GatewayRolloutConfigPublisher {
private readonly codec = StringCodec();
constructor(private readonly connectionManager: INatsConnectionManager) {} constructor(private readonly connectionManager: INatsConnectionManager) {}
async publish(config: GatewayRolloutConfig): Promise<void> { async publish(config: GatewayRolloutConfig): Promise<void> {
@@ -25,7 +24,7 @@ export class GatewayRolloutConfigPublisher {
type: 'gateway_rollout_config', type: 'gateway_rollout_config',
config, config,
}; };
connection.publish(GATEWAY_ROLLOUT_CONFIG_NATS_SUBJECT, this.codec.encode(JSON.stringify(message))); connection.publish(GATEWAY_ROLLOUT_CONFIG_NATS_SUBJECT, textEncoder.encode(JSON.stringify(message)));
await connection.flush(); await connection.flush();
} }
} }
@@ -18,7 +18,7 @@ function ensureBearerScope(ctx: Context<HonoEnv>, scope: OAuth2Scope, mode: OAut
return false; return false;
} }
const oauthScopes = ctx.get('oauthBearerScopes'); const oauthScopes = ctx.get('oauthBearerScopes');
if (!oauthScopes || !oauthScopes.has(scope)) { if (!oauthScopes?.has(scope)) {
throw new MissingOAuthScopeError(scope); throw new MissingOAuthScopeError(scope);
} }
return true; return true;
@@ -52,46 +52,44 @@ describe('ResponseTypeMiddleware', () => {
expect(await response.json()).toEqual({id: '123456789012345678'}); expect(await response.json()).toEqual({id: '123456789012345678'});
}); });
test.each([ test.each([undefined, 'application/json', 'application/json; charset=utf-8', 'Application/JSON; charset=utf-8'])(
undefined, 'rejects mismatching responses while validation is enabled (content type: %s)',
'application/json', async (responseContentType) => {
'application/json; charset=utf-8', const app = new Hono<HonoEnv>();
'Application/JSON; charset=utf-8', const middleware =
])('rejects mismatching responses while validation is enabled (content type: %s)', async (responseContentType) => { responseContentType === undefined
const app = new Hono<HonoEnv>(); ? ResponseType(SnowflakeResponse)
const middleware = : OpenAPI({
responseContentType === undefined operationId: 'get_invalid_snowflake_test',
? ResponseType(SnowflakeResponse) summary: 'Get invalid snowflake',
: OpenAPI({ description: 'Returns an invalid snowflake to verify JSON response validation.',
operationId: 'get_invalid_snowflake_test', responseSchema: SnowflakeResponse,
summary: 'Get invalid snowflake', responseContentType,
description: 'Returns an invalid snowflake to verify JSON response validation.', tags: ['Tests'],
responseSchema: SnowflakeResponse, });
responseContentType, app.get('/snowflake', middleware, (ctx) => ctx.json({id: 'not-a-snowflake'}));
tags: ['Tests'], const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
});
app.get('/snowflake', middleware, (ctx) => ctx.json({id: 'not-a-snowflake'}));
const errorLoggerSpy = vi.spyOn(Logger.prototype, 'error').mockImplementation(() => {});
try { try {
const response = await app.request('/snowflake'); const response = await app.request('/snowflake');
expect(response.status).toBe(500); expect(response.status).toBe(500);
expect(errorLoggerSpy).toHaveBeenCalledTimes(1); expect(errorLoggerSpy).toHaveBeenCalledTimes(1);
expect(errorLoggerSpy).toHaveBeenCalledWith( expect(errorLoggerSpy).toHaveBeenCalledWith(
{ {
body: {id: 'not-a-snowflake'}, body: {id: 'not-a-snowflake'},
method: 'GET', method: 'GET',
path: '/snowflake', path: '/snowflake',
status: 200, status: 200,
validationErrors: [{message: 'INVALID_SNOWFLAKE_FORMAT', path: 'id'}], validationErrors: [{message: 'INVALID_SNOWFLAKE_FORMAT', path: 'id'}],
}, },
'Response validation failed', 'Response validation failed',
); );
} finally { } finally {
errorLoggerSpy.mockRestore(); errorLoggerSpy.mockRestore();
} }
}); },
);
test('passes the response through untouched while validation is disabled', async () => { test('passes the response through untouched while validation is disabled', async () => {
Config.dev.validateResponses = false; Config.dev.validateResponses = false;
+1 -1
View File
@@ -35,7 +35,7 @@ export class BotAuthService {
} }
const {applicationId, secret} = parsed; const {applicationId, secret} = parsed;
const application = await this.applicationRepository.getApplication(applicationId); const application = await this.applicationRepository.getApplication(applicationId);
if (!application || !application.hasBotUser() || !application.botTokenHash) { if (!application?.hasBotUser() || !application.botTokenHash) {
return null; return null;
} }
try { try {
@@ -246,7 +246,7 @@ export class OAuth2RequestService {
try { try {
const applicationId = createApplicationID(BigInt(params.body.client_id)); const applicationId = createApplicationID(BigInt(params.body.client_id));
const application = await this.applicationRepository.getApplication(applicationId); const application = await this.applicationRepository.getApplication(applicationId);
if (!application || !application.botUserId) { if (!application?.botUserId) {
throw new NotABotApplicationError(); throw new NotABotApplicationError();
} }
const botUserId = application.botUserId; const botUserId = application.botUserId;
@@ -454,7 +454,7 @@ export class OAuth2RequestService {
createApplicationID(params.applicationId), createApplicationID(params.applicationId),
); );
const application = await this.applicationRepository.getApplication(createApplicationID(params.applicationId)); const application = await this.applicationRepository.getApplication(createApplicationID(params.applicationId));
if (!application || !application.botUserId) { if (!application?.botUserId) {
throw new BotUserNotFoundError(); throw new BotUserNotFoundError();
} }
const botUser = await this.apiContext.services.users.findUnique(application.botUserId); const botUser = await this.apiContext.services.users.findUnique(application.botUserId);
+1 -1
View File
@@ -333,7 +333,7 @@ export class OAuth2Service {
async userInfo(accessToken: string) { async userInfo(accessToken: string) {
const token = await this.tokens.getAccessToken(accessToken); const token = await this.tokens.getAccessToken(accessToken);
if (!token || !token.userId) { if (!token?.userId) {
throw new InvalidTokenError(); throw new InvalidTokenError();
} }
const application = await this.applications.getApplication(token.applicationId); const application = await this.applications.getApplication(token.applicationId);
File diff suppressed because it is too large Load Diff
@@ -519,7 +519,7 @@ function scoreUserAgent(signals: RiskSignals): Array<ScoreContribution> {
function scoreGeoLocale(signals: RiskSignals): Array<ScoreContribution> { function scoreGeoLocale(signals: RiskSignals): Array<ScoreContribution> {
const geo = signals.localeGeoMatch; const geo = signals.localeGeoMatch;
if (!geo || !geo.mismatchDetected) return []; if (!geo?.mismatchDetected) return [];
return [ return [
{ {
rule: RULE.geoMismatch, rule: RULE.geoMismatch,
@@ -606,7 +606,7 @@ function applySharedConnectionDampener(
function scoreTiming(signals: RiskSignals): Array<ScoreContribution> { function scoreTiming(signals: RiskSignals): Array<ScoreContribution> {
const timing = signals.registrationTiming; const timing = signals.registrationTiming;
if (!timing || !timing.isSuspiciousHour) return []; if (!timing?.isSuspiciousHour) return [];
return [ return [
{ {
rule: RULE.suspiciousHour, rule: RULE.suspiciousHour,
@@ -101,7 +101,7 @@ describe('IpInfoService caching', () => {
expect(result.available).toBe(false); expect(result.available).toBe(false);
expect(sets[0]?.ttlSeconds).toBe(60); expect(sets[0]?.ttlSeconds).toBe(60);
expect((sets[0]?.value as CachedIpInfoFailure).failureOutcome).toBe('request_failed'); expect((sets[0]?.value as CachedIpInfoFailure)?.failureOutcome).toBe('request_failed');
}); });
it('negative-caches a schema mismatch', async () => { it('negative-caches a schema mismatch', async () => {
@@ -113,8 +113,8 @@ describe('IpInfoService caching', () => {
expect(result.available).toBe(false); expect(result.available).toBe(false);
expect(sets[0]?.ttlSeconds).toBe(600); expect(sets[0]?.ttlSeconds).toBe(600);
expect((sets[0]?.value as CachedIpInfoFailure).failureOutcome).toBe('schema_mismatch'); expect((sets[0]?.value as CachedIpInfoFailure)?.failureOutcome).toBe('schema_mismatch');
expect((sets[0]?.value as CachedIpInfoFailure).failureHttpStatus).toBe(200); expect((sets[0]?.value as CachedIpInfoFailure)?.failureHttpStatus).toBe(200);
}); });
it('negative-caches a quota rejection for longer', async () => { it('negative-caches a quota rejection for longer', async () => {
+1 -1
View File
@@ -958,7 +958,7 @@ export class RpcService {
const loadUserDataStartedAtNs = startRpcTiming(); const loadUserDataStartedAtNs = startRpcTiming();
const userData = await this.getUserData({userId, includePrivateChannels: true, timingSteps: loadUserDataSteps}); const userData = await this.getUserData({userId, includePrivateChannels: true, timingSteps: loadUserDataSteps});
timings.record('load_user_data', loadUserDataStartedAtNs, loadUserDataSteps); timings.record('load_user_data', loadUserDataStartedAtNs, loadUserDataSteps);
if (!userData || !userData.user) { if (!userData?.user) {
Logger.warn( Logger.warn(
{ {
tokenType, tokenType,
@@ -659,35 +659,33 @@ describe('Message Search Permissions', () => {
expect(user12Messages.length).toBeGreaterThan(0); expect(user12Messages.length).toBeGreaterThan(0);
expect(user34Messages.length).toBe(0); expect(user34Messages.length).toBe(0);
}); });
test.each([ test.each(['all_dms', 'open_dms', 'all', 'open_dms_and_all_guilds'] as const)(
'all_dms', 'scope: %s does not trust foreign DM context_channel_id',
'open_dms', async (scope) => {
'all', const user1 = await createTestAccount(harness);
'open_dms_and_all_guilds', const user2 = await createTestAccount(harness);
] as const)('scope: %s does not trust foreign DM context_channel_id', async (scope) => { const attacker = await createTestAccount(harness);
const user1 = await createTestAccount(harness); await createFriendship(harness, user1, user2);
const user2 = await createTestAccount(harness); const dmChannel = await createDmChannel(harness, user1.token, user2.userId);
const attacker = await createTestAccount(harness); const canary = `foreign-dm-context-canary-${Date.now()}-${scope}`;
await createFriendship(harness, user1, user2); await sendChannelMessage(harness, user1.token, dmChannel.id, canary);
const dmChannel = await createDmChannel(harness, user1.token, user2.userId); await markChannelAsIndexed(harness, dmChannel.id);
const canary = `foreign-dm-context-canary-${Date.now()}-${scope}`; const result = await createBuilder<MessageSearchResponse>(harness, attacker.token)
await sendChannelMessage(harness, user1.token, dmChannel.id, canary); .post('/search/messages')
await markChannelAsIndexed(harness, dmChannel.id); .body({
const result = await createBuilder<MessageSearchResponse>(harness, attacker.token) content: canary,
.post('/search/messages') scope,
.body({ context_channel_id: dmChannel.id,
content: canary, })
scope, .expect(HTTP_STATUS.OK)
context_channel_id: dmChannel.id, .execute();
}) if (!isSearchResult(result)) {
.expect(HTTP_STATUS.OK) expect.fail('Expected search result but got indexing response');
.execute(); }
if (!isSearchResult(result)) { expect(result.messages.some((message) => message.channel_id === dmChannel.id)).toBe(false);
expect.fail('Expected search result but got indexing response'); expect(result.messages.some((message) => message.content === canary)).toBe(false);
} },
expect(result.messages.some((message) => message.channel_id === dmChannel.id)).toBe(false); );
expect(result.messages.some((message) => message.content === canary)).toBe(false);
});
test('scope: all_dms does not trust a guild context_channel_id', async () => { test('scope: all_dms does not trust a guild context_channel_id', async () => {
const owner = await createTestAccount(harness); const owner = await createTestAccount(harness);
const attacker = await createTestAccount(harness); const attacker = await createTestAccount(harness);
@@ -59,7 +59,7 @@ describe('Message Search Referenced Message', () => {
try { try {
await createBuilder(harness, token).post('/search/messages').body(body).expect(HTTP_STATUS.OK).execute(); await createBuilder(harness, token).post('/search/messages').body(body).expect(HTTP_STATUS.OK).execute();
const [invocation] = spy.mock.results; const [invocation] = spy.mock.results;
if (!invocation || invocation.type !== 'return') { if (invocation?.type !== 'return') {
throw new Error('SearchService.searchMessages did not return a result'); throw new Error('SearchService.searchMessages did not return a result');
} }
return await invocation.value; return await invocation.value;
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
export const STRIPE_API_VERSION = '2026-03-25.dahlia'; export const STRIPE_API_VERSION = '2026-08-26.dahlia';

Some files were not shown because too many files have changed in this diff Show More