Merge remote-tracking branch 'upstream/main' into defaults

pull/3707/head
dull b 2023-07-23 18:20:19 +00:00
commit 374b0f6f9e
169 changed files with 2693 additions and 1653 deletions

View File

@ -1,5 +1,5 @@
tab_spaces = 2 tab_spaces = 2
edition="2021" edition = "2021"
imports_layout="HorizontalVertical" imports_layout = "HorizontalVertical"
imports_granularity="Crate" imports_granularity = "Crate"
group_imports="One" group_imports = "One"

View File

@ -18,7 +18,6 @@ pipeline:
image: alpine:3 image: alpine:3
commands: commands:
- apk add git - apk add git
#- git fetch --tags
- git submodule init - git submodule init
- git submodule update - git submodule update
@ -27,7 +26,34 @@ pipeline:
commands: commands:
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' - 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: cargo_fmt:
image: *muslrust_image image: *muslrust_image
environment: environment:
@ -35,42 +61,15 @@ pipeline:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
# need make existing toolchain available # need make existing toolchain available
- cp ~/.cargo . -r - cp -n ~/.cargo . -r
- rustup toolchain install nightly - rustup toolchain install nightly-2023-07-10
- rustup component add rustfmt --toolchain nightly - rustup component add rustfmt --toolchain nightly-2023-07-10
- cargo +nightly fmt -- --check - cargo +nightly-2023-07-10 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
# when: # when:
# platform: linux/amd64 # platform: linux/amd64
# make sure api builds with default features (used by other crates relying on lemmy api) # 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 image: *muslrust_image
environment: environment:
CARGO_HOME: .cargo CARGO_HOME: .cargo
@ -88,6 +87,14 @@ pipeline:
# when: # when:
# platform: linux/amd64 # 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: check_defaults_hjson_updated:
image: *muslrust_image image: *muslrust_image
environment: environment:
@ -109,12 +116,45 @@ pipeline:
- diesel print-schema --config-file=diesel.toml > tmp.schema - diesel print-schema --config-file=diesel.toml > tmp.schema
- diff tmp.schema crates/db_schema/src/schema.rs - 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: cargo_test:
image: *muslrust_image image: *muslrust_image
environment: environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1" RUST_BACKTRACE: "1"
RUST_TEST_THREADS: "1"
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson - export LEMMY_CONFIG_LOCATION=../../config/config.hjson
@ -146,6 +186,29 @@ pipeline:
# when: # when:
# platform: linux/amd64 # 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: publish_release_docker:
image: woodpeckerci/plugin-docker-buildx image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password] secrets: [docker_username, docker_password]
@ -172,20 +235,6 @@ pipeline:
when: when:
event: cron 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: notify_on_failure:
image: alpine:3 image: alpine:3
commands: commands:

56
Cargo.lock generated
View File

@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]] [[package]]
name = "activitypub_federation" name = "activitypub_federation"
version = "0.4.5" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ab3ac148d9c0b4163a6d41040c17de7558a42224b9ecbd4e8f033aef6c254d9" checksum = "4e6e7fefba6602240fcf612931b70640ad1e249dff833551ebc218f1c96a4193"
dependencies = [ dependencies = [
"activitystreams-kinds", "activitystreams-kinds",
"actix-web", "actix-web",
@ -22,7 +22,6 @@ dependencies = [
"bytes", "bytes",
"chrono", "chrono",
"derive_builder", "derive_builder",
"displaydoc",
"dyn-clone", "dyn-clone",
"enum_delegate", "enum_delegate",
"futures-core", "futures-core",
@ -346,7 +345,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [ dependencies = [
"getrandom 0.2.8", "getrandom 0.2.10",
"once_cell", "once_cell",
"version_check", "version_check",
] ]
@ -358,7 +357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom 0.2.8", "getrandom 0.2.10",
"once_cell", "once_cell",
"version_check", "version_check",
] ]
@ -678,7 +677,7 @@ checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3"
dependencies = [ dependencies = [
"base64 0.21.2", "base64 0.21.2",
"blowfish", "blowfish",
"getrandom 0.2.8", "getrandom 0.2.10",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@ -1548,17 +1547,6 @@ dependencies = [
"chrono", "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]] [[package]]
name = "dlv-list" name = "dlv-list"
version = "0.3.0" version = "0.3.0"
@ -2045,9 +2033,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.8" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@ -2397,17 +2385,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 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]] [[package]]
name = "idna" name = "idna"
version = "0.3.0" version = "0.3.0"
@ -2644,16 +2621,19 @@ dependencies = [
name = "lemmy_api_common" name = "lemmy_api_common"
version = "0.18.1" version = "0.18.1"
dependencies = [ dependencies = [
"activitypub_federation",
"actix-web", "actix-web",
"anyhow", "anyhow",
"chrono", "chrono",
"encoding", "encoding",
"futures", "futures",
"getrandom 0.2.10",
"lemmy_db_schema", "lemmy_db_schema",
"lemmy_db_views", "lemmy_db_views",
"lemmy_db_views_actor", "lemmy_db_views_actor",
"lemmy_db_views_moderator", "lemmy_db_views_moderator",
"lemmy_utils", "lemmy_utils",
"once_cell",
"percent-encoding", "percent-encoding",
"regex", "regex",
"reqwest", "reqwest",
@ -2779,7 +2759,6 @@ dependencies = [
"tokio", "tokio",
"tracing", "tracing",
"ts-rs", "ts-rs",
"typed-builder",
] ]
[[package]] [[package]]
@ -2792,7 +2771,6 @@ dependencies = [
"serde", "serde",
"serde_with", "serde_with",
"ts-rs", "ts-rs",
"typed-builder",
] ]
[[package]] [[package]]
@ -3162,12 +3140,6 @@ dependencies = [
"regex-automata 0.1.10", "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]] [[package]]
name = "matchit" name = "matchit"
version = "0.5.0" version = "0.5.0"
@ -3195,7 +3167,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5736ba45bbac8f7ccc99a897f88ce85e508a18baec973a040f2514e6cdbff0d2" checksum = "5736ba45bbac8f7ccc99a897f88ce85e508a18baec973a040f2514e6cdbff0d2"
dependencies = [ dependencies = [
"idna 0.2.3", "idna 0.3.0",
"once_cell", "once_cell",
"regex", "regex",
] ]
@ -4271,7 +4243,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom 0.2.8", "getrandom 0.2.10",
] ]
[[package]] [[package]]
@ -4437,7 +4409,7 @@ checksum = "1b97ad83c2fc18113346b7158d79732242002427c30f620fa817c1f32901e0a8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"getrandom 0.2.8", "getrandom 0.2.10",
"matchit 0.7.0", "matchit 0.7.0",
"opentelemetry 0.16.0", "opentelemetry 0.16.0",
"reqwest", "reqwest",
@ -5915,7 +5887,7 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
dependencies = [ dependencies = [
"getrandom 0.2.8", "getrandom 0.2.10",
"serde", "serde",
] ]

View File

@ -24,25 +24,36 @@ doctest = false
debug = 0 debug = 0
lto = "thin" 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] [features]
embed-pictrs = ["pict-rs"] 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"] json-log = ["tracing-subscriber/json"]
prometheus-metrics = ["prometheus", "actix-web-prom"] prometheus-metrics = ["prometheus", "actix-web-prom"]
default = [] default = []
[workspace] [workspace]
members = [ members = [
"crates/api", "crates/api",
"crates/api_crud", "crates/api_crud",
"crates/api_common", "crates/api_common",
"crates/apub", "crates/apub",
"crates/utils", "crates/utils",
"crates/db_schema", "crates/db_schema",
"crates/db_views", "crates/db_views",
"crates/db_views_actor", "crates/db_views_actor",
"crates/db_views_actor", "crates/db_views_actor",
"crates/routes" "crates/routes",
] ]
[workspace.dependencies] [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 = { version = "=0.18.1", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.18.1", path = "./crates/db_views_actor" } 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" } 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 = "2.1.0"
diesel_migrations = "2.1.0" diesel_migrations = "2.1.0"
diesel-async = "0.3.1" diesel-async = "0.3.1"
serde = { version = "1.0.167", features = ["derive"] } serde = { version = "1.0.167", features = ["derive"] }
serde_with = "3.0.0" 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 = "0.1.37"
tracing-actix-web = { version = "0.7.5", default-features = false } tracing-actix-web = { version = "0.7.5", default-features = false }
tracing-error = "0.2.0" tracing-error = "0.2.0"
@ -82,7 +101,9 @@ base64 = "0.21.2"
uuid = { version = "1.4.0", features = ["serde", "v4"] } uuid = { version = "1.4.0", features = ["serde", "v4"] }
async-trait = "0.1.71" async-trait = "0.1.71"
captcha = "0.0.9" 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" diesel_ltree = "0.3.0"
typed-builder = "0.15.0" typed-builder = "0.15.0"
serial_test = "2.0.0" serial_test = "2.0.0"
@ -91,7 +112,7 @@ sha2 = "0.10.7"
regex = "1.9.0" regex = "1.9.0"
once_cell = "1.18.0" once_cell = "1.18.0"
diesel-derive-newtype = "2.1.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 = "0.25.0"
strum_macros = "0.25.1" strum_macros = "0.25.1"
itertools = "0.11.0" itertools = "0.11.0"
@ -103,7 +124,7 @@ rand = "0.8.5"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] } opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
tracing-opentelemetry = { version = "0.19.0" } tracing-opentelemetry = { version = "0.19.0" }
ts-rs = { version = "6.2", features = ["serde-compat", "chrono-impl"] } 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" futures-util = "0.3.28"
tokio-postgres = "0.7.8" tokio-postgres = "0.7.8"
tokio-postgres-rustls = "0.10.0" tokio-postgres-rustls = "0.10.0"

View File

@ -16,7 +16,8 @@
<a href="readmes/README.es.md">Español</a> | <a href="readmes/README.es.md">Español</a> |
<a href="readmes/README.ru.md">Русский</a> | <a href="readmes/README.ru.md">Русский</a> |
<a href="readmes/README.zh.hans.md">汉语</a> | <a href="readmes/README.zh.hans.md">汉语</a> |
<a href="readmes/README.zh.hant.md">漢語</a> <a href="readmes/README.zh.hant.md">漢語</a> |
<a href="readmes/README.ja.md">日本語</a>
</p> </p>
<p align="center"> <p align="center">

View File

@ -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). 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 ## 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` is removed, and the pictrs config is now a block. If using docker, it should look like:
``` ```
pictrs: { pictrs: {
url: "http://pictrs:8080/" url: "http://pictrs:8080/"
# api_key: "API_KEY" 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. - 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). _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 ## Support development

View File

@ -19,7 +19,7 @@
"eslint": "^8.40.0", "eslint": "^8.40.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"jest": "^29.5.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", "prettier": "^3.0.0",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"typescript": "^5.0.4" "typescript": "^5.0.4"

View File

@ -1,11 +1,15 @@
#!/usr/bin/env bash #!/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 set -e
export RUST_BACKTRACE=1 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" 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 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" psql "${LEMMY_DATABASE_URL}/lemmy" -c "DROP DATABASE IF EXISTS $INSTANCE"
echo "create database"
psql "${LEMMY_DATABASE_URL}/lemmy" -c "CREATE DATABASE $INSTANCE" psql "${LEMMY_DATABASE_URL}/lemmy" -c "CREATE DATABASE $INSTANCE"
done done
@ -26,6 +30,7 @@ else
done done
fi fi
echo "killall existing lemmy_server processes"
killall lemmy_server || true killall lemmy_server || true
echo "$PWD" echo "$PWD"
@ -59,7 +64,12 @@ target/lemmy_server >/tmp/lemmy_epsilon.out 2>&1 &
echo "wait for all instances to start" 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 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 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 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 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 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"

View File

@ -29,6 +29,7 @@ import {
getComments, getComments,
getCommentParentId, getCommentParentId,
resolveCommunity, resolveCommunity,
getPersonDetails,
} from "./shared"; } from "./shared";
import { CommentView } from "lemmy-js-client/dist/types/CommentView"; 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 () => { test("Create a comment in a non-existent post", async () => {
let commentRes = (await createComment(alpha, -1)) as any; await expect(createComment(alpha, -1)).rejects.toBe("couldnt_find_post");
expect(commentRes.error).toBe("couldnt_find_post");
}); });
test("Update a comment", async () => { test("Update a comment", async () => {
@ -122,11 +122,9 @@ test("Delete a comment", async () => {
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true); expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
// Make sure that comment is undefined on beta // Make sure that comment is undefined on beta
let betaCommentRes = (await resolveComment( await expect(
beta, resolveComment(beta, commentRes.comment_view.comment),
commentRes.comment_view.comment, ).rejects.toBe("couldnt_find_object");
)) as any;
expect(betaCommentRes.error).toBe("couldnt_find_object");
let undeleteCommentRes = await deleteComment( let undeleteCommentRes = await deleteComment(
alpha, 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); 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) // 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, alpha,
postRes.post_view.post.id, commentRes.comment_view.comment.creator_id,
); );
expect(refetchedPostComments.comments[0].comment.removed).toBe(true); expect(refetchedPostComments.comments[0].comment.removed).toBe(true);

View File

@ -52,8 +52,9 @@ test("Create community", async () => {
// A dupe check // A dupe check
let prevName = communityRes.community_view.community.name; let prevName = communityRes.community_view.community.name;
let communityRes2: any = await createCommunity(alpha, prevName); await expect(createCommunity(alpha, prevName)).rejects.toBe(
expect(communityRes2["error"]).toBe("community_already_exists"); "community_already_exists",
);
// Cache the community on beta, make sure it has the other fields // Cache the community on beta, make sure it has the other fields
let searchShort = `!${prevName}@lemmy-alpha:8541`; let searchShort = `!${prevName}@lemmy-alpha:8541`;

View File

@ -88,17 +88,18 @@ test("Create a post", async () => {
assertPostFederation(betaPost, postRes.post_view); assertPostFederation(betaPost, postRes.post_view);
// Delta only follows beta, so it should not see an alpha ap_id // Delta only follows beta, so it should not see an alpha ap_id
let deltaPost = (await resolvePost(delta, postRes.post_view.post)).post; await expect(resolvePost(delta, postRes.post_view.post)).rejects.toBe(
expect(deltaPost).toBeUndefined(); "couldnt_find_object",
);
// Epsilon has alpha blocked, it should not see the alpha post // Epsilon has alpha blocked, it should not see the alpha post
let epsilonPost = (await resolvePost(epsilon, postRes.post_view.post)).post; await expect(resolvePost(epsilon, postRes.post_view.post)).rejects.toBe(
expect(epsilonPost).toBeUndefined(); "couldnt_find_object",
);
}); });
test("Create a post in a non-existent community", async () => { test("Create a post in a non-existent community", async () => {
let postRes = (await createPost(alpha, -2)) as any; await expect(createPost(alpha, -2)).rejects.toBe("couldnt_find_community");
expect(postRes.error).toBe("couldnt_find_community");
}); });
test("Unlike a post", async () => { test("Unlike a post", async () => {
@ -145,8 +146,9 @@ test("Update a post", async () => {
assertPostFederation(betaPost, updatedPost.post_view); assertPostFederation(betaPost, updatedPost.post_view);
// Make sure lemmy beta cannot update the post // Make sure lemmy beta cannot update the post
let updatedPostBeta = (await editPost(beta, betaPost.post)) as any; await expect(editPost(beta, betaPost.post)).rejects.toBe(
expect(updatedPostBeta.error).toBe("no_post_edit_allowed"); "no_post_edit_allowed",
);
}); });
test("Sticky a post", async () => { test("Sticky a post", async () => {
@ -210,8 +212,7 @@ test("Lock a post", async () => {
expect(alphaPost1.post.locked).toBe(true); expect(alphaPost1.post.locked).toBe(true);
// Try to make a new comment there, on alpha // Try to make a new comment there, on alpha
let comment: any = await createComment(alpha, alphaPost1.post.id); await expect(createComment(alpha, alphaPost1.post.id)).rejects.toBe("locked");
expect(comment["error"]).toBe("locked");
// Unlock a post // Unlock a post
let unlockedPost = await lockPost(beta, false, betaPost1.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); expect(deletedPost.post_view.post.name).toBe(postRes.post_view.post.name);
// Make sure lemmy beta sees post is deleted // 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 // 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 // Undelete
let undeletedPost = await deletePost(alpha, false, postRes.post_view.post); let undeletedPost = await deletePost(alpha, false, postRes.post_view.post);
@ -259,8 +261,9 @@ test("Delete a post", async () => {
assertPostFederation(betaPost2, undeletedPost.post_view); assertPostFederation(betaPost2, undeletedPost.post_view);
// Make sure lemmy beta cannot delete the post // Make sure lemmy beta cannot delete the post
let deletedPostBeta = (await deletePost(beta, true, betaPost2.post)) as any; await expect(deletePost(beta, true, betaPost2.post)).rejects.toBe(
expect(deletedPostBeta.error).toStrictEqual("no_post_edit_allowed"); "no_post_edit_allowed",
);
}); });
test("Remove a post from admin and community on different instance", async () => { 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); expect(alphaUserOnBeta1.person?.person.banned).toBe(true);
// existing alpha post should be removed on beta // existing alpha post should be removed on beta
let searchBeta2 = await searchPostLocal(beta, postRes1.post_view.post); let searchBeta2 = await getPost(beta, searchBeta1.posts[0].post.id);
expect(searchBeta2.posts[0].post.removed).toBe(true); expect(searchBeta2.post_view.post.removed).toBe(true);
// Unban alpha // Unban alpha
let unBanAlpha = await banPersonFromSite( let unBanAlpha = await banPersonFromSite(
@ -436,12 +439,14 @@ test("Enforce community ban for federated user", async () => {
expect(banAlpha.banned).toBe(true); expect(banAlpha.banned).toBe(true);
// ensure that the post by alpha got removed // ensure that the post by alpha got removed
let searchAlpha1 = await searchPostLocal(alpha, postRes1.post_view.post); await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe(
expect(searchAlpha1.posts[0].post.removed).toBe(true); "unknown",
);
// Alpha tries to make post on beta, but it fails because of ban // Alpha tries to make post on beta, but it fails because of ban
let postRes2 = await createPost(alpha, betaCommunity.community.id); await expect(createPost(alpha, betaCommunity.community.id)).rejects.toBe(
expect(postRes2.post_view).toBeUndefined(); "banned_from_community",
);
// Unban alpha // Unban alpha
let unBanAlpha = await banPersonFromCommunity( let unBanAlpha = await banPersonFromCommunity(

View File

@ -58,6 +58,8 @@ import { CommentReportResponse } from "lemmy-js-client/dist/types/CommentReportR
import { CreateCommentReport } from "lemmy-js-client/dist/types/CreateCommentReport"; import { CreateCommentReport } from "lemmy-js-client/dist/types/CreateCommentReport";
import { ListCommentReportsResponse } from "lemmy-js-client/dist/types/ListCommentReportsResponse"; import { ListCommentReportsResponse } from "lemmy-js-client/dist/types/ListCommentReportsResponse";
import { ListCommentReports } from "lemmy-js-client/dist/types/ListCommentReports"; 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 { export interface API {
client: LemmyHttp; client: LemmyHttp;
@ -186,8 +188,11 @@ export async function setupLogins() {
await epsilon.client.editSite(editSiteForm); await epsilon.client.editSite(editSiteForm);
// Create the main alpha/beta communities // Create the main alpha/beta communities
await createCommunity(alpha, "main"); // Ignore thrown errors of duplicates
await createCommunity(beta, "main"); try {
await createCommunity(alpha, "main");
await createCommunity(beta, "main");
} catch (_) {}
} }
export async function createPost( export async function createPost(
@ -646,6 +651,16 @@ export async function saveUserSettings(
): Promise<LoginResponse> { ): Promise<LoginResponse> {
return api.client.saveUserSettings(form); return api.client.saveUserSettings(form);
} }
export async function getPersonDetails(
api: API,
person_id: number,
): Promise<GetPersonDetailsResponse> {
let form: GetPersonDetails = {
auth: api.auth,
person_id: person_id,
};
return api.client.getPersonDetails(form);
}
export async function deleteUser(api: API): Promise<DeleteAccountResponse> { export async function deleteUser(api: API): Promise<DeleteAccountResponse> {
let form: DeleteAccount = { let form: DeleteAccount = {

View File

@ -92,10 +92,18 @@ test("Delete user", async () => {
await deleteUser(user); await deleteUser(user);
expect((await resolvePost(alpha, localPost)).post).toBeUndefined(); await expect(resolvePost(alpha, localPost)).rejects.toBe(
expect((await resolveComment(alpha, localComment)).comment).toBeUndefined(); "couldnt_find_object",
expect((await resolvePost(alpha, remotePost)).post).toBeUndefined(); );
expect((await resolveComment(alpha, remoteComment)).comment).toBeUndefined(); 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 () => { test("Requests with invalid auth should be treated as unauthenticated", async () => {

View File

@ -2157,10 +2157,10 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
lemmy-js-client@0.17.2-rc.13: lemmy-js-client@0.18.3-rc.3:
version "0.17.2-rc.13" version "0.18.3-rc.3"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.13.tgz#f2a61050c1308e85cb39c0e1f561e392e84e3921" resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.18.3-rc.3.tgz#fc6489eb141bd09558bca38d9e46b40771a29f37"
integrity sha512-4IyR1pisCumJ9L8fEPISC+Su1kVTI4pL/gWLsuOXxZC/lK36mG2+NfaNPiUmIklpCF5TUN+1F7E9bEvtTGogww== integrity sha512-njixgXk4uMU4gGifnljwhSe9Kf445C4wAXcXhtpTtwPPLXpHQgxA1RASMb9Uq4zblfE6nC2JbrAka8y8N2N/Bw==
dependencies: dependencies:
cross-fetch "^3.1.5" cross-fetch "^3.1.5"
form-data "^4.0.0" form-data "^4.0.0"

View File

@ -22,24 +22,19 @@ impl Perform for ListCommentReports {
let data: &ListCommentReports = self; let data: &ListCommentReports = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; 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 community_id = data.community_id;
let unresolved_only = data.unresolved_only; let unresolved_only = data.unresolved_only;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let comment_reports = CommentReportQuery::builder() let comment_reports = CommentReportQuery {
.pool(&mut context.pool()) community_id,
.my_person_id(person_id) unresolved_only,
.admin(admin) page,
.community_id(community_id) limit,
.unresolved_only(unresolved_only) }
.page(page) .list(&mut context.pool(), &local_user_view.person)
.limit(limit) .await?;
.build()
.list()
.await?;
Ok(ListCommentReportsResponse { comment_reports }) Ok(ListCommentReportsResponse { comment_reports })
} }

View File

@ -76,6 +76,9 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use lemmy_api_common::utils::check_validator_time; use lemmy_api_common::utils::check_validator_time;
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{

View File

@ -27,18 +27,17 @@ impl Perform for GetPersonMentions {
let person_id = Some(local_user_view.person.id); let person_id = Some(local_user_view.person.id);
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts); let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
let mentions = PersonMentionQuery::builder() let mentions = PersonMentionQuery {
.pool(&mut context.pool()) recipient_id: person_id,
.recipient_id(person_id) my_person_id: person_id,
.my_person_id(person_id) sort,
.sort(sort) unread_only,
.unread_only(unread_only) show_bot_accounts,
.show_bot_accounts(show_bot_accounts) page,
.page(page) limit,
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
Ok(GetPersonMentionsResponse { mentions }) Ok(GetPersonMentionsResponse { mentions })
} }

View File

@ -24,18 +24,17 @@ impl Perform for GetReplies {
let person_id = Some(local_user_view.person.id); let person_id = Some(local_user_view.person.id);
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts); let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
let replies = CommentReplyQuery::builder() let replies = CommentReplyQuery {
.pool(&mut context.pool()) recipient_id: person_id,
.recipient_id(person_id) my_person_id: person_id,
.my_person_id(person_id) sort,
.sort(sort) unread_only,
.unread_only(unread_only) show_bot_accounts,
.show_bot_accounts(show_bot_accounts) page,
.page(page) limit,
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
Ok(GetRepliesResponse { replies }) Ok(GetRepliesResponse { replies })
} }

View File

@ -133,6 +133,7 @@ impl Perform for SaveUserSettings {
.totp_2fa_secret(totp_2fa_secret) .totp_2fa_secret(totp_2fa_secret)
.totp_2fa_url(totp_2fa_url) .totp_2fa_url(totp_2fa_url)
.open_links_in_new_tab(data.open_links_in_new_tab) .open_links_in_new_tab(data.open_links_in_new_tab)
.infinite_scroll_enabled(data.infinite_scroll_enabled)
.build(); .build();
let local_user_res = let local_user_res =

View File

@ -22,24 +22,19 @@ impl Perform for ListPostReports {
let data: &ListPostReports = self; let data: &ListPostReports = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; 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 community_id = data.community_id;
let unresolved_only = data.unresolved_only; let unresolved_only = data.unresolved_only;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let post_reports = PostReportQuery::builder() let post_reports = PostReportQuery {
.pool(&mut context.pool()) community_id,
.my_person_id(person_id) unresolved_only,
.admin(admin) page,
.community_id(community_id) limit,
.unresolved_only(unresolved_only) }
.page(page) .list(&mut context.pool(), &local_user_view.person)
.limit(limit) .await?;
.build()
.list()
.await?;
Ok(ListPostReportsResponse { post_reports }) Ok(ListPostReportsResponse { post_reports })
} }

View File

@ -21,14 +21,13 @@ impl Perform for ListPrivateMessageReports {
let unresolved_only = self.unresolved_only; let unresolved_only = self.unresolved_only;
let page = self.page; let page = self.page;
let limit = self.limit; let limit = self.limit;
let private_message_reports = PrivateMessageReportQuery::builder() let private_message_reports = PrivateMessageReportQuery {
.pool(&mut context.pool()) unresolved_only,
.unresolved_only(unresolved_only) page,
.page(page) limit,
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
Ok(ListPrivateMessageReportsResponse { Ok(ListPrivateMessageReportsResponse {
private_message_reports, private_message_reports,

View File

@ -23,19 +23,18 @@ impl Perform for ListRegistrationApplications {
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
let unread_only = data.unread_only; 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 page = data.page;
let limit = data.limit; let limit = data.limit;
let registration_applications = RegistrationApplicationQuery::builder() let registration_applications = RegistrationApplicationQuery {
.pool(&mut context.pool()) unread_only,
.unread_only(unread_only) verified_email_only,
.verified_email_only(Some(verified_email_only)) page,
.page(page) limit,
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
Ok(Self::Response { Ok(Self::Response {
registration_applications, registration_applications,

View File

@ -14,9 +14,27 @@ path = "src/lib.rs"
doctest = false doctest = false
[features] [features]
full = ["tracing", "rosetta-i18n", "chrono", "lemmy_utils", full = [
"lemmy_db_views/full", "lemmy_db_views_actor/full", "lemmy_db_views_moderator/full", "tracing",
"percent-encoding", "encoding", "reqwest-middleware", "webpage", "ts-rs"] "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] [dependencies]
lemmy_db_views = { workspace = true } lemmy_db_views = { workspace = true }
@ -24,6 +42,7 @@ lemmy_db_views_moderator = { workspace = true }
lemmy_db_views_actor = { workspace = true } lemmy_db_views_actor = { workspace = true }
lemmy_db_schema = { workspace = true } lemmy_db_schema = { workspace = true }
lemmy_utils = { workspace = true, optional = true } lemmy_utils = { workspace = true, optional = true }
activitypub_federation = { workspace = true, optional = true }
serde = { workspace = true } serde = { workspace = true }
serde_with = { workspace = true } serde_with = { workspace = true }
url = { workspace = true } url = { workspace = true }
@ -33,12 +52,17 @@ reqwest-middleware = { workspace = true, optional = true }
regex = { workspace = true } regex = { workspace = true }
rosetta-i18n = { workspace = true, optional = true } rosetta-i18n = { workspace = true, optional = true }
percent-encoding = { 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 } encoding = { version = "0.2.33", optional = true }
anyhow = { workspace = true } anyhow = { workspace = true }
futures = { workspace = true } futures = { workspace = true, optional = true }
uuid = { workspace = true } uuid = { workspace = true, optional = true }
tokio = { workspace = true } tokio = { workspace = true, optional = true }
reqwest = { workspace = true } reqwest = { workspace = true, optional = true }
ts-rs = { 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"] }

View File

@ -64,7 +64,7 @@ pub async fn build_community_response(
} }
pub async fn build_post_response( pub async fn build_post_response(
context: &Data<LemmyContext>, context: &LemmyContext,
community_id: CommunityId, community_id: CommunityId,
person_id: PersonId, person_id: PersonId,
post_id: PostId, post_id: PostId,

View File

@ -10,6 +10,8 @@ pub mod post;
pub mod private_message; pub mod private_message;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod request; pub mod request;
#[cfg(feature = "full")]
pub mod send_activity;
pub mod sensitive; pub mod sensitive;
pub mod site; pub mod site;
#[cfg(feature = "full")] #[cfg(feature = "full")]

View File

@ -133,6 +133,8 @@ pub struct SaveUserSettings {
pub auth: Sensitive<String>, pub auth: Sensitive<String>,
/// Open links in a new tab /// Open links in a new tab
pub open_links_in_new_tab: Option<bool>, pub open_links_in_new_tab: Option<bool>,
/// Enable infinite scroll
pub infinite_scroll_enabled: Option<bool>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]

View File

@ -270,6 +270,9 @@ pub fn build_user_agent(settings: &Settings) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::request::{ use crate::request::{
build_user_agent, build_user_agent,
fetch_site_metadata, fetch_site_metadata,

View File

@ -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<for<'a> fn(SendActivityData, &'a Data<LemmyContext>) -> BoxFuture<'a, LemmyResult<()>>>;
/// This static is necessary so that activities can be sent out synchronously for tests.
pub static MATCH_OUTGOING_ACTIVITIES: OnceCell<MatchOutgoingActivitiesBoxed> = 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<ActivityChannel> = 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<SendActivityData>,
receiver: Mutex<UnboundedReceiver<SendActivityData>>,
keepalive_sender: Mutex<Option<UnboundedSender<SendActivityData>>>,
}
impl ActivityChannel {
pub async fn retrieve_activity() -> Option<SendActivityData> {
let mut lock = ACTIVITY_CHANNEL.receiver.lock().await;
lock.recv().await
}
pub async fn submit_activity(
data: SendActivityData,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
if *SYNCHRONOUS_FEDERATION {
MATCH_OUTGOING_ACTIVITIES
.get()
.expect("retrieve function pointer")(data, context)
.await?;
}
// could do `ACTIVITY_CHANNEL.keepalive_sender.lock()` instead and get rid of weak_sender,
// not sure which way is more efficient
else if let Some(sender) = ACTIVITY_CHANNEL.weak_sender.upgrade() {
sender.send(data)?;
}
Ok(())
}
pub async fn close(outgoing_activities_task: JoinHandle<LemmyResult<()>>) -> LemmyResult<()> {
ACTIVITY_CHANNEL.keepalive_sender.lock().await.take();
outgoing_activities_task.await??;
Ok(())
}
}

View File

@ -667,13 +667,13 @@ pub async fn remove_user_data_in_community(
// Comments // Comments
// TODO Diesel doesn't allow updates with joins, so this has to be a loop // TODO Diesel doesn't allow updates with joins, so this has to be a loop
let comments = CommentQuery::builder() let comments = CommentQuery {
.pool(pool) creator_id: Some(banned_person_id),
.creator_id(Some(banned_person_id)) community_id: Some(community_id),
.community_id(Some(community_id)) ..Default::default()
.build() }
.list() .list(pool)
.await?; .await?;
for comment_view in &comments { for comment_view in &comments {
let comment_id = comment_view.comment.id; let comment_id = comment_view.comment.id;
@ -731,6 +731,9 @@ pub async fn delete_user_account(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::utils::{honeypot_check, password_length_check}; use crate::utils::{honeypot_check, password_length_check};
#[test] #[test]

View File

@ -23,4 +23,4 @@ url = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
webmention = "0.4.0" webmention = "0.4.0"
chrono = { workspace = true } chrono = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }

View File

@ -16,6 +16,7 @@ use lemmy_api_common::{
}, },
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
impls::actor_language::default_post_language,
source::{ source::{
actor_language::CommunityLanguage, actor_language::CommunityLanguage,
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm}, comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
@ -82,25 +83,31 @@ impl PerformCrud for CreateComment {
check_comment_depth(parent)?; check_comment_depth(parent)?;
} }
// if no language is set, copy language from parent post/comment
let parent_language = parent_opt
.as_ref()
.map(|p| p.language_id)
.unwrap_or(post.language_id);
let language_id = data.language_id.unwrap_or(parent_language);
CommunityLanguage::is_allowed_community_language( CommunityLanguage::is_allowed_community_language(
&mut context.pool(), &mut context.pool(),
Some(language_id), data.language_id,
community_id, community_id,
) )
.await?; .await?;
// attempt to set default language if none was provided
let language_id = match data.language_id {
Some(lid) => Some(lid),
None => {
default_post_language(
&mut context.pool(),
community_id,
local_user_view.local_user.id,
)
.await?
}
};
let comment_form = CommentInsertForm::builder() let comment_form = CommentInsertForm::builder()
.content(content_slurs_removed.clone()) .content(content_slurs_removed.clone())
.post_id(data.post_id) .post_id(data.post_id)
.creator_id(local_user_view.person.id) .creator_id(local_user_view.person.id)
.language_id(Some(language_id)) .language_id(language_id)
.build(); .build();
// Create the comment // Create the comment

View File

@ -31,18 +31,18 @@ impl PerformCrud for ListCommunities {
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let local_user = local_user_view.map(|l| l.local_user); let local_user = local_user_view.map(|l| l.local_user);
let communities = CommunityQuery::builder() let communities = CommunityQuery {
.pool(&mut context.pool()) listing_type,
.listing_type(listing_type) show_nsfw,
.show_nsfw(show_nsfw) sort,
.sort(sort) local_user: local_user.as_ref(),
.local_user(local_user.as_ref()) page,
.page(page) limit,
.limit(limit) is_mod_or_admin: is_admin,
.is_mod_or_admin(is_admin) ..Default::default()
.build() }
.list() .list(&mut context.pool())
.await?; .await?;
// Return the jwt // Return the jwt
Ok(ListCommunitiesResponse { communities }) Ok(ListCommunitiesResponse { communities })

View File

@ -5,7 +5,7 @@ use lemmy_utils::error::LemmyError;
mod comment; mod comment;
mod community; mod community;
mod custom_emoji; mod custom_emoji;
mod post; pub mod post;
mod private_message; mod private_message;
mod site; mod site;
mod user; mod user;

View File

@ -1,10 +1,11 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{CreatePost, PostResponse}, post::{CreatePost, PostResponse},
request::fetch_site_data, request::fetch_site_data,
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -40,147 +41,153 @@ use tracing::Instrument;
use url::Url; use url::Url;
use webmention::{Webmention, WebmentionError}; use webmention::{Webmention, WebmentionError};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for CreatePost { pub async fn create_post(
type Response = PostResponse; data: Json<CreatePost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let slur_regex = local_site_to_slur_regex(&local_site);
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { check_slurs(&data.name, &slur_regex)?;
let data: &CreatePost = self; check_slurs_opt(&data.body, &slur_regex)?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; honeypot_check(&data.honeypot)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let slur_regex = local_site_to_slur_regex(&local_site); let data_url = data.url.as_ref();
check_slurs(&data.name, &slur_regex)?; let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
check_slurs_opt(&data.body, &slur_regex)?;
honeypot_check(&data.honeypot)?;
let data_url = data.url.as_ref(); is_valid_post_title(&data.name)?;
let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear" is_valid_body_field(&data.body, true)?;
check_url_scheme(&data.url)?;
is_valid_post_title(&data.name)?; check_community_ban(
is_valid_body_field(&data.body, true)?; local_user_view.person.id,
check_url_scheme(&data.url)?; data.community_id,
&mut context.pool(),
check_community_ban( )
local_user_view.person.id, .await?;
data.community_id, check_community_deleted_or_removed(data.community_id, &mut context.pool()).await?;
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(data.community_id, &mut context.pool()).await?;
let community_id = data.community_id;
let community = Community::read(&mut context.pool(), community_id).await?;
if community.posting_restricted_to_mods {
let community_id = data.community_id; let community_id = data.community_id;
let community = Community::read(&mut context.pool(), community_id).await?; let is_mod = CommunityView::is_mod_or_admin(
if community.posting_restricted_to_mods {
let community_id = data.community_id;
let is_mod = CommunityView::is_mod_or_admin(
&mut context.pool(),
local_user_view.local_user.person_id,
community_id,
)
.await?;
if !is_mod {
return Err(LemmyErrorType::OnlyModsCanPostInCommunity)?;
}
}
// Fetch post links and pictrs cached image
let (metadata_res, thumbnail_url) =
fetch_site_data(context.client(), context.settings(), data_url, true).await;
let (embed_title, embed_description, embed_video_url) = metadata_res
.map(|u| (u.title, u.description, u.embed_video_url))
.unwrap_or_default();
let language_id = match data.language_id {
Some(lid) => Some(lid),
None => {
default_post_language(
&mut context.pool(),
community_id,
local_user_view.local_user.id,
)
.await?
}
};
CommunityLanguage::is_allowed_community_language(
&mut context.pool(), &mut context.pool(),
language_id, local_user_view.local_user.person_id,
community_id, community_id,
) )
.await?; .await?;
if !is_mod {
return Err(LemmyErrorType::OnlyModsCanPostInCommunity)?;
}
}
let post_form = PostInsertForm::builder() // Fetch post links and pictrs cached image
.name(data.name.trim().to_owned()) let (metadata_res, thumbnail_url) =
.url(url) fetch_site_data(context.client(), context.settings(), data_url, true).await;
.body(data.body.clone()) let (embed_title, embed_description, embed_video_url) = metadata_res
.community_id(data.community_id) .map(|u| (u.title, u.description, u.embed_video_url))
.creator_id(local_user_view.person.id) .unwrap_or_default();
.nsfw(data.nsfw)
.embed_title(embed_title)
.embed_description(embed_description)
.embed_video_url(embed_video_url)
.language_id(language_id)
.thumbnail_url(thumbnail_url)
.build();
let inserted_post = Post::create(&mut context.pool(), &post_form) // Only need to check if language is allowed in case user set it explicitly. When using default
.await // language, it already only returns allowed languages.
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; CommunityLanguage::is_allowed_community_language(
&mut context.pool(),
data.language_id,
community_id,
)
.await?;
let inserted_post_id = inserted_post.id; // attempt to set default language if none was provided
let protocol_and_hostname = context.settings().get_protocol_and_hostname(); let language_id = match data.language_id {
let apub_id = generate_local_apub_endpoint( Some(lid) => Some(lid),
EndpointType::Post, None => {
&inserted_post_id.to_string(), default_post_language(
&protocol_and_hostname, &mut context.pool(),
)?; community_id,
let updated_post = Post::update( local_user_view.local_user.id,
&mut context.pool(), )
inserted_post_id, .await?
&PostUpdateForm::builder().ap_id(Some(apub_id)).build(), }
) };
let post_form = PostInsertForm::builder()
.name(data.name.trim().to_owned())
.url(url)
.body(data.body.clone())
.community_id(data.community_id)
.creator_id(local_user_view.person.id)
.nsfw(data.nsfw)
.embed_title(embed_title)
.embed_description(embed_description)
.embed_video_url(embed_video_url)
.language_id(language_id)
.thumbnail_url(thumbnail_url)
.build();
let inserted_post = Post::create(&mut context.pool(), &post_form)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
// They like their own post by default let inserted_post_id = inserted_post.id;
let person_id = local_user_view.person.id; let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let post_id = inserted_post.id; let apub_id = generate_local_apub_endpoint(
let like_form = PostLikeForm { EndpointType::Post,
post_id, &inserted_post_id.to_string(),
person_id, &protocol_and_hostname,
score: 1, )?;
}; let updated_post = Post::update(
&mut context.pool(),
inserted_post_id,
&PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
PostLike::like(&mut context.pool(), &like_form) // They like their own post by default
.await let person_id = local_user_view.person.id;
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?; let post_id = inserted_post.id;
let like_form = PostLikeForm {
post_id,
person_id,
score: 1,
};
// Mark the post as read PostLike::like(&mut context.pool(), &like_form)
mark_post_as_read(person_id, post_id, &mut context.pool()).await?; .await
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
if let Some(url) = updated_post.url.clone() { ActivityChannel::submit_activity(SendActivityData::CreatePost(updated_post.clone()), &context)
let task = async move { .await?;
let mut webmention =
Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?; // Mark the post as read
webmention.set_checked(true); mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
match webmention
.send() if let Some(url) = updated_post.url.clone() {
.instrument(tracing::info_span!("Sending webmention")) let task = async move {
.await let mut webmention =
{ Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()), webmention.set_checked(true);
Ok(_) => Ok(()), match webmention
Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention), .send()
} .instrument(tracing::info_span!("Sending webmention"))
}; .await
if *SYNCHRONOUS_FEDERATION { {
task.await?; Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()),
} else { Ok(_) => Ok(()),
spawn_try_task(task); Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention),
} }
}; };
if *SYNCHRONOUS_FEDERATION {
task.await?;
} else {
spawn_try_task(task);
}
};
build_post_response(context, community_id, person_id, post_id).await Ok(Json(
} build_post_response(&context, community_id, person_id, post_id).await?,
))
} }

View File

@ -1,4 +1,4 @@
mod create; pub mod create;
mod delete; mod delete;
mod read; mod read;
mod remove; mod remove;

View File

@ -100,12 +100,12 @@ impl PerformCrud for GetPost {
// Fetch the cross_posts // Fetch the cross_posts
let cross_posts = if let Some(url) = &post_view.post.url { let cross_posts = if let Some(url) = &post_view.post.url {
let mut x_posts = PostQuery::builder() let mut x_posts = PostQuery {
.pool(&mut context.pool()) url_search: Some(url.inner().as_str().into()),
.url_search(Some(url.inner().as_str().into())) ..Default::default()
.build() }
.list() .list(&mut context.pool())
.await?; .await?;
// Don't return this post as one of the cross_posts // Don't return this post as one of the cross_posts
x_posts.retain(|x| x.post.id != post_id); x_posts.retain(|x| x.post.id != post_id);

View File

@ -24,15 +24,13 @@ impl PerformCrud for GetPrivateMessages {
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let unread_only = data.unread_only; let unread_only = data.unread_only;
let mut messages = PrivateMessageQuery::builder() let mut messages = PrivateMessageQuery {
.pool(&mut context.pool()) page,
.recipient_id(person_id) limit,
.page(page) unread_only,
.limit(limit) }
.unread_only(unread_only) .list(&mut context.pool(), person_id)
.build() .await?;
.list()
.await?;
// Messages sent by ourselves should be marked as read. The `read` column in database is only // Messages sent by ourselves should be marked as read. The `read` column in database is only
// for the recipient, and shouldnt be exposed to sender. // for the recipient, and shouldnt be exposed to sender.

View File

@ -183,6 +183,9 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::site::create::validate_create_payload; use crate::site::create::validate_create_payload;
use lemmy_api_common::site::CreateSite; use lemmy_api_common::site::CreateSite;
use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode}; use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode};

View File

@ -42,6 +42,9 @@ pub fn application_question_check(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::site::{application_question_check, site_default_post_listing_type_check}; use crate::site::{application_question_check, site_default_post_listing_type_check};
use lemmy_db_schema::{ListingType, RegistrationMode}; use lemmy_db_schema::{ListingType, RegistrationMode};

View File

@ -217,6 +217,9 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::site::update::validate_update_payload; use crate::site::update::validate_update_payload;
use lemmy_api_common::site::EditSite; use lemmy_api_common::site::EditSite;
use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode}; use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode};

View File

@ -138,6 +138,7 @@ impl PerformCrud for Register {
.password_encrypted(data.password.to_string()) .password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw)) .show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application) .accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type))
.build(); .build();
let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?; let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?;

View File

@ -25,7 +25,7 @@ chrono = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
actix-web = { workspace = true } actix-web = { workspace = true }
tokio = {workspace = true} tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
strum_macros = { workspace = true } strum_macros = { workspace = true }
url = { workspace = true } url = { workspace = true }

View File

@ -9,7 +9,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
objects::{instance::remote_instance_inboxes, person::ApubPerson}, objects::{instance::remote_instance_inboxes, person::ApubPerson},
protocol::activities::block::block_user::BlockUser, protocol::activities::block::block_user::BlockUser,
}; };
@ -124,6 +124,7 @@ impl ActivityHandler for BlockUser {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
match self.target.dereference(context).await? { match self.target.dereference(context).await? {
SiteOrCommunity::Site(site) => { SiteOrCommunity::Site(site) => {
@ -147,7 +148,6 @@ impl ActivityHandler for BlockUser {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let expires = self.expires.map(|u| u.naive_local()); let expires = self.expires.map(|u| u.naive_local());
let mod_person = self.actor.dereference(context).await?; let mod_person = self.actor.dereference(context).await?;
let blocked_person = self.object.dereference(context).await?; let blocked_person = self.object.dereference(context).await?;

View File

@ -7,7 +7,7 @@ use crate::{
verify_is_public, verify_is_public,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
objects::{instance::remote_instance_inboxes, person::ApubPerson}, objects::{instance::remote_instance_inboxes, person::ApubPerson},
protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser}, protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
}; };
@ -88,6 +88,7 @@ impl ActivityHandler for UndoBlockUser {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
verify_domains_match(self.actor.inner(), self.object.actor.inner())?; verify_domains_match(self.actor.inner(), self.object.actor.inner())?;
self.object.verify(context).await?; self.object.verify(context).await?;
@ -96,7 +97,6 @@ impl ActivityHandler for UndoBlockUser {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let expires = self.object.expires.map(|u| u.naive_local()); let expires = self.object.expires.map(|u| u.naive_local());
let mod_person = self.actor.dereference(context).await?; let mod_person = self.actor.dereference(context).await?;
let blocked_person = self.object.object.dereference(context).await?; let blocked_person = self.object.object.dereference(context).await?;

View File

@ -6,7 +6,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
objects::community::ApubCommunity, objects::community::ApubCommunity,
protocol::{ protocol::{
activities::community::announce::{AnnounceActivity, RawAnnouncableActivities}, activities::community::announce::{AnnounceActivity, RawAnnouncableActivities},
@ -133,14 +133,14 @@ impl ActivityHandler for AnnounceActivity {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, _context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
Ok(()) Ok(())
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let object: AnnouncableActivities = self.object.object(context).await?.try_into()?; let object: AnnouncableActivities = self.object.object(context).await?.try_into()?;
// This is only for sending, not receiving so we reject it. // This is only for sending, not receiving so we reject it.
if let AnnouncableActivities::Page(_) = object { if let AnnouncableActivities::Page(_) = object {

View File

@ -7,7 +7,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{ protocol::{
activities::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove}, activities::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove},
@ -108,6 +108,7 @@ impl ActivityHandler for CollectionAdd {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
@ -117,7 +118,6 @@ impl ActivityHandler for CollectionAdd {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let (community, collection_type) = let (community, collection_type) =
Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?; Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?;
match collection_type { match collection_type {

View File

@ -7,7 +7,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{activities::community::collection_remove::CollectionRemove, InCommunity}, protocol::{activities::community::collection_remove::CollectionRemove, InCommunity},
}; };
@ -101,6 +101,7 @@ impl ActivityHandler for CollectionRemove {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
@ -110,7 +111,6 @@ impl ActivityHandler for CollectionRemove {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let (community, collection_type) = let (community, collection_type) =
Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?; Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?;
match collection_type { match collection_type {

View File

@ -8,7 +8,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
protocol::{ protocol::{
activities::community::lock_page::{LockPage, LockType, UndoLockPage}, activities::community::lock_page::{LockPage, LockType, UndoLockPage},
InCommunity, InCommunity,
@ -79,6 +79,7 @@ impl ActivityHandler for UndoLockPage {
} }
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), Self::Error> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
@ -94,7 +95,6 @@ impl ActivityHandler for UndoLockPage {
} }
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), Self::Error> {
insert_activity(&self.id, &self, false, false, context).await?;
let form = PostUpdateForm::builder().locked(Some(false)).build(); let form = PostUpdateForm::builder().locked(Some(false)).build();
let post = self.object.object.dereference(context).await?; let post = self.object.object.dereference(context).await?;
Post::update(&mut context.pool(), post.id, &form).await?; Post::update(&mut context.pool(), post.id, &form).await?;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community}, activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community},
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::report::Report, InCommunity}, protocol::{activities::community::report::Report, InCommunity},
PostOrComment, PostOrComment,
@ -115,6 +115,7 @@ impl ActivityHandler for Report {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
Ok(()) Ok(())
@ -122,7 +123,6 @@ impl ActivityHandler for Report {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, true, context).await?;
let actor = self.actor.dereference(context).await?; let actor = self.actor.dereference(context).await?;
match self.object.dereference(context).await? { match self.object.dereference(context).await? {
PostOrComment::Post(post) => { PostOrComment::Post(post) => {

View File

@ -7,7 +7,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::update::UpdateCommunity, InCommunity}, protocol::{activities::community::update::UpdateCommunity, InCommunity},
SendActivity, SendActivity,
@ -82,6 +82,7 @@ impl ActivityHandler for UpdateCommunity {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
@ -92,7 +93,6 @@ impl ActivityHandler for UpdateCommunity {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let community = self.community(context).await?; let community = self.community(context).await?;
let community_update_form = self.object.into_update_form(); let community_update_form = self.object.into_update_form();

View File

@ -7,7 +7,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
mentions::MentionOrValue, mentions::MentionOrValue,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson}, objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
protocol::{ protocol::{
@ -154,6 +154,7 @@ impl ActivityHandler for CreateOrUpdateNote {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
let post = self.object.get_parents(context).await?.0; let post = self.object.get_parents(context).await?.0;
let community = self.community(context).await?; let community = self.community(context).await?;
@ -169,7 +170,6 @@ impl ActivityHandler for CreateOrUpdateNote {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
// Need to do this check here instead of Note::from_json because we need the person who // Need to do this check here instead of Note::from_json because we need the person who
// send the activity, not the comment author. // send the activity, not the comment author.
let existing_comment = self.object.id.dereference_local(context).await.ok(); let existing_comment = self.object.id.dereference_local(context).await.ok();

View File

@ -8,7 +8,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost}, objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{ protocol::{
activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType}, activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
@ -24,7 +24,7 @@ use activitypub_federation::{
}; };
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{CreatePost, EditPost, PostResponse}, post::{EditPost, PostResponse},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::PostAggregates, aggregates::structs::PostAggregates,
@ -39,25 +39,6 @@ use lemmy_db_schema::{
use lemmy_utils::error::{LemmyError, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorType};
use url::Url; use url::Url;
#[async_trait::async_trait]
impl SendActivity for CreatePost {
type Response = PostResponse;
async fn send_activity(
_request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
CreateOrUpdatePage::send(
&response.post_view.post,
response.post_view.creator.id,
CreateOrUpdateType::Create,
context,
)
.await
}
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl SendActivity for EditPost { impl SendActivity for EditPost {
type Response = PostResponse; type Response = PostResponse;
@ -68,10 +49,10 @@ impl SendActivity for EditPost {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
CreateOrUpdatePage::send( CreateOrUpdatePage::send(
&response.post_view.post, response.post_view.post.clone(),
response.post_view.creator.id, response.post_view.creator.id,
CreateOrUpdateType::Update, CreateOrUpdateType::Update,
context, context.reset_request_count(),
) )
.await .await
} }
@ -102,12 +83,12 @@ impl CreateOrUpdatePage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub(crate) async fn send( pub(crate) async fn send(
post: &Post, post: Post,
person_id: PersonId, person_id: PersonId,
kind: CreateOrUpdateType, kind: CreateOrUpdateType,
context: &Data<LemmyContext>, context: Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let post = ApubPost(post.clone()); let post = ApubPost(post);
let community_id = post.community_id; let community_id = post.community_id;
let person: ApubPerson = Person::read(&mut context.pool(), person_id).await?.into(); let person: ApubPerson = Person::read(&mut context.pool(), person_id).await?.into();
let community: ApubCommunity = Community::read(&mut context.pool(), community_id) let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
@ -115,8 +96,8 @@ impl CreateOrUpdatePage {
.into(); .into();
let create_or_update = let create_or_update =
CreateOrUpdatePage::new(post, &person, &community, kind, context).await?; CreateOrUpdatePage::new(post, &person, &community, kind, &context).await?;
let is_mod_action = create_or_update.object.is_mod_action(context).await?; let is_mod_action = create_or_update.object.is_mod_action(&context).await?;
let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update); let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
send_activity_in_community( send_activity_in_community(
activity, activity,
@ -124,7 +105,7 @@ impl CreateOrUpdatePage {
&community, &community,
vec![], vec![],
is_mod_action, is_mod_action,
context, &context,
) )
.await?; .await?;
Ok(()) Ok(())
@ -146,6 +127,7 @@ impl ActivityHandler for CreateOrUpdatePage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &self.cc)?; verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
@ -180,7 +162,6 @@ impl ActivityHandler for CreateOrUpdatePage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let post = ApubPost::from_json(self.object, context).await?; let post = ApubPost::from_json(self.object, context).await?;
// author likes their own post by default // author likes their own post by default

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person}, activities::{generate_activity_id, send_lemmy_activity, verify_person},
insert_activity, insert_received_activity,
objects::{person::ApubPerson, private_message::ApubPrivateMessage}, objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::{ protocol::activities::{
create_or_update::chat_message::CreateOrUpdateChatMessage, create_or_update::chat_message::CreateOrUpdateChatMessage,
@ -109,6 +109,7 @@ impl ActivityHandler for CreateOrUpdateChatMessage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_person(&self.actor, context).await?; verify_person(&self.actor, context).await?;
verify_domains_match(self.actor.inner(), self.object.id.inner())?; verify_domains_match(self.actor.inner(), self.object.id.inner())?;
verify_domains_match(self.to[0].inner(), self.object.to[0].inner())?; verify_domains_match(self.to[0].inner(), self.object.to[0].inner())?;
@ -118,7 +119,6 @@ impl ActivityHandler for CreateOrUpdateChatMessage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, true, context).await?;
ApubPrivateMessage::from_json(self.object, context).await?; ApubPrivateMessage::from_json(self.object, context).await?;
Ok(()) Ok(())
} }

View File

@ -3,7 +3,7 @@ use crate::{
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects}, deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
generate_activity_id, generate_activity_id,
}, },
insert_activity, insert_received_activity,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::{activities::deletion::delete::Delete, IdOrNestedObject}, protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
}; };
@ -43,13 +43,13 @@ impl ActivityHandler for Delete {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_delete_activity(self, self.summary.is_some(), context).await?; verify_delete_activity(self, self.summary.is_some(), context).await?;
Ok(()) Ok(())
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
if let Some(reason) = self.summary { if let Some(reason) = self.summary {
// We set reason to empty string if it doesn't exist, to distinguish between delete and // We set reason to empty string if it doesn't exist, to distinguish between delete and
// remove. Here we change it back to option, so we don't write it to db. // remove. Here we change it back to option, so we don't write it to db.

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_is_public, verify_person}, activities::{generate_activity_id, send_lemmy_activity, verify_is_public, verify_person},
insert_activity, insert_received_activity,
objects::{instance::remote_instance_inboxes, person::ApubPerson}, objects::{instance::remote_instance_inboxes, person::ApubPerson},
protocol::activities::deletion::delete_user::DeleteUser, protocol::activities::deletion::delete_user::DeleteUser,
SendActivity, SendActivity,
@ -73,6 +73,7 @@ impl ActivityHandler for DeleteUser {
} }
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_is_public(&self.to, &[])?; verify_is_public(&self.to, &[])?;
verify_person(&self.actor, context).await?; verify_person(&self.actor, context).await?;
verify_urls_match(self.actor.inner(), self.object.inner())?; verify_urls_match(self.actor.inner(), self.object.inner())?;
@ -80,7 +81,6 @@ impl ActivityHandler for DeleteUser {
} }
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
let actor = self.actor.dereference(context).await?; let actor = self.actor.dereference(context).await?;
delete_user_account( delete_user_account(
actor.id, actor.id,

View File

@ -3,7 +3,7 @@ use crate::{
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects}, deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
generate_activity_id, generate_activity_id,
}, },
insert_activity, insert_received_activity,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete}, protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
}; };
@ -42,6 +42,7 @@ impl ActivityHandler for UndoDelete {
} }
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
insert_received_activity(&self.id, data).await?;
self.object.verify(data).await?; self.object.verify(data).await?;
verify_delete_activity(&self.object, self.object.summary.is_some(), data).await?; verify_delete_activity(&self.object, self.object.summary.is_some(), data).await?;
Ok(()) Ok(())
@ -49,7 +50,6 @@ impl ActivityHandler for UndoDelete {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, false, context).await?;
if self.object.summary.is_some() { if self.object.summary.is_some() {
UndoDelete::receive_undo_remove_action( UndoDelete::receive_undo_remove_action(
&self.actor.dereference(context).await?, &self.actor.dereference(context).await?,

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity}, activities::{generate_activity_id, send_lemmy_activity},
insert_activity, insert_received_activity,
protocol::activities::following::{accept::AcceptFollow, follow::Follow}, protocol::activities::following::{accept::AcceptFollow, follow::Follow},
}; };
use activitypub_federation::{ use activitypub_federation::{
@ -50,6 +50,7 @@ impl ActivityHandler for AcceptFollow {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_urls_match(self.actor.inner(), self.object.object.inner())?; verify_urls_match(self.actor.inner(), self.object.object.inner())?;
self.object.verify(context).await?; self.object.verify(context).await?;
if let Some(to) = &self.to { if let Some(to) = &self.to {
@ -60,7 +61,6 @@ impl ActivityHandler for AcceptFollow {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, true, context).await?;
let community = self.actor.dereference(context).await?; let community = self.actor.dereference(context).await?;
let person = self.object.actor.dereference(context).await?; let person = self.object.actor.dereference(context).await?;
// This will throw an error if no follow was requested // This will throw an error if no follow was requested

View File

@ -6,7 +6,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
}, },
fetcher::user_or_community::UserOrCommunity, fetcher::user_or_community::UserOrCommunity,
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{ protocol::activities::following::{
accept::AcceptFollow, accept::AcceptFollow,
@ -90,6 +90,7 @@ impl ActivityHandler for Follow {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_person(&self.actor, context).await?; verify_person(&self.actor, context).await?;
let object = self.object.dereference(context).await?; let object = self.object.dereference(context).await?;
if let UserOrCommunity::Community(c) = object { if let UserOrCommunity::Community(c) = object {
@ -103,7 +104,6 @@ impl ActivityHandler for Follow {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, true, context).await?;
let actor = self.actor.dereference(context).await?; let actor = self.actor.dereference(context).await?;
let object = self.object.dereference(context).await?; let object = self.object.dereference(context).await?;
match object { match object {

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person}, activities::{generate_activity_id, send_lemmy_activity, verify_person},
fetcher::user_or_community::UserOrCommunity, fetcher::user_or_community::UserOrCommunity,
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{follow::Follow, undo_follow::UndoFollow}, protocol::activities::following::{follow::Follow, undo_follow::UndoFollow},
}; };
@ -60,6 +60,7 @@ impl ActivityHandler for UndoFollow {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
verify_urls_match(self.actor.inner(), self.object.actor.inner())?; verify_urls_match(self.actor.inner(), self.object.actor.inner())?;
verify_person(&self.actor, context).await?; verify_person(&self.actor, context).await?;
self.object.verify(context).await?; self.object.verify(context).await?;
@ -71,7 +72,6 @@ impl ActivityHandler for UndoFollow {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, true, context).await?;
let person = self.actor.dereference(context).await?; let person = self.actor.dereference(context).await?;
let object = self.object.object.dereference(context).await?; let object = self.object.object.dereference(context).await?;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
insert_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
CONTEXT, CONTEXT,
}; };
use activitypub_federation::{ use activitypub_federation::{
@ -12,12 +12,28 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use anyhow::anyhow; use anyhow::anyhow;
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::{
use lemmy_db_schema::{newtypes::CommunityId, source::community::Community}; context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
};
use lemmy_db_schema::{
newtypes::CommunityId,
source::{
activity::{SentActivity, SentActivityForm},
community::Community,
instance::Instance,
},
};
use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView}; use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
spawn_try_task,
SYNCHRONOUS_FEDERATION,
};
use moka::future::Cache;
use once_cell::sync::Lazy;
use serde::Serialize; use serde::Serialize;
use std::ops::Deref; use std::{ops::Deref, sync::Arc, time::Duration};
use tracing::info; use tracing::info;
use url::{ParseError, Url}; use url::{ParseError, Url};
use uuid::Uuid; use uuid::Uuid;
@ -30,6 +46,10 @@ pub mod following;
pub mod unfederated; pub mod unfederated;
pub mod voting; pub mod voting;
/// Amount of time that the list of dead instances is cached. This is only updated once a day,
/// so there is no harm in caching it for a longer time.
pub static DEAD_INSTANCE_LIST_CACHE_DURATION: Duration = Duration::from_secs(30 * 60);
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person /// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
/// doesn't have a site ban. /// doesn't have a site ban.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -148,7 +168,7 @@ async fn send_lemmy_activity<Activity, ActorT>(
data: &Data<LemmyContext>, data: &Data<LemmyContext>,
activity: Activity, activity: Activity,
actor: &ActorT, actor: &ActorT,
inbox: Vec<Url>, mut inbox: Vec<Url>,
sensitive: bool, sensitive: bool,
) -> Result<(), LemmyError> ) -> Result<(), LemmyError>
where where
@ -156,11 +176,62 @@ where
ActorT: Actor, ActorT: Actor,
Activity: ActivityHandler<Error = LemmyError>, Activity: ActivityHandler<Error = LemmyError>,
{ {
static CACHE: Lazy<Cache<(), Arc<Vec<String>>>> = Lazy::new(|| {
Cache::builder()
.max_capacity(1)
.time_to_live(DEAD_INSTANCE_LIST_CACHE_DURATION)
.build()
});
let dead_instances = CACHE
.try_get_with((), async {
Ok::<_, diesel::result::Error>(Arc::new(Instance::dead_instances(&mut data.pool()).await?))
})
.await?;
inbox.retain(|i| {
let domain = i.domain().expect("has domain").to_string();
!dead_instances.contains(&domain)
});
info!("Sending activity {}", activity.id().to_string()); info!("Sending activity {}", activity.id().to_string());
let activity = WithContext::new(activity, CONTEXT.deref().clone()); let activity = WithContext::new(activity, CONTEXT.deref().clone());
insert_activity(activity.id(), &activity, true, sensitive, data).await?; let form = SentActivityForm {
ap_id: activity.id().clone().into(),
data: serde_json::to_value(activity.clone())?,
sensitive,
};
SentActivity::create(&mut data.pool(), form).await?;
send_activity(activity, actor, inbox, data).await?; send_activity(activity, actor, inbox, data).await?;
Ok(()) Ok(())
} }
pub async fn handle_outgoing_activities(context: Data<LemmyContext>) -> LemmyResult<()> {
while let Some(data) = ActivityChannel::retrieve_activity().await {
match_outgoing_activities(data, &context.reset_request_count()).await?
}
Ok(())
}
pub async fn match_outgoing_activities(
data: SendActivityData,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let fed_task = match data {
SendActivityData::CreatePost(post) => {
let creator_id = post.creator_id;
CreateOrUpdatePage::send(
post,
creator_id,
CreateOrUpdateType::Create,
context.reset_request_count(),
)
}
};
if *SYNCHRONOUS_FEDERATION {
fed_task.await?;
} else {
spawn_try_task(fed_task);
}
Ok(())
}

View File

@ -4,7 +4,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
voting::{undo_vote_comment, undo_vote_post}, voting::{undo_vote_comment, undo_vote_post},
}, },
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{ protocol::{
activities::voting::{undo_vote::UndoVote, vote::Vote}, activities::voting::{undo_vote::UndoVote, vote::Vote},
@ -57,6 +57,7 @@ impl ActivityHandler for UndoVote {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
verify_urls_match(self.actor.inner(), self.object.actor.inner())?; verify_urls_match(self.actor.inner(), self.object.actor.inner())?;
@ -66,7 +67,6 @@ impl ActivityHandler for UndoVote {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, true, context).await?;
let actor = self.actor.dereference(context).await?; let actor = self.actor.dereference(context).await?;
let object = self.object.object.dereference(context).await?; let object = self.object.object.dereference(context).await?;
match object { match object {

View File

@ -4,7 +4,7 @@ use crate::{
verify_person_in_community, verify_person_in_community,
voting::{vote_comment, vote_post}, voting::{vote_comment, vote_post},
}, },
insert_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{ protocol::{
activities::voting::vote::{Vote, VoteType}, activities::voting::vote::{Vote, VoteType},
@ -56,6 +56,7 @@ impl ActivityHandler for Vote {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_received_activity(&self.id, context).await?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
let enable_downvotes = LocalSite::read(&mut context.pool()) let enable_downvotes = LocalSite::read(&mut context.pool())
@ -70,7 +71,6 @@ impl ActivityHandler for Vote {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> { async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
insert_activity(&self.id, &self, false, true, context).await?;
let actor = self.actor.dereference(context).await?; let actor = self.actor.dereference(context).await?;
let object = self.object.dereference(context).await?; let object = self.object.dereference(context).await?;
match object { match object {

View File

@ -134,6 +134,9 @@ impl InCommunity for AnnouncableActivities {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
activity_lists::{ activity_lists::{
GroupInboxActivities, GroupInboxActivities,

View File

@ -39,7 +39,11 @@ pub async fn list_comments(
let limit = data.limit; let limit = data.limit;
let parent_id = data.parent_id; let parent_id = data.parent_id;
let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?; let listing_type = Some(listing_type_with_default(
data.type_,
&local_site,
community_id,
)?);
// If a parent_id is given, fetch the comment to get the path // If a parent_id is given, fetch the comment to get the path
let parent_path = if let Some(parent_id) = parent_id { let parent_path = if let Some(parent_id) = parent_id {
@ -50,23 +54,22 @@ pub async fn list_comments(
let parent_path_cloned = parent_path.clone(); let parent_path_cloned = parent_path.clone();
let post_id = data.post_id; let post_id = data.post_id;
let local_user = local_user_view.map(|l| l.local_user); let comments = CommentQuery {
let comments = CommentQuery::builder() listing_type,
.pool(&mut context.pool()) sort,
.listing_type(Some(listing_type)) max_depth,
.sort(sort) saved_only,
.max_depth(max_depth) community_id,
.saved_only(saved_only) parent_path: parent_path_cloned,
.community_id(community_id) post_id,
.parent_path(parent_path_cloned) local_user: local_user_view.as_ref(),
.post_id(post_id) page,
.local_user(local_user.as_ref()) limit,
.page(page) ..Default::default()
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await
.await .with_lemmy_type(LemmyErrorType::CouldntGetComments)?;
.with_lemmy_type(LemmyErrorType::CouldntGetComments)?;
Ok(Json(GetCommentsResponse { comments })) Ok(Json(GetCommentsResponse { comments }))
} }

View File

@ -8,7 +8,7 @@ use actix_web::web::{Json, Query};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{GetPosts, GetPostsResponse}, post::{GetPosts, GetPostsResponse},
utils::{check_private_instance, is_mod_or_admin_opt, local_user_view_from_jwt_opt}, utils::{check_private_instance, local_user_view_from_jwt_opt},
}; };
use lemmy_db_schema::source::{community::Community, local_site::LocalSite}; use lemmy_db_schema::source::{community::Community, local_site::LocalSite};
use lemmy_db_views::post_view::PostQuery; use lemmy_db_views::post_view::PostQuery;
@ -36,27 +36,25 @@ pub async fn list_posts(
}; };
let saved_only = data.saved_only; let saved_only = data.saved_only;
let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?; let listing_type = Some(listing_type_with_default(
data.type_,
&local_site,
community_id,
)?);
let is_mod_or_admin = let posts = PostQuery {
is_mod_or_admin_opt(&mut context.pool(), local_user_view.as_ref(), community_id) local_user: local_user_view.as_ref(),
.await listing_type,
.is_ok(); sort,
community_id,
let posts = PostQuery::builder() saved_only,
.pool(&mut context.pool()) page,
.local_user(local_user_view.map(|l| l.local_user).as_ref()) limit,
.listing_type(Some(listing_type)) ..Default::default()
.sort(sort) }
.community_id(community_id) .list(&mut context.pool())
.saved_only(saved_only) .await
.page(page) .with_lemmy_type(LemmyErrorType::CouldntGetPosts)?;
.limit(limit)
.is_mod_or_admin(Some(is_mod_or_admin))
.build()
.list()
.await
.with_lemmy_type(LemmyErrorType::CouldntGetPosts)?;
Ok(Json(GetPostsResponse { posts })) Ok(Json(GetPostsResponse { posts }))
} }

View File

@ -4,7 +4,7 @@ use actix_web::web::{Json, Query};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{GetPersonDetails, GetPersonDetailsResponse}, person::{GetPersonDetails, GetPersonDetailsResponse},
utils::{check_private_instance, is_admin, local_user_view_from_jwt_opt}, utils::{check_private_instance, local_user_view_from_jwt_opt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{local_site::LocalSite, person::Person}, source::{local_site::LocalSite, person::Person},
@ -26,7 +26,6 @@ pub async fn read_person(
let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await;
let local_site = LocalSite::read(&mut context.pool()).await?; let local_site = LocalSite::read(&mut context.pool()).await?;
let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok());
check_private_instance(&local_user_view, &local_site)?; check_private_instance(&local_user_view, &local_site)?;
@ -53,52 +52,42 @@ pub async fn read_person(
let limit = data.limit; let limit = data.limit;
let saved_only = data.saved_only; let saved_only = data.saved_only;
let community_id = data.community_id; let community_id = data.community_id;
let local_user = local_user_view.map(|l| l.local_user); // If its saved only, you don't care what creator it was
let local_user_clone = local_user.clone(); // Or, if its not saved, then you only want it for that specific creator
let creator_id = if !saved_only.unwrap_or(false) {
Some(person_details_id)
} else {
None
};
let posts = PostQuery::builder() let posts = PostQuery {
.pool(&mut context.pool()) sort,
.sort(sort) saved_only,
.saved_only(saved_only) local_user: local_user_view.as_ref(),
.local_user(local_user.as_ref()) community_id,
.community_id(community_id) is_profile_view: Some(true),
.is_mod_or_admin(is_admin) page,
.page(page) limit,
.limit(limit) creator_id,
.creator_id( ..Default::default()
// If its saved only, you don't care what creator it was }
// Or, if its not saved, then you only want it for that specific creator .list(&mut context.pool())
if !saved_only.unwrap_or(false) { .await?;
Some(person_details_id)
} else {
None
},
)
.build()
.list()
.await?;
let comments = CommentQuery::builder() let comments = CommentQuery {
.pool(&mut context.pool()) local_user: (local_user_view.as_ref()),
.local_user(local_user_clone.as_ref()) sort: (sort.map(post_to_comment_sort_type)),
.sort(sort.map(post_to_comment_sort_type)) saved_only: (saved_only),
.saved_only(saved_only) show_deleted_and_removed: (Some(false)),
.show_deleted_and_removed(Some(false)) community_id: (community_id),
.community_id(community_id) is_profile_view: Some(true),
.page(page) page: (page),
.limit(limit) limit: (limit),
.creator_id( creator_id,
// If its saved only, you don't care what creator it was ..Default::default()
// Or, if its not saved, then you only want it for that specific creator }
if !saved_only.unwrap_or(false) { .list(&mut context.pool())
Some(person_details_id) .await?;
} else {
None
},
)
.build()
.list()
.await?;
let moderates = let moderates =
CommunityModeratorView::for_person(&mut context.pool(), person_details_id).await?; CommunityModeratorView::for_person(&mut context.pool(), person_details_id).await?;

View File

@ -50,119 +50,116 @@ pub async fn search(
data.community_id data.community_id
}; };
let creator_id = data.creator_id; let creator_id = data.creator_id;
let local_user = local_user_view.map(|l| l.local_user); let local_user = local_user_view.as_ref().map(|l| l.local_user.clone());
match search_type { match search_type {
SearchType::Posts => { SearchType::Posts => {
posts = PostQuery::builder() posts = PostQuery {
.pool(&mut context.pool()) sort: (sort),
.sort(sort) listing_type: (listing_type),
.listing_type(listing_type) community_id: (community_id),
.community_id(community_id) creator_id: (creator_id),
.creator_id(creator_id) local_user: (local_user_view.as_ref()),
.local_user(local_user.as_ref()) search_term: (Some(q)),
.search_term(Some(q)) page: (page),
.is_mod_or_admin(is_admin) limit: (limit),
.page(page) ..Default::default()
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
} }
SearchType::Comments => { SearchType::Comments => {
comments = CommentQuery::builder() comments = CommentQuery {
.pool(&mut context.pool()) sort: (sort.map(post_to_comment_sort_type)),
.sort(sort.map(post_to_comment_sort_type)) listing_type: (listing_type),
.listing_type(listing_type) search_term: (Some(q)),
.search_term(Some(q)) community_id: (community_id),
.community_id(community_id) creator_id: (creator_id),
.creator_id(creator_id) local_user: (local_user_view.as_ref()),
.local_user(local_user.as_ref()) page: (page),
.page(page) limit: (limit),
.limit(limit) ..Default::default()
.build() }
.list() .list(&mut context.pool())
.await?; .await?;
} }
SearchType::Communities => { SearchType::Communities => {
communities = CommunityQuery::builder() communities = CommunityQuery {
.pool(&mut context.pool()) sort: (sort),
.sort(sort) listing_type: (listing_type),
.listing_type(listing_type) search_term: (Some(q)),
.search_term(Some(q)) local_user: (local_user.as_ref()),
.local_user(local_user.as_ref()) is_mod_or_admin: (is_admin),
.is_mod_or_admin(is_admin) page: (page),
.page(page) limit: (limit),
.limit(limit) ..Default::default()
.build() }
.list() .list(&mut context.pool())
.await?; .await?;
} }
SearchType::Users => { SearchType::Users => {
users = PersonQuery::builder() users = PersonQuery {
.pool(&mut context.pool()) sort: (sort),
.sort(sort) search_term: (Some(q)),
.search_term(Some(q)) page: (page),
.page(page) limit: (limit),
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
} }
SearchType::All => { SearchType::All => {
// If the community or creator is included, dont search communities or users // If the community or creator is included, dont search communities or users
let community_or_creator_included = let community_or_creator_included =
data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some(); data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some();
let local_user_ = local_user.clone(); let q = data.q.clone();
posts = PostQuery::builder()
.pool(&mut context.pool()) posts = PostQuery {
.sort(sort) sort: (sort),
.listing_type(listing_type) listing_type: (listing_type),
.community_id(community_id) community_id: (community_id),
.creator_id(creator_id) creator_id: (creator_id),
.local_user(local_user_.as_ref()) local_user: (local_user_view.as_ref()),
.search_term(Some(q)) search_term: (Some(q)),
.is_mod_or_admin(is_admin) page: (page),
.page(page) limit: (limit),
.limit(limit) ..Default::default()
.build() }
.list() .list(&mut context.pool())
.await?; .await?;
let q = data.q.clone(); let q = data.q.clone();
let local_user_ = local_user.clone(); comments = CommentQuery {
comments = CommentQuery::builder() sort: (sort.map(post_to_comment_sort_type)),
.pool(&mut context.pool()) listing_type: (listing_type),
.sort(sort.map(post_to_comment_sort_type)) search_term: (Some(q)),
.listing_type(listing_type) community_id: (community_id),
.search_term(Some(q)) creator_id: (creator_id),
.community_id(community_id) local_user: (local_user_view.as_ref()),
.creator_id(creator_id) page: (page),
.local_user(local_user_.as_ref()) limit: (limit),
.page(page) ..Default::default()
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
let q = data.q.clone(); let q = data.q.clone();
communities = if community_or_creator_included { communities = if community_or_creator_included {
vec![] vec![]
} else { } else {
CommunityQuery::builder() CommunityQuery {
.pool(&mut context.pool()) sort: (sort),
.sort(sort) listing_type: (listing_type),
.listing_type(listing_type) search_term: (Some(q)),
.search_term(Some(q)) local_user: (local_user.as_ref()),
.local_user(local_user.as_ref()) is_mod_or_admin: (is_admin),
.is_mod_or_admin(is_admin) page: (page),
.page(page) limit: (limit),
.limit(limit) ..Default::default()
.build() }
.list() .list(&mut context.pool())
.await? .await?
}; };
let q = data.q.clone(); let q = data.q.clone();
@ -170,31 +167,29 @@ pub async fn search(
users = if community_or_creator_included { users = if community_or_creator_included {
vec![] vec![]
} else { } else {
PersonQuery::builder() PersonQuery {
.pool(&mut context.pool()) sort: (sort),
.sort(sort) search_term: (Some(q)),
.search_term(Some(q)) page: (page),
.page(page) limit: (limit),
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?
.await?
}; };
} }
SearchType::Url => { SearchType::Url => {
posts = PostQuery::builder() posts = PostQuery {
.pool(&mut context.pool()) sort: (sort),
.sort(sort) listing_type: (listing_type),
.listing_type(listing_type) community_id: (community_id),
.community_id(community_id) creator_id: (creator_id),
.creator_id(creator_id) url_search: (Some(q)),
.url_search(Some(q)) page: (page),
.is_mod_or_admin(is_admin) limit: (limit),
.page(page) ..Default::default()
.limit(limit) }
.build() .list(&mut context.pool())
.list() .await?;
.await?;
} }
}; };

View File

@ -78,18 +78,20 @@ impl Collection for ApubCommunityModerators {
// Add new mods to database which have been added to moderators collection // Add new mods to database which have been added to moderators collection
for mod_id in apub.ordered_items { for mod_id in apub.ordered_items {
let mod_user: ApubPerson = mod_id.dereference(data).await?; // Ignore errors as mod accounts might be deleted or instances unavailable.
let mod_user: Option<ApubPerson> = mod_id.dereference(data).await.ok();
if !current_moderators if let Some(mod_user) = mod_user {
.iter() if !current_moderators
.map(|c| c.moderator.actor_id.clone()) .iter()
.any(|x| x == mod_user.actor_id) .map(|c| c.moderator.actor_id.clone())
{ .any(|x| x == mod_user.actor_id)
let community_moderator_form = CommunityModeratorForm { {
community_id: owner.id, let community_moderator_form = CommunityModeratorForm {
person_id: mod_user.id, community_id: owner.id,
}; person_id: mod_user.id,
CommunityModerator::join(&mut data.pool(), &community_moderator_form).await?; };
CommunityModerator::join(&mut data.pool(), &community_moderator_form).await?;
}
} }
} }
@ -100,6 +102,9 @@ impl Collection for ApubCommunityModerators {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::{
objects::{ objects::{

View File

@ -13,7 +13,7 @@ use activitypub_federation::{
use actix_web::{web, web::Bytes, HttpRequest, HttpResponse}; use actix_web::{web, web::Bytes, HttpRequest, HttpResponse};
use http::StatusCode; use http::StatusCode;
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::source::activity::Activity; use lemmy_db_schema::source::activity::SentActivity;
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Deref; use std::ops::Deref;
@ -88,12 +88,10 @@ pub(crate) async fn get_activity(
info.id info.id
))? ))?
.into(); .into();
let activity = Activity::read_from_apub_id(&mut context.pool(), &activity_id).await?; let activity = SentActivity::read_from_apub_id(&mut context.pool(), &activity_id).await?;
let sensitive = activity.sensitive; let sensitive = activity.sensitive;
if !activity.local { if sensitive {
Err(err_object_not_local())
} else if sensitive {
Ok(HttpResponse::Forbidden().finish()) Ok(HttpResponse::Forbidden().finish())
} else { } else {
create_apub_response(&activity.data) create_apub_response(&activity.data)

View File

@ -3,18 +3,12 @@ use activitypub_federation::config::{Data, UrlVerifier};
use async_trait::async_trait; use async_trait::async_trait;
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{activity::ReceivedActivity, instance::Instance, local_site::LocalSite},
activity::{Activity, ActivityInsertForm},
instance::Instance,
local_site::LocalSite,
},
traits::Crud,
utils::{ActualDbPool, DbPool}, utils::{ActualDbPool, DbPool},
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult};
use moka::future::Cache; use moka::future::Cache;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde::Serialize;
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use url::Url; use url::Url;
@ -178,30 +172,16 @@ pub(crate) async fn check_apub_id_valid_with_strictness(
Ok(()) Ok(())
} }
/// Store a sent or received activity in the database. /// Store received activities in the database.
/// ///
/// Stored activities are served over the HTTP endpoint `GET /activities/{type_}/{id}`. This also /// This ensures that the same activity doesnt get received and processed more than once, which
/// ensures that the same activity cannot be received more than once. /// would be a waste of resources.
#[tracing::instrument(skip(data, activity))] #[tracing::instrument(skip(data))]
async fn insert_activity<T>( async fn insert_received_activity(
ap_id: &Url, ap_id: &Url,
activity: &T,
local: bool,
sensitive: bool,
data: &Data<LemmyContext>, data: &Data<LemmyContext>,
) -> Result<(), LemmyError> ) -> Result<(), LemmyError> {
where ReceivedActivity::create(&mut data.pool(), &ap_id.clone().into()).await?;
T: Serialize,
{
let ap_id = ap_id.clone().into();
let form = ActivityInsertForm {
ap_id,
data: serde_json::to_value(activity)?,
local: Some(local),
sensitive: Some(sensitive),
updated: None,
};
Activity::create(&mut data.pool(), &form).await?;
Ok(()) Ok(())
} }

View File

@ -179,6 +179,9 @@ impl Object for ApubComment {
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::{
objects::{ objects::{

View File

@ -204,6 +204,9 @@ impl ApubCommunity {
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::{
objects::{instance::tests::parse_lemmy_instance, tests::init_context}, objects::{instance::tests::parse_lemmy_instance, tests::init_context},

View File

@ -206,6 +206,9 @@ pub(crate) async fn remote_instance_inboxes(pool: &mut DbPool<'_>) -> Result<Vec
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{objects::tests::init_context, protocol::tests::file_to_json_object}; use crate::{objects::tests::init_context, protocol::tests::file_to_json_object};
use lemmy_db_schema::traits::Crud; use lemmy_db_schema::traits::Crud;

View File

@ -54,6 +54,9 @@ pub(crate) fn verify_is_remote_object(id: &Url, settings: &Settings) -> Result<(
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use activitypub_federation::config::{Data, FederationConfig}; use activitypub_federation::config::{Data, FederationConfig};
use anyhow::anyhow; use anyhow::anyhow;
use lemmy_api_common::{context::LemmyContext, request::build_user_agent}; use lemmy_api_common::{context::LemmyContext, request::build_user_agent};

View File

@ -195,6 +195,9 @@ impl Actor for ApubPerson {
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::{
objects::{ objects::{

View File

@ -280,6 +280,9 @@ impl Object for ApubPost {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::{
objects::{ objects::{

View File

@ -136,6 +136,9 @@ impl Object for ApubPrivateMessage {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::{
objects::{ objects::{

View File

@ -3,6 +3,9 @@ pub mod undo_block_user;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser}, activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
tests::test_parse_lemmy_item, tests::test_parse_lemmy_item,

View File

@ -7,6 +7,9 @@ pub mod update;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
activities::community::{ activities::community::{
announce::AnnounceActivity, announce::AnnounceActivity,

View File

@ -4,6 +4,9 @@ pub mod page;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
activities::create_or_update::{ activities::create_or_update::{
chat_message::CreateOrUpdateChatMessage, chat_message::CreateOrUpdateChatMessage,

View File

@ -4,6 +4,9 @@ pub mod undo_delete;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
activities::deletion::{delete::Delete, delete_user::DeleteUser, undo_delete::UndoDelete}, activities::deletion::{delete::Delete, delete_user::DeleteUser, undo_delete::UndoDelete},
tests::test_parse_lemmy_item, tests::test_parse_lemmy_item,

View File

@ -4,6 +4,9 @@ pub mod undo_follow;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
activities::following::{accept::AcceptFollow, follow::Follow, undo_follow::UndoFollow}, activities::following::{accept::AcceptFollow, follow::Follow, undo_follow::UndoFollow},
tests::test_parse_lemmy_item, tests::test_parse_lemmy_item,

View File

@ -16,6 +16,9 @@ pub enum CreateOrUpdateType {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
activities::{ activities::{
community::announce::AnnounceActivity, community::announce::AnnounceActivity,

View File

@ -3,6 +3,9 @@ pub mod vote;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
activities::voting::{undo_vote::UndoVote, vote::Vote}, activities::voting::{undo_vote::UndoVote, vote::Vote},
tests::test_parse_lemmy_item, tests::test_parse_lemmy_item,

View File

@ -6,6 +6,9 @@ pub(crate) mod group_outbox;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
collections::{ collections::{
empty_outbox::EmptyOutbox, empty_outbox::EmptyOutbox,

View File

@ -89,6 +89,9 @@ pub trait InCommunity {
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use activitypub_federation::protocol::context::WithContext; use activitypub_federation::protocol::context::WithContext;
use assert_json_diff::assert_json_include; use assert_json_diff::assert_json_include;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;

View File

@ -95,6 +95,9 @@ impl LanguageTag {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{ use crate::protocol::{
objects::{ objects::{
chat_message::ChatMessage, chat_message::ChatMessage,

View File

@ -242,6 +242,9 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::protocol::{objects::page::Page, tests::test_parse_lemmy_item}; use crate::protocol::{objects::page::Page, tests::test_parse_lemmy_item};
#[test] #[test]

View File

@ -14,9 +14,27 @@ path = "src/lib.rs"
doctest = false doctest = false
[features] [features]
full = ["diesel", "diesel-derive-newtype", "diesel-derive-enum", "diesel_migrations", "bcrypt", "lemmy_utils", full = [
"activitypub_federation", "sha2", "regex", "once_cell", "serde_json", "diesel_ltree", "diesel",
"diesel-async", "deadpool", "ts-rs"] "diesel-derive-newtype",
"diesel-derive-enum",
"diesel_migrations",
"bcrypt",
"lemmy_utils",
"activitypub_federation",
"sha2",
"regex",
"once_cell",
"serde_json",
"diesel_ltree",
"diesel-async",
"deadpool",
"ts-rs",
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
"rustls",
]
[dependencies] [dependencies]
chrono = { workspace = true } chrono = { workspace = true }
@ -29,25 +47,33 @@ serde_json = { workspace = true, optional = true }
activitypub_federation = { workspace = true, optional = true } activitypub_federation = { workspace = true, optional = true }
lemmy_utils = { workspace = true, optional = true } lemmy_utils = { workspace = true, optional = true }
bcrypt = { workspace = true, optional = true } bcrypt = { workspace = true, optional = true }
diesel = { workspace = true, features = ["postgres","chrono", "serde_json", "uuid"], optional = true } diesel = { workspace = true, features = [
"postgres",
"chrono",
"serde_json",
"uuid",
], optional = true }
diesel-derive-newtype = { workspace = true, optional = true } diesel-derive-newtype = { workspace = true, optional = true }
diesel-derive-enum = { workspace = true, optional = true } diesel-derive-enum = { workspace = true, optional = true }
diesel_migrations = { workspace = true, optional = true } diesel_migrations = { workspace = true, optional = true }
diesel-async = { workspace = true, features = ["postgres", "deadpool"], optional = true } diesel-async = { workspace = true, features = [
"postgres",
"deadpool",
], optional = true }
sha2 = { workspace = true, optional = true } sha2 = { workspace = true, optional = true }
regex = { workspace = true, optional = true } regex = { workspace = true, optional = true }
once_cell = { workspace = true, optional = true } once_cell = { workspace = true, optional = true }
diesel_ltree = { workspace = true, optional = true } diesel_ltree = { workspace = true, optional = true }
typed-builder = { workspace = true } typed-builder = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
deadpool = { version = "0.9.5", features = ["rt_tokio_1"], optional = true } deadpool = { version = "0.9.5", features = ["rt_tokio_1"], optional = true }
ts-rs = { workspace = true, optional = true } ts-rs = { workspace = true, optional = true }
rustls = { workspace = true }
futures-util = { workspace = true } futures-util = { workspace = true }
tokio-postgres = { workspace = true } tokio = { workspace = true, optional = true }
tokio-postgres-rustls = { workspace = true } tokio-postgres = { workspace = true, optional = true }
tokio-postgres-rustls = { workspace = true, optional = true }
rustls = { workspace = true, optional = true }
uuid = { workspace = true, features = ["v4"] } uuid = { workspace = true, features = ["v4"] }
[dev-dependencies] [dev-dependencies]

View File

@ -35,6 +35,9 @@ impl CommentAggregates {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
aggregates::comment_aggregates::CommentAggregates, aggregates::comment_aggregates::CommentAggregates,
source::{ source::{

View File

@ -19,6 +19,9 @@ impl CommunityAggregates {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
aggregates::community_aggregates::CommunityAggregates, aggregates::community_aggregates::CommunityAggregates,
source::{ source::{

View File

@ -19,6 +19,9 @@ impl PersonAggregates {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
aggregates::person_aggregates::PersonAggregates, aggregates::person_aggregates::PersonAggregates,
source::{ source::{

View File

@ -35,6 +35,9 @@ impl PostAggregates {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
aggregates::post_aggregates::PostAggregates, aggregates::post_aggregates::PostAggregates,
source::{ source::{

View File

@ -15,6 +15,9 @@ impl SiteAggregates {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
aggregates::site_aggregates::SiteAggregates, aggregates::site_aggregates::SiteAggregates,
source::{ source::{

View File

@ -96,6 +96,8 @@ pub struct PostAggregates {
pub featured_local: bool, pub featured_local: bool,
pub hot_rank: i32, pub hot_rank: i32,
pub hot_rank_active: i32, pub hot_rank_active: i32,
pub community_id: CommunityId,
pub creator_id: PersonId,
} }
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]

View File

@ -1,139 +1,115 @@
use crate::{ use crate::{
diesel::OptionalExtension,
newtypes::DbUrl, newtypes::DbUrl,
schema::activity::dsl::{activity, ap_id}, source::activity::{ReceivedActivity, SentActivity, SentActivityForm},
source::activity::{Activity, ActivityInsertForm, ActivityUpdateForm},
traits::Crud,
utils::{get_conn, DbPool}, utils::{get_conn, DbPool},
}; };
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel::{
dsl::insert_into,
result::{DatabaseErrorKind, Error, Error::DatabaseError},
ExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
#[async_trait] #[async_trait]
impl Crud for Activity { impl Crud for Activity {
type InsertForm = ActivityInsertForm; pub async fn create(pool: &mut DbPool<'_>, form: SentActivityForm) -> Result<Self, Error> {
type UpdateForm = ActivityUpdateForm; use crate::schema::sent_activity::dsl::sent_activity;
type IdType = i32;
async fn create(pool: &mut DbPool<'_>, new_activity: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(activity) insert_into(sent_activity)
.values(new_activity) .values(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn update( pub async fn read_from_apub_id(pool: &mut DbPool<'_>, object_id: &DbUrl) -> Result<Self, Error> {
pool: &mut DbPool<'_>, use crate::schema::sent_activity::dsl::{ap_id, sent_activity};
activity_id: i32,
new_activity: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::update(activity.find(activity_id)) sent_activity
.set(new_activity)
.get_result::<Self>(conn)
.await
}
async fn delete(pool: &mut DbPool<'_>, activity_id: i32) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(activity.find(activity_id))
.execute(conn)
.await
}
}
impl Activity {
pub async fn read_from_apub_id(
pool: &mut DbPool<'_>,
object_id: &DbUrl,
) -> Result<Activity, Error> {
let conn = &mut get_conn(pool).await?;
activity
.filter(ap_id.eq(object_id)) .filter(ap_id.eq(object_id))
.first::<Self>(conn) .first::<Self>(conn)
.await .await
} }
} }
impl ReceivedActivity {
pub async fn create(pool: &mut DbPool<'_>, ap_id_: &DbUrl) -> Result<(), Error> {
use crate::schema::received_activity::dsl::{ap_id, id, received_activity};
let conn = &mut get_conn(pool).await?;
let res = insert_into(received_activity)
.values(ap_id.eq(ap_id_))
.on_conflict_do_nothing()
.returning(id)
.get_result::<i64>(conn)
.await
.optional()?;
if res.is_some() {
// new activity inserted successfully
Ok(())
} else {
// duplicate activity
Err(DatabaseError(
DatabaseErrorKind::UniqueViolation,
Box::<String>::default(),
))
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::utils::build_db_pool_for_tests;
newtypes::DbUrl, use serde_json::json;
source::{
activity::{Activity, ActivityInsertForm},
instance::Instance,
person::{Person, PersonInsertForm},
},
utils::build_db_pool_for_tests,
};
use serde_json::Value;
use serial_test::serial; use serial_test::serial;
use url::Url; use url::Url;
#[tokio::test] #[tokio::test]
#[serial] #[serial]
async fn test_crud() { async fn receive_activity_duplicate() {
let pool = &build_db_pool_for_tests().await; let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into(); let pool = &mut pool.into();
let ap_id: DbUrl = Url::parse("http://example.com/activity/531")
.unwrap()
.into();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()) // inserting activity for first time
.await let res = ReceivedActivity::create(pool, &ap_id).await;
.unwrap(); assert!(res.is_ok());
let creator_form = PersonInsertForm::builder() let res = ReceivedActivity::create(pool, &ap_id).await;
.name("activity_creator_ pm".into()) assert!(res.is_err());
.public_key("pubkey".to_string()) }
.instance_id(inserted_instance.id)
.build();
let inserted_creator = Person::create(pool, &creator_form).await.unwrap(); #[tokio::test]
#[serial]
async fn sent_activity_write_read() {
let pool = &build_db_pool_for_tests().await;
let pool = &mut pool.into();
let ap_id: DbUrl = Url::parse("http://example.com/activity/412")
.unwrap()
.into();
let data = json!({
"key1": "0xF9BA143B95FF6D82",
"key2": "42",
});
let sensitive = false;
let ap_id_: DbUrl = Url::parse( let form = SentActivityForm {
"https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c", ap_id: ap_id.clone(),
) data: data.clone(),
.unwrap() sensitive,
.into();
let test_json: Value = serde_json::from_str(
r#"{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c",
"type": "Delete",
"actor": "https://enterprise.lemmy.ml/u/riker",
"to": "https://www.w3.org/ns/activitystreams#Public",
"cc": [
"https://enterprise.lemmy.ml/c/main/"
],
"object": "https://enterprise.lemmy.ml/post/32"
}"#,
)
.unwrap();
let activity_form = ActivityInsertForm {
ap_id: ap_id_.clone(),
data: test_json.clone(),
local: Some(true),
sensitive: Some(false),
updated: None,
}; };
let inserted_activity = Activity::create(pool, &activity_form).await.unwrap(); SentActivity::create(pool, form).await.unwrap();
let expected_activity = Activity { let res = SentActivity::read_from_apub_id(pool, &ap_id).await.unwrap();
ap_id: ap_id_.clone(), assert_eq!(res.ap_id, ap_id);
id: inserted_activity.id, assert_eq!(res.data, data);
data: test_json, assert_eq!(res.sensitive, sensitive);
local: true,
sensitive: false,
published: inserted_activity.published,
updated: None,
};
let read_activity = Activity::read(pool, inserted_activity.id).await.unwrap();
let read_activity_by_apub_id = Activity::read_from_apub_id(pool, &ap_id_).await.unwrap();
Person::delete(pool, inserted_creator.id).await.unwrap();
Activity::delete(pool, inserted_activity.id).await.unwrap();
assert_eq!(expected_activity, read_activity);
assert_eq!(expected_activity, read_activity_by_apub_id);
assert_eq!(expected_activity, inserted_activity);
} }
} }

View File

@ -275,25 +275,37 @@ impl CommunityLanguage {
return Ok(()); return Ok(());
} }
let form = lang_ids
.into_iter()
.map(|language_id| CommunityLanguageForm {
community_id: for_community_id,
language_id,
})
.collect::<Vec<_>>();
conn conn
.build_transaction() .build_transaction()
.run(|conn| { .run(|conn| {
Box::pin(async move { Box::pin(async move {
use crate::schema::community_language::dsl::{community_id, community_language}; use crate::schema::community_language::dsl::{community_id, community_language};
use diesel::result::DatabaseErrorKind::UniqueViolation;
// Clear the current languages // Clear the current languages
delete(community_language.filter(community_id.eq(for_community_id))) delete(community_language.filter(community_id.eq(for_community_id)))
.execute(conn) .execute(conn)
.await?; .await?;
for l in lang_ids { let insert_res = insert_into(community_language)
let form = CommunityLanguageForm { .values(form)
community_id: for_community_id, .get_result::<Self>(conn)
language_id: l, .await;
};
insert_into(community_language) if let Err(Error::DatabaseError(UniqueViolation, _info)) = insert_res {
.values(form) // race condition: this function was probably called simultaneously from another caller. ignore error
.get_result::<Self>(conn) // tracing::warn!("unique error: {_info:#?}");
.await?; // _info.constraint_name() should be = "community_language_community_id_language_id_key"
return Ok(());
} else {
insert_res?;
} }
Ok(()) Ok(())
}) as _ }) as _
@ -372,6 +384,9 @@ async fn convert_read_languages(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*; use super::*;
use crate::{ use crate::{
impls::actor_language::{ impls::actor_language::{

View File

@ -50,6 +50,9 @@ impl CaptchaAnswer {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
source::captcha_answer::{CaptchaAnswer, CaptchaAnswerForm, CheckCaptchaAnswer}, source::captcha_answer::{CaptchaAnswer, CaptchaAnswerForm, CheckCaptchaAnswer},
utils::build_db_pool_for_tests, utils::build_db_pool_for_tests,

View File

@ -247,6 +247,9 @@ impl Saveable for CommentSaved {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
newtypes::LanguageId, newtypes::LanguageId,
source::{ source::{

View File

@ -74,6 +74,9 @@ impl CommentReply {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{ use crate::{
source::{ source::{
comment::{Comment, CommentInsertForm}, comment::{Comment, CommentInsertForm},

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