diff --git a/.rustfmt.toml b/.rustfmt.toml
index c539ff0b4..80c01a69b 100644
--- a/.rustfmt.toml
+++ b/.rustfmt.toml
@@ -1,5 +1,5 @@
tab_spaces = 2
-edition="2021"
-imports_layout="HorizontalVertical"
-imports_granularity="Crate"
-group_imports="One"
\ No newline at end of file
+edition = "2021"
+imports_layout = "HorizontalVertical"
+imports_granularity = "Crate"
+group_imports = "One"
diff --git a/.woodpecker.yml b/.woodpecker.yml
index 073bc0a0b..58ab2f0f9 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -18,7 +18,6 @@ pipeline:
image: alpine:3
commands:
- apk add git
- #- git fetch --tags
- git submodule init
- git submodule update
@@ -27,7 +26,34 @@ pipeline:
commands:
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations'
- # use minimum supported rust version for most steps
+ restore-cache:
+ image: meltwater/drone-cache:v1
+ pull: true
+ settings:
+ restore: true
+ endpoint:
+ from_secret: MINIO_ENDPOINT
+ access-key:
+ from_secret: MINIO_WRITE_USER
+ secret-key:
+ from_secret: MINIO_WRITE_PASSWORD
+ bucket:
+ from_secret: MINIO_BUCKET
+ region: us-east-1
+ cache_key: "rust-cache"
+ path-style: true
+ mount:
+ - ".cargo"
+ - "target"
+ - "api_tests/node_modules"
+ secrets:
+ [MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
+
+ toml_fmt:
+ image: tamasfe/taplo:0.8.1
+ commands:
+ - taplo format --check
+
cargo_fmt:
image: *muslrust_image
environment:
@@ -35,42 +61,15 @@ pipeline:
CARGO_HOME: .cargo
commands:
# need make existing toolchain available
- - cp ~/.cargo . -r
- - rustup toolchain install nightly
- - rustup component add rustfmt --toolchain nightly
- - cargo +nightly fmt -- --check
- # when:
- # platform: linux/amd64
-
- cargo_clippy:
- image: *muslrust_image
- environment:
- CARGO_HOME: .cargo
- commands:
- # latest rust for clippy to get extra checks
- # when adding new clippy lints, make sure to also add them in scripts/fix-clippy.sh
- - rustup component add clippy
- - cargo clippy --workspace --tests --all-targets --features console --
- -D warnings -D deprecated -D clippy::perf -D clippy::complexity
- -D clippy::style -D clippy::correctness -D clippy::suspicious
- -D clippy::dbg_macro -D clippy::inefficient_to_string
- -D clippy::items-after-statements -D clippy::implicit_clone
- -D clippy::cast_lossless -D clippy::manual_string_new
- -D clippy::redundant_closure_for_method_calls
- -D clippy::unused_self
- -A clippy::uninlined_format_args
- -D clippy::get_first
- -D clippy::explicit_into_iter_loop
- -D clippy::explicit_iter_loop
- -D clippy::needless_collect
- - cargo clippy --workspace --features console --
- -D clippy::unwrap_used
- -D clippy::indexing_slicing
+ - cp -n ~/.cargo . -r
+ - rustup toolchain install nightly-2023-07-10
+ - rustup component add rustfmt --toolchain nightly-2023-07-10
+ - cargo +nightly-2023-07-10 fmt -- --check
# when:
# platform: linux/amd64
# make sure api builds with default features (used by other crates relying on lemmy api)
- cargo_check:
+ check_api_common_default_features:
image: *muslrust_image
environment:
CARGO_HOME: .cargo
@@ -88,6 +87,14 @@ pipeline:
# when:
# platform: linux/amd64
+ lemmy_api_common_works_with_wasm:
+ image: *muslrust_image
+ environment:
+ CARGO_HOME: .cargo
+ commands:
+ - "rustup target add wasm32-unknown-unknown"
+ - "cargo check --target wasm32-unknown-unknown -p lemmy_api_common"
+
check_defaults_hjson_updated:
image: *muslrust_image
environment:
@@ -109,12 +116,45 @@ pipeline:
- diesel print-schema --config-file=diesel.toml > tmp.schema
- diff tmp.schema crates/db_schema/src/schema.rs
+ check_diesel_migration_revertable:
+ image: willsquire/diesel-cli
+ environment:
+ CARGO_HOME: .cargo
+ DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
+ commands:
+ - diesel migration run
+ - diesel migration redo
+
+ cargo_clippy:
+ image: *muslrust_image
+ environment:
+ CARGO_HOME: .cargo
+ commands:
+ # when adding new clippy lints, make sure to also add them in scripts/fix-clippy.sh
+ - rustup component add clippy
+ - cargo clippy --workspace --tests --all-targets --features console --
+ -D warnings -D deprecated -D clippy::perf -D clippy::complexity
+ -D clippy::style -D clippy::correctness -D clippy::suspicious
+ -D clippy::dbg_macro -D clippy::inefficient_to_string
+ -D clippy::items-after-statements -D clippy::implicit_clone
+ -D clippy::cast_lossless -D clippy::manual_string_new
+ -D clippy::redundant_closure_for_method_calls
+ -D clippy::unused_self
+ -A clippy::uninlined_format_args
+ -D clippy::get_first
+ -D clippy::explicit_into_iter_loop
+ -D clippy::explicit_iter_loop
+ -D clippy::needless_collect
+ -D clippy::unwrap_used
+ -D clippy::indexing_slicing
+ # when:
+ # platform: linux/amd64
+
cargo_test:
image: *muslrust_image
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1"
- RUST_TEST_THREADS: "1"
CARGO_HOME: .cargo
commands:
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
@@ -146,6 +186,29 @@ pipeline:
# when:
# platform: linux/amd64
+ rebuild-cache:
+ image: meltwater/drone-cache:v1
+ pull: true
+ settings:
+ rebuild: true
+ endpoint:
+ from_secret: MINIO_ENDPOINT
+ access-key:
+ from_secret: MINIO_WRITE_USER
+ secret-key:
+ from_secret: MINIO_WRITE_PASSWORD
+ bucket:
+ from_secret: MINIO_BUCKET
+ cache_key: "rust-cache"
+ region: us-east-1
+ path-style: true
+ mount:
+ - ".cargo"
+ - "target"
+ - "api_tests/node_modules"
+ secrets:
+ [MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
+
publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
@@ -172,20 +235,6 @@ pipeline:
when:
event: cron
- # using https://github.com/pksunkara/cargo-workspaces
- publish_to_crates_io:
- image: *muslrust_image
- commands:
- - 'echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"'
- - cargo install cargo-workspaces
- - cp -r migrations crates/db_schema/
- - cargo login "$CARGO_API_TOKEN"
- - cargo workspaces publish --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
- secrets: [cargo_api_token]
- when:
- event: tag
- #platform: linux/amd64
-
notify_on_failure:
image: alpine:3
commands:
diff --git a/Cargo.lock b/Cargo.lock
index b2d3a7bf4..0e8f5fc6b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "activitypub_federation"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ab3ac148d9c0b4163a6d41040c17de7558a42224b9ecbd4e8f033aef6c254d9"
+checksum = "4e6e7fefba6602240fcf612931b70640ad1e249dff833551ebc218f1c96a4193"
dependencies = [
"activitystreams-kinds",
"actix-web",
@@ -22,7 +22,6 @@ dependencies = [
"bytes",
"chrono",
"derive_builder",
- "displaydoc",
"dyn-clone",
"enum_delegate",
"futures-core",
@@ -346,7 +345,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
"once_cell",
"version_check",
]
@@ -358,7 +357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
"once_cell",
"version_check",
]
@@ -678,7 +677,7 @@ checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3"
dependencies = [
"base64 0.21.2",
"blowfish",
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
"subtle",
"zeroize",
]
@@ -1548,17 +1547,6 @@ dependencies = [
"chrono",
]
-[[package]]
-name = "displaydoc"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.25",
-]
-
[[package]]
name = "dlv-list"
version = "0.3.0"
@@ -2045,9 +2033,9 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.2.8"
+version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"js-sys",
@@ -2397,17 +2385,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
-[[package]]
-name = "idna"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
-dependencies = [
- "matches",
- "unicode-bidi",
- "unicode-normalization",
-]
-
[[package]]
name = "idna"
version = "0.3.0"
@@ -2644,16 +2621,19 @@ dependencies = [
name = "lemmy_api_common"
version = "0.18.1"
dependencies = [
+ "activitypub_federation",
"actix-web",
"anyhow",
"chrono",
"encoding",
"futures",
+ "getrandom 0.2.10",
"lemmy_db_schema",
"lemmy_db_views",
"lemmy_db_views_actor",
"lemmy_db_views_moderator",
"lemmy_utils",
+ "once_cell",
"percent-encoding",
"regex",
"reqwest",
@@ -2779,7 +2759,6 @@ dependencies = [
"tokio",
"tracing",
"ts-rs",
- "typed-builder",
]
[[package]]
@@ -2792,7 +2771,6 @@ dependencies = [
"serde",
"serde_with",
"ts-rs",
- "typed-builder",
]
[[package]]
@@ -3162,12 +3140,6 @@ dependencies = [
"regex-automata 0.1.10",
]
-[[package]]
-name = "matches"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
-
[[package]]
name = "matchit"
version = "0.5.0"
@@ -3195,7 +3167,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5736ba45bbac8f7ccc99a897f88ce85e508a18baec973a040f2514e6cdbff0d2"
dependencies = [
- "idna 0.2.3",
+ "idna 0.3.0",
"once_cell",
"regex",
]
@@ -4271,7 +4243,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
]
[[package]]
@@ -4437,7 +4409,7 @@ checksum = "1b97ad83c2fc18113346b7158d79732242002427c30f620fa817c1f32901e0a8"
dependencies = [
"anyhow",
"async-trait",
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
"matchit 0.7.0",
"opentelemetry 0.16.0",
"reqwest",
@@ -5915,7 +5887,7 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
dependencies = [
- "getrandom 0.2.8",
+ "getrandom 0.2.10",
"serde",
]
diff --git a/Cargo.toml b/Cargo.toml
index 9df383df4..f5268be24 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,25 +24,36 @@ doctest = false
debug = 0
lto = "thin"
+# This profile significantly speeds up build time. If debug info is needed you can comment the line
+# out temporarily, but make sure to leave this in the main branch.
+[profile.dev]
+debug = 0
+
[features]
embed-pictrs = ["pict-rs"]
-console = ["console-subscriber", "opentelemetry", "opentelemetry-otlp", "tracing-opentelemetry", "reqwest-tracing/opentelemetry_0_16"]
+console = [
+ "console-subscriber",
+ "opentelemetry",
+ "opentelemetry-otlp",
+ "tracing-opentelemetry",
+ "reqwest-tracing/opentelemetry_0_16",
+]
json-log = ["tracing-subscriber/json"]
prometheus-metrics = ["prometheus", "actix-web-prom"]
default = []
[workspace]
members = [
- "crates/api",
- "crates/api_crud",
- "crates/api_common",
- "crates/apub",
- "crates/utils",
- "crates/db_schema",
- "crates/db_views",
- "crates/db_views_actor",
- "crates/db_views_actor",
- "crates/routes"
+ "crates/api",
+ "crates/api_crud",
+ "crates/api_common",
+ "crates/apub",
+ "crates/utils",
+ "crates/db_schema",
+ "crates/db_views",
+ "crates/db_views_actor",
+ "crates/db_views_actor",
+ "crates/routes",
]
[workspace.dependencies]
@@ -56,13 +67,21 @@ lemmy_routes = { version = "=0.18.1", path = "./crates/routes" }
lemmy_db_views = { version = "=0.18.1", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.18.1", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.18.1", path = "./crates/db_views_moderator" }
-activitypub_federation = { version = "0.4.5", default-features = false, features = ["actix-web"] }
+activitypub_federation = { version = "0.4.6", default-features = false, features = [
+ "actix-web",
+] }
diesel = "2.1.0"
diesel_migrations = "2.1.0"
diesel-async = "0.3.1"
serde = { version = "1.0.167", features = ["derive"] }
serde_with = "3.0.0"
-actix-web = { version = "4.3.1", default-features = false, features = ["macros", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] }
+actix-web = { version = "4.3.1", default-features = false, features = [
+ "macros",
+ "rustls",
+ "compress-brotli",
+ "compress-gzip",
+ "compress-zstd",
+] }
tracing = "0.1.37"
tracing-actix-web = { version = "0.7.5", default-features = false }
tracing-error = "0.2.0"
@@ -82,7 +101,9 @@ base64 = "0.21.2"
uuid = { version = "1.4.0", features = ["serde", "v4"] }
async-trait = "0.1.71"
captcha = "0.0.9"
-anyhow = { version = "1.0.71", features = ["backtrace"] } # backtrace is on by default on nightly, but not stable rust
+anyhow = { version = "1.0.71", features = [
+ "backtrace",
+] } # backtrace is on by default on nightly, but not stable rust
diesel_ltree = "0.3.0"
typed-builder = "0.15.0"
serial_test = "2.0.0"
@@ -91,7 +112,7 @@ sha2 = "0.10.7"
regex = "1.9.0"
once_cell = "1.18.0"
diesel-derive-newtype = "2.1.0"
-diesel-derive-enum = {version = "2.1.0", features = ["postgres"] }
+diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
strum = "0.25.0"
strum_macros = "0.25.1"
itertools = "0.11.0"
@@ -103,7 +124,7 @@ rand = "0.8.5"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
tracing-opentelemetry = { version = "0.19.0" }
ts-rs = { version = "6.2", features = ["serde-compat", "chrono-impl"] }
-rustls = { version ="0.21.3", features = ["dangerous_configuration"]}
+rustls = { version = "0.21.3", features = ["dangerous_configuration"] }
futures-util = "0.3.28"
tokio-postgres = "0.7.8"
tokio-postgres-rustls = "0.10.0"
diff --git a/README.md b/README.md
index 513ad8c87..f27d8441e 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@
Español |
Русский |
汉语 |
- 漢語
+ 漢語 |
+ 日本語
diff --git a/RELEASES.md b/RELEASES.md
index 1dd500f40..bca2f02f1 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -470,7 +470,7 @@ The installation instructions have been slightly updated. However there are no b
Follow the upgrade instructions for [ansible](https://github.com/LemmyNet/lemmy-ansible#upgrading) or [docker](https://join-lemmy.org/docs/en/administration/install_docker.html#updating).
-If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/!OwmdVYiZSXrXbtCNLw:matrix.org).
+If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/#lemmy-admin-support-topics:discuss.online).
## Support development
@@ -1016,8 +1016,8 @@ Next, **manually edit** your [lemmy.hjson](https://github.com/LemmyNet/lemmy/blo
- `pictrs_url` is removed, and the pictrs config is now a block. If using docker, it should look like:
```
pictrs: {
- url: "http://pictrs:8080/"
- # api_key: "API_KEY"
+ url: "http://pictrs:8080/"
+ api_key: "{{ postgres_password }}"
}
```
- The `rate_limit`, `federation`, `captcha`, and `slur_filter` blocks should be removed, as they are now in the database, and can be updated through the UI.
@@ -1048,7 +1048,7 @@ _Note_: On production databases with thousands of comments, this upgrade **takes
_Note_: If you have any issues upgrading, you can restore your old database using the [backup and restore instructions here](https://join-lemmy.org/docs/en/administration/backup_and_restore.html).
-If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/!BZVTUuEiNmRcbFeLeI:matrix.org).
+If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/#lemmy-admin-support-topics:discuss.online).
## Support development
diff --git a/api_tests/package.json b/api_tests/package.json
index d81ef235d..ec692e1b5 100644
--- a/api_tests/package.json
+++ b/api_tests/package.json
@@ -19,7 +19,7 @@
"eslint": "^8.40.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^29.5.0",
- "lemmy-js-client": "0.17.2-rc.13",
+ "lemmy-js-client": "0.18.3-rc.3",
"prettier": "^3.0.0",
"ts-jest": "^29.1.0",
"typescript": "^5.0.4"
diff --git a/api_tests/prepare-drone-federation-test.sh b/api_tests/prepare-drone-federation-test.sh
index 813b3b15c..7eceeeb77 100755
--- a/api_tests/prepare-drone-federation-test.sh
+++ b/api_tests/prepare-drone-federation-test.sh
@@ -1,11 +1,15 @@
#!/usr/bin/env bash
+# IMPORTANT NOTE: this script does not use the normal LEMMY_DATABASE_URL format
+# it is expected that this script is called by run-federation-test.sh script.
set -e
export RUST_BACKTRACE=1
export RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
+ echo "DB URL: ${LEMMY_DATABASE_URL} INSTANCE: $INSTANCE"
psql "${LEMMY_DATABASE_URL}/lemmy" -c "DROP DATABASE IF EXISTS $INSTANCE"
+ echo "create database"
psql "${LEMMY_DATABASE_URL}/lemmy" -c "CREATE DATABASE $INSTANCE"
done
@@ -26,6 +30,7 @@ else
done
fi
+echo "killall existing lemmy_server processes"
killall lemmy_server || true
echo "$PWD"
@@ -59,7 +64,12 @@ target/lemmy_server >/tmp/lemmy_epsilon.out 2>&1 &
echo "wait for all instances to start"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-alpha:8541/api/v3/site')" != "200" ]]; do sleep 1; done
+echo "alpha started"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-beta:8551/api/v3/site')" != "200" ]]; do sleep 1; done
+echo "beta started"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-gamma:8561/api/v3/site')" != "200" ]]; do sleep 1; done
+echo "gamma started"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-delta:8571/api/v3/site')" != "200" ]]; do sleep 1; done
+echo "delta started"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-epsilon:8581/api/v3/site')" != "200" ]]; do sleep 1; done
+echo "epsilon started. All started"
diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts
index 80cb868f2..932c7ffeb 100644
--- a/api_tests/src/comment.spec.ts
+++ b/api_tests/src/comment.spec.ts
@@ -29,6 +29,7 @@ import {
getComments,
getCommentParentId,
resolveCommunity,
+ getPersonDetails,
} from "./shared";
import { CommentView } from "lemmy-js-client/dist/types/CommentView";
@@ -82,8 +83,7 @@ test("Create a comment", async () => {
});
test("Create a comment in a non-existent post", async () => {
- let commentRes = (await createComment(alpha, -1)) as any;
- expect(commentRes.error).toBe("couldnt_find_post");
+ await expect(createComment(alpha, -1)).rejects.toBe("couldnt_find_post");
});
test("Update a comment", async () => {
@@ -122,11 +122,9 @@ test("Delete a comment", async () => {
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
// Make sure that comment is undefined on beta
- let betaCommentRes = (await resolveComment(
- beta,
- commentRes.comment_view.comment,
- )) as any;
- expect(betaCommentRes.error).toBe("couldnt_find_object");
+ await expect(
+ resolveComment(beta, commentRes.comment_view.comment),
+ ).rejects.toBe("couldnt_find_object");
let undeleteCommentRes = await deleteComment(
alpha,
@@ -160,9 +158,9 @@ test("Remove a comment from admin and community on the same instance", async ()
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
// Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
- let refetchedPostComments = await getComments(
+ let refetchedPostComments = await getPersonDetails(
alpha,
- postRes.post_view.post.id,
+ commentRes.comment_view.comment.creator_id,
);
expect(refetchedPostComments.comments[0].comment.removed).toBe(true);
diff --git a/api_tests/src/community.spec.ts b/api_tests/src/community.spec.ts
index d9a76cf87..a5a202ace 100644
--- a/api_tests/src/community.spec.ts
+++ b/api_tests/src/community.spec.ts
@@ -52,8 +52,9 @@ test("Create community", async () => {
// A dupe check
let prevName = communityRes.community_view.community.name;
- let communityRes2: any = await createCommunity(alpha, prevName);
- expect(communityRes2["error"]).toBe("community_already_exists");
+ await expect(createCommunity(alpha, prevName)).rejects.toBe(
+ "community_already_exists",
+ );
// Cache the community on beta, make sure it has the other fields
let searchShort = `!${prevName}@lemmy-alpha:8541`;
diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts
index cabbcfd8a..8ea3ea912 100644
--- a/api_tests/src/post.spec.ts
+++ b/api_tests/src/post.spec.ts
@@ -88,17 +88,18 @@ test("Create a post", async () => {
assertPostFederation(betaPost, postRes.post_view);
// Delta only follows beta, so it should not see an alpha ap_id
- let deltaPost = (await resolvePost(delta, postRes.post_view.post)).post;
- expect(deltaPost).toBeUndefined();
+ await expect(resolvePost(delta, postRes.post_view.post)).rejects.toBe(
+ "couldnt_find_object",
+ );
// Epsilon has alpha blocked, it should not see the alpha post
- let epsilonPost = (await resolvePost(epsilon, postRes.post_view.post)).post;
- expect(epsilonPost).toBeUndefined();
+ await expect(resolvePost(epsilon, postRes.post_view.post)).rejects.toBe(
+ "couldnt_find_object",
+ );
});
test("Create a post in a non-existent community", async () => {
- let postRes = (await createPost(alpha, -2)) as any;
- expect(postRes.error).toBe("couldnt_find_community");
+ await expect(createPost(alpha, -2)).rejects.toBe("couldnt_find_community");
});
test("Unlike a post", async () => {
@@ -145,8 +146,9 @@ test("Update a post", async () => {
assertPostFederation(betaPost, updatedPost.post_view);
// Make sure lemmy beta cannot update the post
- let updatedPostBeta = (await editPost(beta, betaPost.post)) as any;
- expect(updatedPostBeta.error).toBe("no_post_edit_allowed");
+ await expect(editPost(beta, betaPost.post)).rejects.toBe(
+ "no_post_edit_allowed",
+ );
});
test("Sticky a post", async () => {
@@ -210,8 +212,7 @@ test("Lock a post", async () => {
expect(alphaPost1.post.locked).toBe(true);
// Try to make a new comment there, on alpha
- let comment: any = await createComment(alpha, alphaPost1.post.id);
- expect(comment["error"]).toBe("locked");
+ await expect(createComment(alpha, alphaPost1.post.id)).rejects.toBe("locked");
// Unlock a post
let unlockedPost = await lockPost(beta, false, betaPost1.post);
@@ -242,9 +243,10 @@ test("Delete a post", async () => {
expect(deletedPost.post_view.post.name).toBe(postRes.post_view.post.name);
// Make sure lemmy beta sees post is deleted
- let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
// This will be undefined because of the tombstone
- expect(betaPost).toBeUndefined();
+ await expect(resolvePost(beta, postRes.post_view.post)).rejects.toBe(
+ "couldnt_find_object",
+ );
// Undelete
let undeletedPost = await deletePost(alpha, false, postRes.post_view.post);
@@ -259,8 +261,9 @@ test("Delete a post", async () => {
assertPostFederation(betaPost2, undeletedPost.post_view);
// Make sure lemmy beta cannot delete the post
- let deletedPostBeta = (await deletePost(beta, true, betaPost2.post)) as any;
- expect(deletedPostBeta.error).toStrictEqual("no_post_edit_allowed");
+ await expect(deletePost(beta, true, betaPost2.post)).rejects.toBe(
+ "no_post_edit_allowed",
+ );
});
test("Remove a post from admin and community on different instance", async () => {
@@ -388,8 +391,8 @@ test("Enforce site ban for federated user", async () => {
expect(alphaUserOnBeta1.person?.person.banned).toBe(true);
// existing alpha post should be removed on beta
- let searchBeta2 = await searchPostLocal(beta, postRes1.post_view.post);
- expect(searchBeta2.posts[0].post.removed).toBe(true);
+ let searchBeta2 = await getPost(beta, searchBeta1.posts[0].post.id);
+ expect(searchBeta2.post_view.post.removed).toBe(true);
// Unban alpha
let unBanAlpha = await banPersonFromSite(
@@ -436,12 +439,14 @@ test("Enforce community ban for federated user", async () => {
expect(banAlpha.banned).toBe(true);
// ensure that the post by alpha got removed
- let searchAlpha1 = await searchPostLocal(alpha, postRes1.post_view.post);
- expect(searchAlpha1.posts[0].post.removed).toBe(true);
+ await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe(
+ "unknown",
+ );
// Alpha tries to make post on beta, but it fails because of ban
- let postRes2 = await createPost(alpha, betaCommunity.community.id);
- expect(postRes2.post_view).toBeUndefined();
+ await expect(createPost(alpha, betaCommunity.community.id)).rejects.toBe(
+ "banned_from_community",
+ );
// Unban alpha
let unBanAlpha = await banPersonFromCommunity(
diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts
index 0523712e0..bbd4eaaeb 100644
--- a/api_tests/src/shared.ts
+++ b/api_tests/src/shared.ts
@@ -58,6 +58,8 @@ import { CommentReportResponse } from "lemmy-js-client/dist/types/CommentReportR
import { CreateCommentReport } from "lemmy-js-client/dist/types/CreateCommentReport";
import { ListCommentReportsResponse } from "lemmy-js-client/dist/types/ListCommentReportsResponse";
import { ListCommentReports } from "lemmy-js-client/dist/types/ListCommentReports";
+import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDetailsResponse";
+import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails";
export interface API {
client: LemmyHttp;
@@ -186,8 +188,11 @@ export async function setupLogins() {
await epsilon.client.editSite(editSiteForm);
// Create the main alpha/beta communities
- await createCommunity(alpha, "main");
- await createCommunity(beta, "main");
+ // Ignore thrown errors of duplicates
+ try {
+ await createCommunity(alpha, "main");
+ await createCommunity(beta, "main");
+ } catch (_) {}
}
export async function createPost(
@@ -646,6 +651,16 @@ export async function saveUserSettings(
): Promise {
return api.client.saveUserSettings(form);
}
+export async function getPersonDetails(
+ api: API,
+ person_id: number,
+): Promise {
+ let form: GetPersonDetails = {
+ auth: api.auth,
+ person_id: person_id,
+ };
+ return api.client.getPersonDetails(form);
+}
export async function deleteUser(api: API): Promise {
let form: DeleteAccount = {
diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts
index afe21d1a0..f488ebe1e 100644
--- a/api_tests/src/user.spec.ts
+++ b/api_tests/src/user.spec.ts
@@ -92,10 +92,18 @@ test("Delete user", async () => {
await deleteUser(user);
- expect((await resolvePost(alpha, localPost)).post).toBeUndefined();
- expect((await resolveComment(alpha, localComment)).comment).toBeUndefined();
- expect((await resolvePost(alpha, remotePost)).post).toBeUndefined();
- expect((await resolveComment(alpha, remoteComment)).comment).toBeUndefined();
+ await expect(resolvePost(alpha, localPost)).rejects.toBe(
+ "couldnt_find_object",
+ );
+ await expect(resolveComment(alpha, localComment)).rejects.toBe(
+ "couldnt_find_object",
+ );
+ await expect(resolvePost(alpha, remotePost)).rejects.toBe(
+ "couldnt_find_object",
+ );
+ await expect(resolveComment(alpha, remoteComment)).rejects.toBe(
+ "couldnt_find_object",
+ );
});
test("Requests with invalid auth should be treated as unauthenticated", async () => {
diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock
index a404dc52d..30f13014b 100644
--- a/api_tests/yarn.lock
+++ b/api_tests/yarn.lock
@@ -2157,10 +2157,10 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-lemmy-js-client@0.17.2-rc.13:
- version "0.17.2-rc.13"
- resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.13.tgz#f2a61050c1308e85cb39c0e1f561e392e84e3921"
- integrity sha512-4IyR1pisCumJ9L8fEPISC+Su1kVTI4pL/gWLsuOXxZC/lK36mG2+NfaNPiUmIklpCF5TUN+1F7E9bEvtTGogww==
+lemmy-js-client@0.18.3-rc.3:
+ version "0.18.3-rc.3"
+ resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.18.3-rc.3.tgz#fc6489eb141bd09558bca38d9e46b40771a29f37"
+ integrity sha512-njixgXk4uMU4gGifnljwhSe9Kf445C4wAXcXhtpTtwPPLXpHQgxA1RASMb9Uq4zblfE6nC2JbrAka8y8N2N/Bw==
dependencies:
cross-fetch "^3.1.5"
form-data "^4.0.0"
diff --git a/crates/api/src/comment_report/list.rs b/crates/api/src/comment_report/list.rs
index baa1bf45f..b67ec333c 100644
--- a/crates/api/src/comment_report/list.rs
+++ b/crates/api/src/comment_report/list.rs
@@ -22,24 +22,19 @@ impl Perform for ListCommentReports {
let data: &ListCommentReports = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
- let person_id = local_user_view.person.id;
- let admin = local_user_view.person.admin;
let community_id = data.community_id;
let unresolved_only = data.unresolved_only;
let page = data.page;
let limit = data.limit;
- let comment_reports = CommentReportQuery::builder()
- .pool(&mut context.pool())
- .my_person_id(person_id)
- .admin(admin)
- .community_id(community_id)
- .unresolved_only(unresolved_only)
- .page(page)
- .limit(limit)
- .build()
- .list()
- .await?;
+ let comment_reports = CommentReportQuery {
+ community_id,
+ unresolved_only,
+ page,
+ limit,
+ }
+ .list(&mut context.pool(), &local_user_view.person)
+ .await?;
Ok(ListCommentReportsResponse { comment_reports })
}
diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs
index 988dac27a..9d3cf211c 100644
--- a/crates/api/src/lib.rs
+++ b/crates/api/src/lib.rs
@@ -76,6 +76,9 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul
#[cfg(test)]
mod tests {
+ #![allow(clippy::unwrap_used)]
+ #![allow(clippy::indexing_slicing)]
+
use lemmy_api_common::utils::check_validator_time;
use lemmy_db_schema::{
source::{
diff --git a/crates/api/src/local_user/notifications/list_mentions.rs b/crates/api/src/local_user/notifications/list_mentions.rs
index 1b2d3c7b6..10d24ff8f 100644
--- a/crates/api/src/local_user/notifications/list_mentions.rs
+++ b/crates/api/src/local_user/notifications/list_mentions.rs
@@ -27,18 +27,17 @@ impl Perform for GetPersonMentions {
let person_id = Some(local_user_view.person.id);
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
- let mentions = PersonMentionQuery::builder()
- .pool(&mut context.pool())
- .recipient_id(person_id)
- .my_person_id(person_id)
- .sort(sort)
- .unread_only(unread_only)
- .show_bot_accounts(show_bot_accounts)
- .page(page)
- .limit(limit)
- .build()
- .list()
- .await?;
+ let mentions = PersonMentionQuery {
+ recipient_id: person_id,
+ my_person_id: person_id,
+ sort,
+ unread_only,
+ show_bot_accounts,
+ page,
+ limit,
+ }
+ .list(&mut context.pool())
+ .await?;
Ok(GetPersonMentionsResponse { mentions })
}
diff --git a/crates/api/src/local_user/notifications/list_replies.rs b/crates/api/src/local_user/notifications/list_replies.rs
index 79b0fe223..8c7f3059b 100644
--- a/crates/api/src/local_user/notifications/list_replies.rs
+++ b/crates/api/src/local_user/notifications/list_replies.rs
@@ -24,18 +24,17 @@ impl Perform for GetReplies {
let person_id = Some(local_user_view.person.id);
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
- let replies = CommentReplyQuery::builder()
- .pool(&mut context.pool())
- .recipient_id(person_id)
- .my_person_id(person_id)
- .sort(sort)
- .unread_only(unread_only)
- .show_bot_accounts(show_bot_accounts)
- .page(page)
- .limit(limit)
- .build()
- .list()
- .await?;
+ let replies = CommentReplyQuery {
+ recipient_id: person_id,
+ my_person_id: person_id,
+ sort,
+ unread_only,
+ show_bot_accounts,
+ page,
+ limit,
+ }
+ .list(&mut context.pool())
+ .await?;
Ok(GetRepliesResponse { replies })
}
diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs
index 822f08d28..4176a3f4c 100644
--- a/crates/api/src/local_user/save_settings.rs
+++ b/crates/api/src/local_user/save_settings.rs
@@ -133,6 +133,7 @@ impl Perform for SaveUserSettings {
.totp_2fa_secret(totp_2fa_secret)
.totp_2fa_url(totp_2fa_url)
.open_links_in_new_tab(data.open_links_in_new_tab)
+ .infinite_scroll_enabled(data.infinite_scroll_enabled)
.build();
let local_user_res =
diff --git a/crates/api/src/post_report/list.rs b/crates/api/src/post_report/list.rs
index f7496e1a3..a0d909681 100644
--- a/crates/api/src/post_report/list.rs
+++ b/crates/api/src/post_report/list.rs
@@ -22,24 +22,19 @@ impl Perform for ListPostReports {
let data: &ListPostReports = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
- let person_id = local_user_view.person.id;
- let admin = local_user_view.person.admin;
let community_id = data.community_id;
let unresolved_only = data.unresolved_only;
let page = data.page;
let limit = data.limit;
- let post_reports = PostReportQuery::builder()
- .pool(&mut context.pool())
- .my_person_id(person_id)
- .admin(admin)
- .community_id(community_id)
- .unresolved_only(unresolved_only)
- .page(page)
- .limit(limit)
- .build()
- .list()
- .await?;
+ let post_reports = PostReportQuery {
+ community_id,
+ unresolved_only,
+ page,
+ limit,
+ }
+ .list(&mut context.pool(), &local_user_view.person)
+ .await?;
Ok(ListPostReportsResponse { post_reports })
}
diff --git a/crates/api/src/private_message_report/list.rs b/crates/api/src/private_message_report/list.rs
index 72d182d45..8b4e50767 100644
--- a/crates/api/src/private_message_report/list.rs
+++ b/crates/api/src/private_message_report/list.rs
@@ -21,14 +21,13 @@ impl Perform for ListPrivateMessageReports {
let unresolved_only = self.unresolved_only;
let page = self.page;
let limit = self.limit;
- let private_message_reports = PrivateMessageReportQuery::builder()
- .pool(&mut context.pool())
- .unresolved_only(unresolved_only)
- .page(page)
- .limit(limit)
- .build()
- .list()
- .await?;
+ let private_message_reports = PrivateMessageReportQuery {
+ unresolved_only,
+ page,
+ limit,
+ }
+ .list(&mut context.pool())
+ .await?;
Ok(ListPrivateMessageReportsResponse {
private_message_reports,
diff --git a/crates/api/src/site/registration_applications/list.rs b/crates/api/src/site/registration_applications/list.rs
index 2389ad403..433cee425 100644
--- a/crates/api/src/site/registration_applications/list.rs
+++ b/crates/api/src/site/registration_applications/list.rs
@@ -23,19 +23,18 @@ impl Perform for ListRegistrationApplications {
is_admin(&local_user_view)?;
let unread_only = data.unread_only;
- let verified_email_only = local_site.require_email_verification;
+ let verified_email_only = Some(local_site.require_email_verification);
let page = data.page;
let limit = data.limit;
- let registration_applications = RegistrationApplicationQuery::builder()
- .pool(&mut context.pool())
- .unread_only(unread_only)
- .verified_email_only(Some(verified_email_only))
- .page(page)
- .limit(limit)
- .build()
- .list()
- .await?;
+ let registration_applications = RegistrationApplicationQuery {
+ unread_only,
+ verified_email_only,
+ page,
+ limit,
+ }
+ .list(&mut context.pool())
+ .await?;
Ok(Self::Response {
registration_applications,
diff --git a/crates/api_common/Cargo.toml b/crates/api_common/Cargo.toml
index a9b2bf19b..8a23a4cb2 100644
--- a/crates/api_common/Cargo.toml
+++ b/crates/api_common/Cargo.toml
@@ -14,9 +14,27 @@ path = "src/lib.rs"
doctest = false
[features]
-full = ["tracing", "rosetta-i18n", "chrono", "lemmy_utils",
- "lemmy_db_views/full", "lemmy_db_views_actor/full", "lemmy_db_views_moderator/full",
- "percent-encoding", "encoding", "reqwest-middleware", "webpage", "ts-rs"]
+full = [
+ "tracing",
+ "rosetta-i18n",
+ "chrono",
+ "lemmy_utils",
+ "lemmy_db_views/full",
+ "lemmy_db_views_actor/full",
+ "lemmy_db_views_moderator/full",
+ "activitypub_federation",
+ "percent-encoding",
+ "encoding",
+ "reqwest-middleware",
+ "webpage",
+ "ts-rs",
+ "tokio",
+ "uuid",
+ "reqwest",
+ "actix-web",
+ "futures",
+ "once_cell",
+]
[dependencies]
lemmy_db_views = { workspace = true }
@@ -24,6 +42,7 @@ lemmy_db_views_moderator = { workspace = true }
lemmy_db_views_actor = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_utils = { workspace = true, optional = true }
+activitypub_federation = { workspace = true, optional = true }
serde = { workspace = true }
serde_with = { workspace = true }
url = { workspace = true }
@@ -33,12 +52,17 @@ reqwest-middleware = { workspace = true, optional = true }
regex = { workspace = true }
rosetta-i18n = { workspace = true, optional = true }
percent-encoding = { workspace = true, optional = true }
-webpage = { version = "1.6", default-features = false, features = ["serde"], optional = true }
+webpage = { version = "1.6", default-features = false, features = [
+ "serde",
+], optional = true }
encoding = { version = "0.2.33", optional = true }
anyhow = { workspace = true }
-futures = { workspace = true }
-uuid = { workspace = true }
-tokio = { workspace = true }
-reqwest = { workspace = true }
+futures = { workspace = true, optional = true }
+uuid = { workspace = true, optional = true }
+tokio = { workspace = true, optional = true }
+reqwest = { workspace = true, optional = true }
ts-rs = { workspace = true, optional = true }
-actix-web = { workspace = true }
+once_cell = { workspace = true, optional = true }
+actix-web = { workspace = true, optional = true }
+# necessary for wasmt compilation
+getrandom = { version = "0.2.10", features = ["js"] }
diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs
index 8b96206cb..8a63f7ad4 100644
--- a/crates/api_common/src/build_response.rs
+++ b/crates/api_common/src/build_response.rs
@@ -64,7 +64,7 @@ pub async fn build_community_response(
}
pub async fn build_post_response(
- context: &Data,
+ context: &LemmyContext,
community_id: CommunityId,
person_id: PersonId,
post_id: PostId,
diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs
index 224e114a5..652cbaf43 100644
--- a/crates/api_common/src/lib.rs
+++ b/crates/api_common/src/lib.rs
@@ -10,6 +10,8 @@ pub mod post;
pub mod private_message;
#[cfg(feature = "full")]
pub mod request;
+#[cfg(feature = "full")]
+pub mod send_activity;
pub mod sensitive;
pub mod site;
#[cfg(feature = "full")]
diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs
index 824d132a5..031bc6c7e 100644
--- a/crates/api_common/src/person.rs
+++ b/crates/api_common/src/person.rs
@@ -133,6 +133,8 @@ pub struct SaveUserSettings {
pub auth: Sensitive,
/// Open links in a new tab
pub open_links_in_new_tab: Option,
+ /// Enable infinite scroll
+ pub infinite_scroll_enabled: Option,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs
index dc09ecaa7..82126887a 100644
--- a/crates/api_common/src/request.rs
+++ b/crates/api_common/src/request.rs
@@ -270,6 +270,9 @@ pub fn build_user_agent(settings: &Settings) -> String {
#[cfg(test)]
mod tests {
+ #![allow(clippy::unwrap_used)]
+ #![allow(clippy::indexing_slicing)]
+
use crate::request::{
build_user_agent,
fetch_site_metadata,
diff --git a/crates/api_common/src/send_activity.rs b/crates/api_common/src/send_activity.rs
new file mode 100644
index 000000000..6c91258ec
--- /dev/null
+++ b/crates/api_common/src/send_activity.rs
@@ -0,0 +1,74 @@
+use crate::context::LemmyContext;
+use activitypub_federation::config::Data;
+use futures::future::BoxFuture;
+use lemmy_db_schema::source::post::Post;
+use lemmy_utils::{error::LemmyResult, SYNCHRONOUS_FEDERATION};
+use once_cell::sync::{Lazy, OnceCell};
+use tokio::{
+ sync::{
+ mpsc,
+ mpsc::{UnboundedReceiver, UnboundedSender, WeakUnboundedSender},
+ Mutex,
+ },
+ task::JoinHandle,
+};
+
+type MatchOutgoingActivitiesBoxed =
+ Box fn(SendActivityData, &'a Data) -> BoxFuture<'a, LemmyResult<()>>>;
+
+/// This static is necessary so that activities can be sent out synchronously for tests.
+pub static MATCH_OUTGOING_ACTIVITIES: OnceCell = OnceCell::new();
+
+#[derive(Debug)]
+pub enum SendActivityData {
+ CreatePost(Post),
+}
+
+// TODO: instead of static, move this into LemmyContext. make sure that stopping the process with
+// ctrl+c still works.
+static ACTIVITY_CHANNEL: Lazy = Lazy::new(|| {
+ let (sender, receiver) = mpsc::unbounded_channel();
+ let weak_sender = sender.downgrade();
+ ActivityChannel {
+ weak_sender,
+ receiver: Mutex::new(receiver),
+ keepalive_sender: Mutex::new(Some(sender)),
+ }
+});
+
+pub struct ActivityChannel {
+ weak_sender: WeakUnboundedSender,
+ receiver: Mutex>,
+ keepalive_sender: Mutex