mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-26 07:52:13 +00:00
Merge remote-tracking branch 'upstream/main' into defaults
This commit is contained in:
commit
374b0f6f9e
|
@ -1,5 +1,5 @@
|
|||
tab_spaces = 2
|
||||
edition="2021"
|
||||
imports_layout="HorizontalVertical"
|
||||
imports_granularity="Crate"
|
||||
group_imports="One"
|
||||
edition = "2021"
|
||||
imports_layout = "HorizontalVertical"
|
||||
imports_granularity = "Crate"
|
||||
group_imports = "One"
|
||||
|
|
147
.woodpecker.yml
147
.woodpecker.yml
|
@ -18,7 +18,6 @@ pipeline:
|
|||
image: alpine:3
|
||||
commands:
|
||||
- apk add git
|
||||
#- git fetch --tags
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
|
||||
|
@ -27,7 +26,34 @@ pipeline:
|
|||
commands:
|
||||
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations'
|
||||
|
||||
# use minimum supported rust version for most steps
|
||||
restore-cache:
|
||||
image: meltwater/drone-cache:v1
|
||||
pull: true
|
||||
settings:
|
||||
restore: true
|
||||
endpoint:
|
||||
from_secret: MINIO_ENDPOINT
|
||||
access-key:
|
||||
from_secret: MINIO_WRITE_USER
|
||||
secret-key:
|
||||
from_secret: MINIO_WRITE_PASSWORD
|
||||
bucket:
|
||||
from_secret: MINIO_BUCKET
|
||||
region: us-east-1
|
||||
cache_key: "rust-cache"
|
||||
path-style: true
|
||||
mount:
|
||||
- ".cargo"
|
||||
- "target"
|
||||
- "api_tests/node_modules"
|
||||
secrets:
|
||||
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
|
||||
|
||||
toml_fmt:
|
||||
image: tamasfe/taplo:0.8.1
|
||||
commands:
|
||||
- taplo format --check
|
||||
|
||||
cargo_fmt:
|
||||
image: *muslrust_image
|
||||
environment:
|
||||
|
@ -35,42 +61,15 @@ pipeline:
|
|||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
# need make existing toolchain available
|
||||
- cp ~/.cargo . -r
|
||||
- rustup toolchain install nightly
|
||||
- rustup component add rustfmt --toolchain nightly
|
||||
- cargo +nightly fmt -- --check
|
||||
# when:
|
||||
# platform: linux/amd64
|
||||
|
||||
cargo_clippy:
|
||||
image: *muslrust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
# latest rust for clippy to get extra checks
|
||||
# when adding new clippy lints, make sure to also add them in scripts/fix-clippy.sh
|
||||
- rustup component add clippy
|
||||
- cargo clippy --workspace --tests --all-targets --features console --
|
||||
-D warnings -D deprecated -D clippy::perf -D clippy::complexity
|
||||
-D clippy::style -D clippy::correctness -D clippy::suspicious
|
||||
-D clippy::dbg_macro -D clippy::inefficient_to_string
|
||||
-D clippy::items-after-statements -D clippy::implicit_clone
|
||||
-D clippy::cast_lossless -D clippy::manual_string_new
|
||||
-D clippy::redundant_closure_for_method_calls
|
||||
-D clippy::unused_self
|
||||
-A clippy::uninlined_format_args
|
||||
-D clippy::get_first
|
||||
-D clippy::explicit_into_iter_loop
|
||||
-D clippy::explicit_iter_loop
|
||||
-D clippy::needless_collect
|
||||
- cargo clippy --workspace --features console --
|
||||
-D clippy::unwrap_used
|
||||
-D clippy::indexing_slicing
|
||||
- cp -n ~/.cargo . -r
|
||||
- rustup toolchain install nightly-2023-07-10
|
||||
- rustup component add rustfmt --toolchain nightly-2023-07-10
|
||||
- cargo +nightly-2023-07-10 fmt -- --check
|
||||
# when:
|
||||
# platform: linux/amd64
|
||||
|
||||
# make sure api builds with default features (used by other crates relying on lemmy api)
|
||||
cargo_check:
|
||||
check_api_common_default_features:
|
||||
image: *muslrust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
|
@ -88,6 +87,14 @@ pipeline:
|
|||
# when:
|
||||
# platform: linux/amd64
|
||||
|
||||
lemmy_api_common_works_with_wasm:
|
||||
image: *muslrust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- "rustup target add wasm32-unknown-unknown"
|
||||
- "cargo check --target wasm32-unknown-unknown -p lemmy_api_common"
|
||||
|
||||
check_defaults_hjson_updated:
|
||||
image: *muslrust_image
|
||||
environment:
|
||||
|
@ -109,12 +116,45 @@ pipeline:
|
|||
- diesel print-schema --config-file=diesel.toml > tmp.schema
|
||||
- diff tmp.schema crates/db_schema/src/schema.rs
|
||||
|
||||
check_diesel_migration_revertable:
|
||||
image: willsquire/diesel-cli
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
commands:
|
||||
- diesel migration run
|
||||
- diesel migration redo
|
||||
|
||||
cargo_clippy:
|
||||
image: *muslrust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
# when adding new clippy lints, make sure to also add them in scripts/fix-clippy.sh
|
||||
- rustup component add clippy
|
||||
- cargo clippy --workspace --tests --all-targets --features console --
|
||||
-D warnings -D deprecated -D clippy::perf -D clippy::complexity
|
||||
-D clippy::style -D clippy::correctness -D clippy::suspicious
|
||||
-D clippy::dbg_macro -D clippy::inefficient_to_string
|
||||
-D clippy::items-after-statements -D clippy::implicit_clone
|
||||
-D clippy::cast_lossless -D clippy::manual_string_new
|
||||
-D clippy::redundant_closure_for_method_calls
|
||||
-D clippy::unused_self
|
||||
-A clippy::uninlined_format_args
|
||||
-D clippy::get_first
|
||||
-D clippy::explicit_into_iter_loop
|
||||
-D clippy::explicit_iter_loop
|
||||
-D clippy::needless_collect
|
||||
-D clippy::unwrap_used
|
||||
-D clippy::indexing_slicing
|
||||
# when:
|
||||
# platform: linux/amd64
|
||||
|
||||
cargo_test:
|
||||
image: *muslrust_image
|
||||
environment:
|
||||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
RUST_BACKTRACE: "1"
|
||||
RUST_TEST_THREADS: "1"
|
||||
CARGO_HOME: .cargo
|
||||
commands:
|
||||
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
||||
|
@ -146,6 +186,29 @@ pipeline:
|
|||
# when:
|
||||
# platform: linux/amd64
|
||||
|
||||
rebuild-cache:
|
||||
image: meltwater/drone-cache:v1
|
||||
pull: true
|
||||
settings:
|
||||
rebuild: true
|
||||
endpoint:
|
||||
from_secret: MINIO_ENDPOINT
|
||||
access-key:
|
||||
from_secret: MINIO_WRITE_USER
|
||||
secret-key:
|
||||
from_secret: MINIO_WRITE_PASSWORD
|
||||
bucket:
|
||||
from_secret: MINIO_BUCKET
|
||||
cache_key: "rust-cache"
|
||||
region: us-east-1
|
||||
path-style: true
|
||||
mount:
|
||||
- ".cargo"
|
||||
- "target"
|
||||
- "api_tests/node_modules"
|
||||
secrets:
|
||||
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
|
||||
|
||||
publish_release_docker:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
secrets: [docker_username, docker_password]
|
||||
|
@ -172,20 +235,6 @@ pipeline:
|
|||
when:
|
||||
event: cron
|
||||
|
||||
# using https://github.com/pksunkara/cargo-workspaces
|
||||
publish_to_crates_io:
|
||||
image: *muslrust_image
|
||||
commands:
|
||||
- 'echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"'
|
||||
- cargo install cargo-workspaces
|
||||
- cp -r migrations crates/db_schema/
|
||||
- cargo login "$CARGO_API_TOKEN"
|
||||
- cargo workspaces publish --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
|
||||
secrets: [cargo_api_token]
|
||||
when:
|
||||
event: tag
|
||||
#platform: linux/amd64
|
||||
|
||||
notify_on_failure:
|
||||
image: alpine:3
|
||||
commands:
|
||||
|
|
56
Cargo.lock
generated
56
Cargo.lock
generated
|
@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
|||
|
||||
[[package]]
|
||||
name = "activitypub_federation"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ab3ac148d9c0b4163a6d41040c17de7558a42224b9ecbd4e8f033aef6c254d9"
|
||||
checksum = "4e6e7fefba6602240fcf612931b70640ad1e249dff833551ebc218f1c96a4193"
|
||||
dependencies = [
|
||||
"activitystreams-kinds",
|
||||
"actix-web",
|
||||
|
@ -22,7 +22,6 @@ dependencies = [
|
|||
"bytes",
|
||||
"chrono",
|
||||
"derive_builder",
|
||||
"displaydoc",
|
||||
"dyn-clone",
|
||||
"enum_delegate",
|
||||
"futures-core",
|
||||
|
@ -346,7 +345,7 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.10",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -358,7 +357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.10",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -678,7 +677,7 @@ checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3"
|
|||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"blowfish",
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.10",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -1548,17 +1547,6 @@ dependencies = [
|
|||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.3.0"
|
||||
|
@ -2045,9 +2033,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.8"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
|
@ -2397,17 +2385,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
|
@ -2644,16 +2621,19 @@ dependencies = [
|
|||
name = "lemmy_api_common"
|
||||
version = "0.18.1"
|
||||
dependencies = [
|
||||
"activitypub_federation",
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"encoding",
|
||||
"futures",
|
||||
"getrandom 0.2.10",
|
||||
"lemmy_db_schema",
|
||||
"lemmy_db_views",
|
||||
"lemmy_db_views_actor",
|
||||
"lemmy_db_views_moderator",
|
||||
"lemmy_utils",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
@ -2779,7 +2759,6 @@ dependencies = [
|
|||
"tokio",
|
||||
"tracing",
|
||||
"ts-rs",
|
||||
"typed-builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2792,7 +2771,6 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_with",
|
||||
"ts-rs",
|
||||
"typed-builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3162,12 +3140,6 @@ dependencies = [
|
|||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.5.0"
|
||||
|
@ -3195,7 +3167,7 @@ version = "0.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5736ba45bbac8f7ccc99a897f88ce85e508a18baec973a040f2514e6cdbff0d2"
|
||||
dependencies = [
|
||||
"idna 0.2.3",
|
||||
"idna 0.3.0",
|
||||
"once_cell",
|
||||
"regex",
|
||||
]
|
||||
|
@ -4271,7 +4243,7 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4437,7 +4409,7 @@ checksum = "1b97ad83c2fc18113346b7158d79732242002427c30f620fa817c1f32901e0a8"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.10",
|
||||
"matchit 0.7.0",
|
||||
"opentelemetry 0.16.0",
|
||||
"reqwest",
|
||||
|
@ -5915,7 +5887,7 @@ version = "1.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"getrandom 0.2.10",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
53
Cargo.toml
53
Cargo.toml
|
@ -24,25 +24,36 @@ doctest = false
|
|||
debug = 0
|
||||
lto = "thin"
|
||||
|
||||
# This profile significantly speeds up build time. If debug info is needed you can comment the line
|
||||
# out temporarily, but make sure to leave this in the main branch.
|
||||
[profile.dev]
|
||||
debug = 0
|
||||
|
||||
[features]
|
||||
embed-pictrs = ["pict-rs"]
|
||||
console = ["console-subscriber", "opentelemetry", "opentelemetry-otlp", "tracing-opentelemetry", "reqwest-tracing/opentelemetry_0_16"]
|
||||
console = [
|
||||
"console-subscriber",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"tracing-opentelemetry",
|
||||
"reqwest-tracing/opentelemetry_0_16",
|
||||
]
|
||||
json-log = ["tracing-subscriber/json"]
|
||||
prometheus-metrics = ["prometheus", "actix-web-prom"]
|
||||
default = []
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/api",
|
||||
"crates/api_crud",
|
||||
"crates/api_common",
|
||||
"crates/apub",
|
||||
"crates/utils",
|
||||
"crates/db_schema",
|
||||
"crates/db_views",
|
||||
"crates/db_views_actor",
|
||||
"crates/db_views_actor",
|
||||
"crates/routes"
|
||||
"crates/api",
|
||||
"crates/api_crud",
|
||||
"crates/api_common",
|
||||
"crates/apub",
|
||||
"crates/utils",
|
||||
"crates/db_schema",
|
||||
"crates/db_views",
|
||||
"crates/db_views_actor",
|
||||
"crates/db_views_actor",
|
||||
"crates/routes",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
@ -56,13 +67,21 @@ lemmy_routes = { version = "=0.18.1", path = "./crates/routes" }
|
|||
lemmy_db_views = { version = "=0.18.1", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.18.1", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.18.1", path = "./crates/db_views_moderator" }
|
||||
activitypub_federation = { version = "0.4.5", default-features = false, features = ["actix-web"] }
|
||||
activitypub_federation = { version = "0.4.6", default-features = false, features = [
|
||||
"actix-web",
|
||||
] }
|
||||
diesel = "2.1.0"
|
||||
diesel_migrations = "2.1.0"
|
||||
diesel-async = "0.3.1"
|
||||
serde = { version = "1.0.167", features = ["derive"] }
|
||||
serde_with = "3.0.0"
|
||||
actix-web = { version = "4.3.1", default-features = false, features = ["macros", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] }
|
||||
actix-web = { version = "4.3.1", default-features = false, features = [
|
||||
"macros",
|
||||
"rustls",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
] }
|
||||
tracing = "0.1.37"
|
||||
tracing-actix-web = { version = "0.7.5", default-features = false }
|
||||
tracing-error = "0.2.0"
|
||||
|
@ -82,7 +101,9 @@ base64 = "0.21.2"
|
|||
uuid = { version = "1.4.0", features = ["serde", "v4"] }
|
||||
async-trait = "0.1.71"
|
||||
captcha = "0.0.9"
|
||||
anyhow = { version = "1.0.71", features = ["backtrace"] } # backtrace is on by default on nightly, but not stable rust
|
||||
anyhow = { version = "1.0.71", features = [
|
||||
"backtrace",
|
||||
] } # backtrace is on by default on nightly, but not stable rust
|
||||
diesel_ltree = "0.3.0"
|
||||
typed-builder = "0.15.0"
|
||||
serial_test = "2.0.0"
|
||||
|
@ -91,7 +112,7 @@ sha2 = "0.10.7"
|
|||
regex = "1.9.0"
|
||||
once_cell = "1.18.0"
|
||||
diesel-derive-newtype = "2.1.0"
|
||||
diesel-derive-enum = {version = "2.1.0", features = ["postgres"] }
|
||||
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||
strum = "0.25.0"
|
||||
strum_macros = "0.25.1"
|
||||
itertools = "0.11.0"
|
||||
|
@ -103,7 +124,7 @@ rand = "0.8.5"
|
|||
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
|
||||
tracing-opentelemetry = { version = "0.19.0" }
|
||||
ts-rs = { version = "6.2", features = ["serde-compat", "chrono-impl"] }
|
||||
rustls = { version ="0.21.3", features = ["dangerous_configuration"]}
|
||||
rustls = { version = "0.21.3", features = ["dangerous_configuration"] }
|
||||
futures-util = "0.3.28"
|
||||
tokio-postgres = "0.7.8"
|
||||
tokio-postgres-rustls = "0.10.0"
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
<a href="readmes/README.es.md">Español</a> |
|
||||
<a href="readmes/README.ru.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 align="center">
|
||||
|
|
|
@ -470,7 +470,7 @@ The installation instructions have been slightly updated. However there are no b
|
|||
|
||||
Follow the upgrade instructions for [ansible](https://github.com/LemmyNet/lemmy-ansible#upgrading) or [docker](https://join-lemmy.org/docs/en/administration/install_docker.html#updating).
|
||||
|
||||
If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/!OwmdVYiZSXrXbtCNLw:matrix.org).
|
||||
If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/#lemmy-admin-support-topics:discuss.online).
|
||||
|
||||
## Support development
|
||||
|
||||
|
@ -1016,8 +1016,8 @@ Next, **manually edit** your [lemmy.hjson](https://github.com/LemmyNet/lemmy/blo
|
|||
- `pictrs_url` is removed, and the pictrs config is now a block. If using docker, it should look like:
|
||||
```
|
||||
pictrs: {
|
||||
url: "http://pictrs:8080/"
|
||||
# api_key: "API_KEY"
|
||||
url: "http://pictrs:8080/"
|
||||
api_key: "{{ postgres_password }}"
|
||||
}
|
||||
```
|
||||
- The `rate_limit`, `federation`, `captcha`, and `slur_filter` blocks should be removed, as they are now in the database, and can be updated through the UI.
|
||||
|
@ -1048,7 +1048,7 @@ _Note_: On production databases with thousands of comments, this upgrade **takes
|
|||
|
||||
_Note_: If you have any issues upgrading, you can restore your old database using the [backup and restore instructions here](https://join-lemmy.org/docs/en/administration/backup_and_restore.html).
|
||||
|
||||
If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/!BZVTUuEiNmRcbFeLeI:matrix.org).
|
||||
If you need help with the upgrade, you can ask in our [support forum](https://lemmy.ml/c/lemmy_support) or on the [Matrix Chat](https://matrix.to/#/#lemmy-admin-support-topics:discuss.online).
|
||||
|
||||
## Support development
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"eslint": "^8.40.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"lemmy-js-client": "0.17.2-rc.13",
|
||||
"lemmy-js-client": "0.18.3-rc.3",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.0.4"
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
# IMPORTANT NOTE: this script does not use the normal LEMMY_DATABASE_URL format
|
||||
# it is expected that this script is called by run-federation-test.sh script.
|
||||
set -e
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
export RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
|
||||
|
||||
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
|
||||
echo "DB URL: ${LEMMY_DATABASE_URL} INSTANCE: $INSTANCE"
|
||||
psql "${LEMMY_DATABASE_URL}/lemmy" -c "DROP DATABASE IF EXISTS $INSTANCE"
|
||||
echo "create database"
|
||||
psql "${LEMMY_DATABASE_URL}/lemmy" -c "CREATE DATABASE $INSTANCE"
|
||||
done
|
||||
|
||||
|
@ -26,6 +30,7 @@ else
|
|||
done
|
||||
fi
|
||||
|
||||
echo "killall existing lemmy_server processes"
|
||||
killall lemmy_server || true
|
||||
|
||||
echo "$PWD"
|
||||
|
@ -59,7 +64,12 @@ target/lemmy_server >/tmp/lemmy_epsilon.out 2>&1 &
|
|||
|
||||
echo "wait for all instances to start"
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-alpha:8541/api/v3/site')" != "200" ]]; do sleep 1; done
|
||||
echo "alpha started"
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-beta:8551/api/v3/site')" != "200" ]]; do sleep 1; done
|
||||
echo "beta started"
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-gamma:8561/api/v3/site')" != "200" ]]; do sleep 1; done
|
||||
echo "gamma started"
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-delta:8571/api/v3/site')" != "200" ]]; do sleep 1; done
|
||||
echo "delta started"
|
||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-epsilon:8581/api/v3/site')" != "200" ]]; do sleep 1; done
|
||||
echo "epsilon started. All started"
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
getComments,
|
||||
getCommentParentId,
|
||||
resolveCommunity,
|
||||
getPersonDetails,
|
||||
} from "./shared";
|
||||
import { CommentView } from "lemmy-js-client/dist/types/CommentView";
|
||||
|
||||
|
@ -82,8 +83,7 @@ test("Create a comment", async () => {
|
|||
});
|
||||
|
||||
test("Create a comment in a non-existent post", async () => {
|
||||
let commentRes = (await createComment(alpha, -1)) as any;
|
||||
expect(commentRes.error).toBe("couldnt_find_post");
|
||||
await expect(createComment(alpha, -1)).rejects.toBe("couldnt_find_post");
|
||||
});
|
||||
|
||||
test("Update a comment", async () => {
|
||||
|
@ -122,11 +122,9 @@ test("Delete a comment", async () => {
|
|||
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
||||
|
||||
// Make sure that comment is undefined on beta
|
||||
let betaCommentRes = (await resolveComment(
|
||||
beta,
|
||||
commentRes.comment_view.comment,
|
||||
)) as any;
|
||||
expect(betaCommentRes.error).toBe("couldnt_find_object");
|
||||
await expect(
|
||||
resolveComment(beta, commentRes.comment_view.comment),
|
||||
).rejects.toBe("couldnt_find_object");
|
||||
|
||||
let undeleteCommentRes = await deleteComment(
|
||||
alpha,
|
||||
|
@ -160,9 +158,9 @@ test("Remove a comment from admin and community on the same instance", async ()
|
|||
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
||||
|
||||
// Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
|
||||
let refetchedPostComments = await getComments(
|
||||
let refetchedPostComments = await getPersonDetails(
|
||||
alpha,
|
||||
postRes.post_view.post.id,
|
||||
commentRes.comment_view.comment.creator_id,
|
||||
);
|
||||
expect(refetchedPostComments.comments[0].comment.removed).toBe(true);
|
||||
|
||||
|
|
|
@ -52,8 +52,9 @@ test("Create community", async () => {
|
|||
|
||||
// A dupe check
|
||||
let prevName = communityRes.community_view.community.name;
|
||||
let communityRes2: any = await createCommunity(alpha, prevName);
|
||||
expect(communityRes2["error"]).toBe("community_already_exists");
|
||||
await expect(createCommunity(alpha, prevName)).rejects.toBe(
|
||||
"community_already_exists",
|
||||
);
|
||||
|
||||
// Cache the community on beta, make sure it has the other fields
|
||||
let searchShort = `!${prevName}@lemmy-alpha:8541`;
|
||||
|
|
|
@ -88,17 +88,18 @@ test("Create a post", async () => {
|
|||
assertPostFederation(betaPost, postRes.post_view);
|
||||
|
||||
// Delta only follows beta, so it should not see an alpha ap_id
|
||||
let deltaPost = (await resolvePost(delta, postRes.post_view.post)).post;
|
||||
expect(deltaPost).toBeUndefined();
|
||||
await expect(resolvePost(delta, postRes.post_view.post)).rejects.toBe(
|
||||
"couldnt_find_object",
|
||||
);
|
||||
|
||||
// Epsilon has alpha blocked, it should not see the alpha post
|
||||
let epsilonPost = (await resolvePost(epsilon, postRes.post_view.post)).post;
|
||||
expect(epsilonPost).toBeUndefined();
|
||||
await expect(resolvePost(epsilon, postRes.post_view.post)).rejects.toBe(
|
||||
"couldnt_find_object",
|
||||
);
|
||||
});
|
||||
|
||||
test("Create a post in a non-existent community", async () => {
|
||||
let postRes = (await createPost(alpha, -2)) as any;
|
||||
expect(postRes.error).toBe("couldnt_find_community");
|
||||
await expect(createPost(alpha, -2)).rejects.toBe("couldnt_find_community");
|
||||
});
|
||||
|
||||
test("Unlike a post", async () => {
|
||||
|
@ -145,8 +146,9 @@ test("Update a post", async () => {
|
|||
assertPostFederation(betaPost, updatedPost.post_view);
|
||||
|
||||
// Make sure lemmy beta cannot update the post
|
||||
let updatedPostBeta = (await editPost(beta, betaPost.post)) as any;
|
||||
expect(updatedPostBeta.error).toBe("no_post_edit_allowed");
|
||||
await expect(editPost(beta, betaPost.post)).rejects.toBe(
|
||||
"no_post_edit_allowed",
|
||||
);
|
||||
});
|
||||
|
||||
test("Sticky a post", async () => {
|
||||
|
@ -210,8 +212,7 @@ test("Lock a post", async () => {
|
|||
expect(alphaPost1.post.locked).toBe(true);
|
||||
|
||||
// Try to make a new comment there, on alpha
|
||||
let comment: any = await createComment(alpha, alphaPost1.post.id);
|
||||
expect(comment["error"]).toBe("locked");
|
||||
await expect(createComment(alpha, alphaPost1.post.id)).rejects.toBe("locked");
|
||||
|
||||
// Unlock a post
|
||||
let unlockedPost = await lockPost(beta, false, betaPost1.post);
|
||||
|
@ -242,9 +243,10 @@ test("Delete a post", async () => {
|
|||
expect(deletedPost.post_view.post.name).toBe(postRes.post_view.post.name);
|
||||
|
||||
// Make sure lemmy beta sees post is deleted
|
||||
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
// This will be undefined because of the tombstone
|
||||
expect(betaPost).toBeUndefined();
|
||||
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toBe(
|
||||
"couldnt_find_object",
|
||||
);
|
||||
|
||||
// Undelete
|
||||
let undeletedPost = await deletePost(alpha, false, postRes.post_view.post);
|
||||
|
@ -259,8 +261,9 @@ test("Delete a post", async () => {
|
|||
assertPostFederation(betaPost2, undeletedPost.post_view);
|
||||
|
||||
// Make sure lemmy beta cannot delete the post
|
||||
let deletedPostBeta = (await deletePost(beta, true, betaPost2.post)) as any;
|
||||
expect(deletedPostBeta.error).toStrictEqual("no_post_edit_allowed");
|
||||
await expect(deletePost(beta, true, betaPost2.post)).rejects.toBe(
|
||||
"no_post_edit_allowed",
|
||||
);
|
||||
});
|
||||
|
||||
test("Remove a post from admin and community on different instance", async () => {
|
||||
|
@ -388,8 +391,8 @@ test("Enforce site ban for federated user", async () => {
|
|||
expect(alphaUserOnBeta1.person?.person.banned).toBe(true);
|
||||
|
||||
// existing alpha post should be removed on beta
|
||||
let searchBeta2 = await searchPostLocal(beta, postRes1.post_view.post);
|
||||
expect(searchBeta2.posts[0].post.removed).toBe(true);
|
||||
let searchBeta2 = await getPost(beta, searchBeta1.posts[0].post.id);
|
||||
expect(searchBeta2.post_view.post.removed).toBe(true);
|
||||
|
||||
// Unban alpha
|
||||
let unBanAlpha = await banPersonFromSite(
|
||||
|
@ -436,12 +439,14 @@ test("Enforce community ban for federated user", async () => {
|
|||
expect(banAlpha.banned).toBe(true);
|
||||
|
||||
// ensure that the post by alpha got removed
|
||||
let searchAlpha1 = await searchPostLocal(alpha, postRes1.post_view.post);
|
||||
expect(searchAlpha1.posts[0].post.removed).toBe(true);
|
||||
await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe(
|
||||
"unknown",
|
||||
);
|
||||
|
||||
// Alpha tries to make post on beta, but it fails because of ban
|
||||
let postRes2 = await createPost(alpha, betaCommunity.community.id);
|
||||
expect(postRes2.post_view).toBeUndefined();
|
||||
await expect(createPost(alpha, betaCommunity.community.id)).rejects.toBe(
|
||||
"banned_from_community",
|
||||
);
|
||||
|
||||
// Unban alpha
|
||||
let unBanAlpha = await banPersonFromCommunity(
|
||||
|
|
|
@ -58,6 +58,8 @@ import { CommentReportResponse } from "lemmy-js-client/dist/types/CommentReportR
|
|||
import { CreateCommentReport } from "lemmy-js-client/dist/types/CreateCommentReport";
|
||||
import { ListCommentReportsResponse } from "lemmy-js-client/dist/types/ListCommentReportsResponse";
|
||||
import { ListCommentReports } from "lemmy-js-client/dist/types/ListCommentReports";
|
||||
import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDetailsResponse";
|
||||
import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails";
|
||||
|
||||
export interface API {
|
||||
client: LemmyHttp;
|
||||
|
@ -186,8 +188,11 @@ export async function setupLogins() {
|
|||
await epsilon.client.editSite(editSiteForm);
|
||||
|
||||
// Create the main alpha/beta communities
|
||||
await createCommunity(alpha, "main");
|
||||
await createCommunity(beta, "main");
|
||||
// Ignore thrown errors of duplicates
|
||||
try {
|
||||
await createCommunity(alpha, "main");
|
||||
await createCommunity(beta, "main");
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
export async function createPost(
|
||||
|
@ -646,6 +651,16 @@ export async function saveUserSettings(
|
|||
): Promise<LoginResponse> {
|
||||
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> {
|
||||
let form: DeleteAccount = {
|
||||
|
|
|
@ -92,10 +92,18 @@ test("Delete user", async () => {
|
|||
|
||||
await deleteUser(user);
|
||||
|
||||
expect((await resolvePost(alpha, localPost)).post).toBeUndefined();
|
||||
expect((await resolveComment(alpha, localComment)).comment).toBeUndefined();
|
||||
expect((await resolvePost(alpha, remotePost)).post).toBeUndefined();
|
||||
expect((await resolveComment(alpha, remoteComment)).comment).toBeUndefined();
|
||||
await expect(resolvePost(alpha, localPost)).rejects.toBe(
|
||||
"couldnt_find_object",
|
||||
);
|
||||
await expect(resolveComment(alpha, localComment)).rejects.toBe(
|
||||
"couldnt_find_object",
|
||||
);
|
||||
await expect(resolvePost(alpha, remotePost)).rejects.toBe(
|
||||
"couldnt_find_object",
|
||||
);
|
||||
await expect(resolveComment(alpha, remoteComment)).rejects.toBe(
|
||||
"couldnt_find_object",
|
||||
);
|
||||
});
|
||||
|
||||
test("Requests with invalid auth should be treated as unauthenticated", async () => {
|
||||
|
|
|
@ -2157,10 +2157,10 @@ kleur@^3.0.3:
|
|||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
lemmy-js-client@0.17.2-rc.13:
|
||||
version "0.17.2-rc.13"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.17.2-rc.13.tgz#f2a61050c1308e85cb39c0e1f561e392e84e3921"
|
||||
integrity sha512-4IyR1pisCumJ9L8fEPISC+Su1kVTI4pL/gWLsuOXxZC/lK36mG2+NfaNPiUmIklpCF5TUN+1F7E9bEvtTGogww==
|
||||
lemmy-js-client@0.18.3-rc.3:
|
||||
version "0.18.3-rc.3"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.18.3-rc.3.tgz#fc6489eb141bd09558bca38d9e46b40771a29f37"
|
||||
integrity sha512-njixgXk4uMU4gGifnljwhSe9Kf445C4wAXcXhtpTtwPPLXpHQgxA1RASMb9Uq4zblfE6nC2JbrAka8y8N2N/Bw==
|
||||
dependencies:
|
||||
cross-fetch "^3.1.5"
|
||||
form-data "^4.0.0"
|
||||
|
|
|
@ -22,24 +22,19 @@ impl Perform for ListCommentReports {
|
|||
let data: &ListCommentReports = self;
|
||||
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
let unresolved_only = data.unresolved_only;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let comment_reports = CommentReportQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.my_person_id(person_id)
|
||||
.admin(admin)
|
||||
.community_id(community_id)
|
||||
.unresolved_only(unresolved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let comment_reports = CommentReportQuery {
|
||||
community_id,
|
||||
unresolved_only,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool(), &local_user_view.person)
|
||||
.await?;
|
||||
|
||||
Ok(ListCommentReportsResponse { comment_reports })
|
||||
}
|
||||
|
|
|
@ -76,6 +76,9 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use lemmy_api_common::utils::check_validator_time;
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
|
|
|
@ -27,18 +27,17 @@ impl Perform for GetPersonMentions {
|
|||
let person_id = Some(local_user_view.person.id);
|
||||
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
|
||||
|
||||
let mentions = PersonMentionQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let mentions = PersonMentionQuery {
|
||||
recipient_id: person_id,
|
||||
my_person_id: person_id,
|
||||
sort,
|
||||
unread_only,
|
||||
show_bot_accounts,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
Ok(GetPersonMentionsResponse { mentions })
|
||||
}
|
||||
|
|
|
@ -24,18 +24,17 @@ impl Perform for GetReplies {
|
|||
let person_id = Some(local_user_view.person.id);
|
||||
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
|
||||
|
||||
let replies = CommentReplyQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.recipient_id(person_id)
|
||||
.my_person_id(person_id)
|
||||
.sort(sort)
|
||||
.unread_only(unread_only)
|
||||
.show_bot_accounts(show_bot_accounts)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let replies = CommentReplyQuery {
|
||||
recipient_id: person_id,
|
||||
my_person_id: person_id,
|
||||
sort,
|
||||
unread_only,
|
||||
show_bot_accounts,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
Ok(GetRepliesResponse { replies })
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ impl Perform for SaveUserSettings {
|
|||
.totp_2fa_secret(totp_2fa_secret)
|
||||
.totp_2fa_url(totp_2fa_url)
|
||||
.open_links_in_new_tab(data.open_links_in_new_tab)
|
||||
.infinite_scroll_enabled(data.infinite_scroll_enabled)
|
||||
.build();
|
||||
|
||||
let local_user_res =
|
||||
|
|
|
@ -22,24 +22,19 @@ impl Perform for ListPostReports {
|
|||
let data: &ListPostReports = self;
|
||||
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.person.admin;
|
||||
let community_id = data.community_id;
|
||||
let unresolved_only = data.unresolved_only;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let post_reports = PostReportQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.my_person_id(person_id)
|
||||
.admin(admin)
|
||||
.community_id(community_id)
|
||||
.unresolved_only(unresolved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let post_reports = PostReportQuery {
|
||||
community_id,
|
||||
unresolved_only,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool(), &local_user_view.person)
|
||||
.await?;
|
||||
|
||||
Ok(ListPostReportsResponse { post_reports })
|
||||
}
|
||||
|
|
|
@ -21,14 +21,13 @@ impl Perform for ListPrivateMessageReports {
|
|||
let unresolved_only = self.unresolved_only;
|
||||
let page = self.page;
|
||||
let limit = self.limit;
|
||||
let private_message_reports = PrivateMessageReportQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.unresolved_only(unresolved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let private_message_reports = PrivateMessageReportQuery {
|
||||
unresolved_only,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
Ok(ListPrivateMessageReportsResponse {
|
||||
private_message_reports,
|
||||
|
|
|
@ -23,19 +23,18 @@ impl Perform for ListRegistrationApplications {
|
|||
is_admin(&local_user_view)?;
|
||||
|
||||
let unread_only = data.unread_only;
|
||||
let verified_email_only = local_site.require_email_verification;
|
||||
let verified_email_only = Some(local_site.require_email_verification);
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let registration_applications = RegistrationApplicationQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.unread_only(unread_only)
|
||||
.verified_email_only(Some(verified_email_only))
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let registration_applications = RegistrationApplicationQuery {
|
||||
unread_only,
|
||||
verified_email_only,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
Ok(Self::Response {
|
||||
registration_applications,
|
||||
|
|
|
@ -14,9 +14,27 @@ path = "src/lib.rs"
|
|||
doctest = false
|
||||
|
||||
[features]
|
||||
full = ["tracing", "rosetta-i18n", "chrono", "lemmy_utils",
|
||||
"lemmy_db_views/full", "lemmy_db_views_actor/full", "lemmy_db_views_moderator/full",
|
||||
"percent-encoding", "encoding", "reqwest-middleware", "webpage", "ts-rs"]
|
||||
full = [
|
||||
"tracing",
|
||||
"rosetta-i18n",
|
||||
"chrono",
|
||||
"lemmy_utils",
|
||||
"lemmy_db_views/full",
|
||||
"lemmy_db_views_actor/full",
|
||||
"lemmy_db_views_moderator/full",
|
||||
"activitypub_federation",
|
||||
"percent-encoding",
|
||||
"encoding",
|
||||
"reqwest-middleware",
|
||||
"webpage",
|
||||
"ts-rs",
|
||||
"tokio",
|
||||
"uuid",
|
||||
"reqwest",
|
||||
"actix-web",
|
||||
"futures",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_views = { workspace = true }
|
||||
|
@ -24,6 +42,7 @@ lemmy_db_views_moderator = { workspace = true }
|
|||
lemmy_db_views_actor = { workspace = true }
|
||||
lemmy_db_schema = { workspace = true }
|
||||
lemmy_utils = { workspace = true, optional = true }
|
||||
activitypub_federation = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_with = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
@ -33,12 +52,17 @@ reqwest-middleware = { workspace = true, optional = true }
|
|||
regex = { workspace = true }
|
||||
rosetta-i18n = { workspace = true, optional = true }
|
||||
percent-encoding = { workspace = true, optional = true }
|
||||
webpage = { version = "1.6", default-features = false, features = ["serde"], optional = true }
|
||||
webpage = { version = "1.6", default-features = false, features = [
|
||||
"serde",
|
||||
], optional = true }
|
||||
encoding = { version = "0.2.33", optional = true }
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
uuid = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
reqwest = { workspace = true, optional = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
actix-web = { workspace = true }
|
||||
once_cell = { workspace = true, optional = true }
|
||||
actix-web = { workspace = true, optional = true }
|
||||
# necessary for wasmt compilation
|
||||
getrandom = { version = "0.2.10", features = ["js"] }
|
||||
|
|
|
@ -64,7 +64,7 @@ pub async fn build_community_response(
|
|||
}
|
||||
|
||||
pub async fn build_post_response(
|
||||
context: &Data<LemmyContext>,
|
||||
context: &LemmyContext,
|
||||
community_id: CommunityId,
|
||||
person_id: PersonId,
|
||||
post_id: PostId,
|
||||
|
|
|
@ -10,6 +10,8 @@ pub mod post;
|
|||
pub mod private_message;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod request;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod send_activity;
|
||||
pub mod sensitive;
|
||||
pub mod site;
|
||||
#[cfg(feature = "full")]
|
||||
|
|
|
@ -133,6 +133,8 @@ pub struct SaveUserSettings {
|
|||
pub auth: Sensitive<String>,
|
||||
/// Open links in a new tab
|
||||
pub open_links_in_new_tab: Option<bool>,
|
||||
/// Enable infinite scroll
|
||||
pub infinite_scroll_enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
|
|
|
@ -270,6 +270,9 @@ pub fn build_user_agent(settings: &Settings) -> String {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::request::{
|
||||
build_user_agent,
|
||||
fetch_site_metadata,
|
||||
|
|
74
crates/api_common/src/send_activity.rs
Normal file
74
crates/api_common/src/send_activity.rs
Normal 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(())
|
||||
}
|
||||
}
|
|
@ -667,13 +667,13 @@ pub async fn remove_user_data_in_community(
|
|||
|
||||
// Comments
|
||||
// TODO Diesel doesn't allow updates with joins, so this has to be a loop
|
||||
let comments = CommentQuery::builder()
|
||||
.pool(pool)
|
||||
.creator_id(Some(banned_person_id))
|
||||
.community_id(Some(community_id))
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let comments = CommentQuery {
|
||||
creator_id: Some(banned_person_id),
|
||||
community_id: Some(community_id),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool)
|
||||
.await?;
|
||||
|
||||
for comment_view in &comments {
|
||||
let comment_id = comment_view.comment.id;
|
||||
|
@ -731,6 +731,9 @@ pub async fn delete_user_account(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::utils::{honeypot_check, password_length_check};
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -23,4 +23,4 @@ url = { workspace = true }
|
|||
async-trait = { workspace = true }
|
||||
webmention = "0.4.0"
|
||||
chrono = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
|
|
@ -16,6 +16,7 @@ use lemmy_api_common::{
|
|||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::default_post_language,
|
||||
source::{
|
||||
actor_language::CommunityLanguage,
|
||||
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
|
||||
|
@ -82,25 +83,31 @@ impl PerformCrud for CreateComment {
|
|||
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(
|
||||
&mut context.pool(),
|
||||
Some(language_id),
|
||||
data.language_id,
|
||||
community_id,
|
||||
)
|
||||
.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()
|
||||
.content(content_slurs_removed.clone())
|
||||
.post_id(data.post_id)
|
||||
.creator_id(local_user_view.person.id)
|
||||
.language_id(Some(language_id))
|
||||
.language_id(language_id)
|
||||
.build();
|
||||
|
||||
// Create the comment
|
||||
|
|
|
@ -31,18 +31,18 @@ impl PerformCrud for ListCommunities {
|
|||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let local_user = local_user_view.map(|l| l.local_user);
|
||||
let communities = CommunityQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.listing_type(listing_type)
|
||||
.show_nsfw(show_nsfw)
|
||||
.sort(sort)
|
||||
.local_user(local_user.as_ref())
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.is_mod_or_admin(is_admin)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let communities = CommunityQuery {
|
||||
listing_type,
|
||||
show_nsfw,
|
||||
sort,
|
||||
local_user: local_user.as_ref(),
|
||||
page,
|
||||
limit,
|
||||
is_mod_or_admin: is_admin,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
// Return the jwt
|
||||
Ok(ListCommunitiesResponse { communities })
|
||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_utils::error::LemmyError;
|
|||
mod comment;
|
||||
mod community;
|
||||
mod custom_emoji;
|
||||
mod post;
|
||||
pub mod post;
|
||||
mod private_message;
|
||||
mod site;
|
||||
mod user;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::PerformCrud;
|
||||
use actix_web::web::Data;
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
build_response::build_post_response,
|
||||
context::LemmyContext,
|
||||
post::{CreatePost, PostResponse},
|
||||
request::fetch_site_data,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{
|
||||
check_community_ban,
|
||||
check_community_deleted_or_removed,
|
||||
|
@ -40,147 +41,153 @@ use tracing::Instrument;
|
|||
use url::Url;
|
||||
use webmention::{Webmention, WebmentionError};
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl PerformCrud for CreatePost {
|
||||
type Response = PostResponse;
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn create_post(
|
||||
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))]
|
||||
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> {
|
||||
let data: &CreatePost = self;
|
||||
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
check_slurs(&data.name, &slur_regex)?;
|
||||
check_slurs_opt(&data.body, &slur_regex)?;
|
||||
honeypot_check(&data.honeypot)?;
|
||||
|
||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
check_slurs(&data.name, &slur_regex)?;
|
||||
check_slurs_opt(&data.body, &slur_regex)?;
|
||||
honeypot_check(&data.honeypot)?;
|
||||
let data_url = data.url.as_ref();
|
||||
let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
|
||||
|
||||
let data_url = data.url.as_ref();
|
||||
let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
|
||||
is_valid_post_title(&data.name)?;
|
||||
is_valid_body_field(&data.body, true)?;
|
||||
check_url_scheme(&data.url)?;
|
||||
|
||||
is_valid_post_title(&data.name)?;
|
||||
is_valid_body_field(&data.body, true)?;
|
||||
check_url_scheme(&data.url)?;
|
||||
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
data.community_id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
check_community_deleted_or_removed(data.community_id, &mut context.pool()).await?;
|
||||
check_community_ban(
|
||||
local_user_view.person.id,
|
||||
data.community_id,
|
||||
&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 = Community::read(&mut context.pool(), community_id).await?;
|
||||
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(
|
||||
let is_mod = CommunityView::is_mod_or_admin(
|
||||
&mut context.pool(),
|
||||
language_id,
|
||||
local_user_view.local_user.person_id,
|
||||
community_id,
|
||||
)
|
||||
.await?;
|
||||
if !is_mod {
|
||||
return Err(LemmyErrorType::OnlyModsCanPostInCommunity)?;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
// 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 inserted_post = Post::create(&mut context.pool(), &post_form)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
|
||||
// Only need to check if language is allowed in case user set it explicitly. When using default
|
||||
// language, it already only returns allowed languages.
|
||||
CommunityLanguage::is_allowed_community_language(
|
||||
&mut context.pool(),
|
||||
data.language_id,
|
||||
community_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let inserted_post_id = inserted_post.id;
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let apub_id = generate_local_apub_endpoint(
|
||||
EndpointType::Post,
|
||||
&inserted_post_id.to_string(),
|
||||
&protocol_and_hostname,
|
||||
)?;
|
||||
let updated_post = Post::update(
|
||||
&mut context.pool(),
|
||||
inserted_post_id,
|
||||
&PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
|
||||
)
|
||||
// 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 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
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
|
||||
|
||||
// They like their own post by default
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_id = inserted_post.id;
|
||||
let like_form = PostLikeForm {
|
||||
post_id,
|
||||
person_id,
|
||||
score: 1,
|
||||
};
|
||||
let inserted_post_id = inserted_post.id;
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let apub_id = generate_local_apub_endpoint(
|
||||
EndpointType::Post,
|
||||
&inserted_post_id.to_string(),
|
||||
&protocol_and_hostname,
|
||||
)?;
|
||||
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)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
||||
// They like their own post by default
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_id = inserted_post.id;
|
||||
let like_form = PostLikeForm {
|
||||
post_id,
|
||||
person_id,
|
||||
score: 1,
|
||||
};
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
PostLike::like(&mut context.pool(), &like_form)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
||||
|
||||
if let Some(url) = updated_post.url.clone() {
|
||||
let task = async move {
|
||||
let mut webmention =
|
||||
Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
|
||||
webmention.set_checked(true);
|
||||
match webmention
|
||||
.send()
|
||||
.instrument(tracing::info_span!("Sending webmention"))
|
||||
.await
|
||||
{
|
||||
Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()),
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e).with_lemmy_type(LemmyErrorType::CouldntSendWebmention),
|
||||
}
|
||||
};
|
||||
if *SYNCHRONOUS_FEDERATION {
|
||||
task.await?;
|
||||
} else {
|
||||
spawn_try_task(task);
|
||||
ActivityChannel::submit_activity(SendActivityData::CreatePost(updated_post.clone()), &context)
|
||||
.await?;
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
|
||||
if let Some(url) = updated_post.url.clone() {
|
||||
let task = async move {
|
||||
let mut webmention =
|
||||
Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
|
||||
webmention.set_checked(true);
|
||||
match webmention
|
||||
.send()
|
||||
.instrument(tracing::info_span!("Sending webmention"))
|
||||
.await
|
||||
{
|
||||
Err(WebmentionError::NoEndpointDiscovered(_)) => Ok(()),
|
||||
Ok(_) => Ok(()),
|
||||
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?,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mod create;
|
||||
pub mod create;
|
||||
mod delete;
|
||||
mod read;
|
||||
mod remove;
|
||||
|
|
|
@ -100,12 +100,12 @@ impl PerformCrud for GetPost {
|
|||
|
||||
// Fetch the cross_posts
|
||||
let cross_posts = if let Some(url) = &post_view.post.url {
|
||||
let mut x_posts = PostQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.url_search(Some(url.inner().as_str().into()))
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let mut x_posts = PostQuery {
|
||||
url_search: Some(url.inner().as_str().into()),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
// Don't return this post as one of the cross_posts
|
||||
x_posts.retain(|x| x.post.id != post_id);
|
||||
|
|
|
@ -24,15 +24,13 @@ impl PerformCrud for GetPrivateMessages {
|
|||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only;
|
||||
let mut messages = PrivateMessageQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.recipient_id(person_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.unread_only(unread_only)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let mut messages = PrivateMessageQuery {
|
||||
page,
|
||||
limit,
|
||||
unread_only,
|
||||
}
|
||||
.list(&mut context.pool(), person_id)
|
||||
.await?;
|
||||
|
||||
// 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.
|
||||
|
|
|
@ -183,6 +183,9 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::site::create::validate_create_payload;
|
||||
use lemmy_api_common::site::CreateSite;
|
||||
use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode};
|
||||
|
|
|
@ -42,6 +42,9 @@ pub fn application_question_check(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::site::{application_question_check, site_default_post_listing_type_check};
|
||||
use lemmy_db_schema::{ListingType, RegistrationMode};
|
||||
|
||||
|
|
|
@ -217,6 +217,9 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::site::update::validate_update_payload;
|
||||
use lemmy_api_common::site::EditSite;
|
||||
use lemmy_db_schema::{source::local_site::LocalSite, ListingType, RegistrationMode};
|
||||
|
|
|
@ -138,6 +138,7 @@ impl PerformCrud for Register {
|
|||
.password_encrypted(data.password.to_string())
|
||||
.show_nsfw(Some(data.show_nsfw))
|
||||
.accepted_application(accepted_application)
|
||||
.default_listing_type(Some(local_site.default_post_listing_type))
|
||||
.build();
|
||||
|
||||
let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?;
|
||||
|
|
|
@ -25,7 +25,7 @@ chrono = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
actix-web = { workspace = true }
|
||||
tokio = {workspace = true}
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{instance::remote_instance_inboxes, person::ApubPerson},
|
||||
protocol::activities::block::block_user::BlockUser,
|
||||
};
|
||||
|
@ -124,6 +124,7 @@ impl ActivityHandler for BlockUser {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
match self.target.dereference(context).await? {
|
||||
SiteOrCommunity::Site(site) => {
|
||||
|
@ -147,7 +148,6 @@ impl ActivityHandler for BlockUser {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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 mod_person = self.actor.dereference(context).await?;
|
||||
let blocked_person = self.object.dereference(context).await?;
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
verify_is_public,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{instance::remote_instance_inboxes, person::ApubPerson},
|
||||
protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||
};
|
||||
|
@ -88,6 +88,7 @@ impl ActivityHandler for UndoBlockUser {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
verify_domains_match(self.actor.inner(), self.object.actor.inner())?;
|
||||
self.object.verify(context).await?;
|
||||
|
@ -96,7 +97,6 @@ impl ActivityHandler for UndoBlockUser {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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 mod_person = self.actor.dereference(context).await?;
|
||||
let blocked_person = self.object.object.dereference(context).await?;
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::community::ApubCommunity,
|
||||
protocol::{
|
||||
activities::community::announce::{AnnounceActivity, RawAnnouncableActivities},
|
||||
|
@ -133,14 +133,14 @@ impl ActivityHandler for AnnounceActivity {
|
|||
}
|
||||
|
||||
#[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)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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()?;
|
||||
// This is only for sending, not receiving so we reject it.
|
||||
if let AnnouncableActivities::Page(_) = object {
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::{
|
||||
activities::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove},
|
||||
|
@ -108,6 +108,7 @@ impl ActivityHandler for CollectionAdd {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context).await?;
|
||||
verify_person_in_community(&self.actor, &community, context).await?;
|
||||
|
@ -117,7 +118,6 @@ impl ActivityHandler for CollectionAdd {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_activity(&self.id, &self, false, false, context).await?;
|
||||
let (community, collection_type) =
|
||||
Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?;
|
||||
match collection_type {
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::{activities::community::collection_remove::CollectionRemove, InCommunity},
|
||||
};
|
||||
|
@ -101,6 +101,7 @@ impl ActivityHandler for CollectionRemove {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context).await?;
|
||||
verify_person_in_community(&self.actor, &community, context).await?;
|
||||
|
@ -110,7 +111,6 @@ impl ActivityHandler for CollectionRemove {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_activity(&self.id, &self, false, false, context).await?;
|
||||
let (community, collection_type) =
|
||||
Community::get_by_collection_url(&mut context.pool(), &self.target.into()).await?;
|
||||
match collection_type {
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
protocol::{
|
||||
activities::community::lock_page::{LockPage, LockType, UndoLockPage},
|
||||
InCommunity,
|
||||
|
@ -79,6 +79,7 @@ impl ActivityHandler for UndoLockPage {
|
|||
}
|
||||
|
||||
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)?;
|
||||
let community = self.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> {
|
||||
insert_activity(&self.id, &self, false, false, context).await?;
|
||||
let form = PostUpdateForm::builder().locked(Some(false)).build();
|
||||
let post = self.object.object.dereference(context).await?;
|
||||
Post::update(&mut context.pool(), post.id, &form).await?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::community::report::Report, InCommunity},
|
||||
PostOrComment,
|
||||
|
@ -115,6 +115,7 @@ impl ActivityHandler for Report {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
let community = self.community(context).await?;
|
||||
verify_person_in_community(&self.actor, &community, context).await?;
|
||||
Ok(())
|
||||
|
@ -122,7 +123,6 @@ impl ActivityHandler for Report {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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?;
|
||||
match self.object.dereference(context).await? {
|
||||
PostOrComment::Post(post) => {
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{activities::community::update::UpdateCommunity, InCommunity},
|
||||
SendActivity,
|
||||
|
@ -82,6 +82,7 @@ impl ActivityHandler for UpdateCommunity {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context).await?;
|
||||
verify_person_in_community(&self.actor, &community, context).await?;
|
||||
|
@ -92,7 +93,6 @@ impl ActivityHandler for UpdateCommunity {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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_update_form = self.object.into_update_form();
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
mentions::MentionOrValue,
|
||||
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{
|
||||
|
@ -154,6 +154,7 @@ impl ActivityHandler for CreateOrUpdateNote {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let post = self.object.get_parents(context).await?.0;
|
||||
let community = self.community(context).await?;
|
||||
|
@ -169,7 +170,6 @@ impl ActivityHandler for CreateOrUpdateNote {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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
|
||||
// send the activity, not the comment author.
|
||||
let existing_comment = self.object.id.dereference_local(context).await.ok();
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
activity_lists::AnnouncableActivities,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::{
|
||||
activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
|
||||
|
@ -24,7 +24,7 @@ use activitypub_federation::{
|
|||
};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
post::{CreatePost, EditPost, PostResponse},
|
||||
post::{EditPost, PostResponse},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::PostAggregates,
|
||||
|
@ -39,25 +39,6 @@ use lemmy_db_schema::{
|
|||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||
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]
|
||||
impl SendActivity for EditPost {
|
||||
type Response = PostResponse;
|
||||
|
@ -68,10 +49,10 @@ impl SendActivity for EditPost {
|
|||
context: &Data<LemmyContext>,
|
||||
) -> Result<(), LemmyError> {
|
||||
CreateOrUpdatePage::send(
|
||||
&response.post_view.post,
|
||||
response.post_view.post.clone(),
|
||||
response.post_view.creator.id,
|
||||
CreateOrUpdateType::Update,
|
||||
context,
|
||||
context.reset_request_count(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
@ -102,12 +83,12 @@ impl CreateOrUpdatePage {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn send(
|
||||
post: &Post,
|
||||
post: Post,
|
||||
person_id: PersonId,
|
||||
kind: CreateOrUpdateType,
|
||||
context: &Data<LemmyContext>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<(), LemmyError> {
|
||||
let post = ApubPost(post.clone());
|
||||
let post = ApubPost(post);
|
||||
let community_id = post.community_id;
|
||||
let person: ApubPerson = Person::read(&mut context.pool(), person_id).await?.into();
|
||||
let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
|
||||
|
@ -115,8 +96,8 @@ impl CreateOrUpdatePage {
|
|||
.into();
|
||||
|
||||
let create_or_update =
|
||||
CreateOrUpdatePage::new(post, &person, &community, kind, context).await?;
|
||||
let is_mod_action = create_or_update.object.is_mod_action(context).await?;
|
||||
CreateOrUpdatePage::new(post, &person, &community, kind, &context).await?;
|
||||
let is_mod_action = create_or_update.object.is_mod_action(&context).await?;
|
||||
let activity = AnnouncableActivities::CreateOrUpdatePost(create_or_update);
|
||||
send_activity_in_community(
|
||||
activity,
|
||||
|
@ -124,7 +105,7 @@ impl CreateOrUpdatePage {
|
|||
&community,
|
||||
vec![],
|
||||
is_mod_action,
|
||||
context,
|
||||
&context,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
|
@ -146,6 +127,7 @@ impl ActivityHandler for CreateOrUpdatePage {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &self.cc)?;
|
||||
let community = self.community(context).await?;
|
||||
verify_person_in_community(&self.actor, &community, context).await?;
|
||||
|
@ -180,7 +162,6 @@ impl ActivityHandler for CreateOrUpdatePage {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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?;
|
||||
|
||||
// author likes their own post by default
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
activities::{generate_activity_id, send_lemmy_activity, verify_person},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
|
||||
protocol::activities::{
|
||||
create_or_update::chat_message::CreateOrUpdateChatMessage,
|
||||
|
@ -109,6 +109,7 @@ impl ActivityHandler for CreateOrUpdateChatMessage {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_person(&self.actor, context).await?;
|
||||
verify_domains_match(self.actor.inner(), self.object.id.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)]
|
||||
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?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
|
||||
generate_activity_id,
|
||||
},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::person::ApubPerson,
|
||||
protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
|
||||
};
|
||||
|
@ -43,13 +43,13 @@ impl ActivityHandler for Delete {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_activity(&self.id, &self, false, false, context).await?;
|
||||
if let Some(reason) = self.summary {
|
||||
// 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.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
activities::{generate_activity_id, send_lemmy_activity, verify_is_public, verify_person},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{instance::remote_instance_inboxes, person::ApubPerson},
|
||||
protocol::activities::deletion::delete_user::DeleteUser,
|
||||
SendActivity,
|
||||
|
@ -73,6 +73,7 @@ impl ActivityHandler for DeleteUser {
|
|||
}
|
||||
|
||||
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_is_public(&self.to, &[])?;
|
||||
verify_person(&self.actor, context).await?;
|
||||
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> {
|
||||
insert_activity(&self.id, &self, false, false, context).await?;
|
||||
let actor = self.actor.dereference(context).await?;
|
||||
delete_user_account(
|
||||
actor.id,
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
|
||||
generate_activity_id,
|
||||
},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::person::ApubPerson,
|
||||
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> {
|
||||
insert_received_activity(&self.id, data).await?;
|
||||
self.object.verify(data).await?;
|
||||
verify_delete_activity(&self.object, self.object.summary.is_some(), data).await?;
|
||||
Ok(())
|
||||
|
@ -49,7 +50,6 @@ impl ActivityHandler for UndoDelete {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_activity(&self.id, &self, false, false, context).await?;
|
||||
if self.object.summary.is_some() {
|
||||
UndoDelete::receive_undo_remove_action(
|
||||
&self.actor.dereference(context).await?,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
activities::{generate_activity_id, send_lemmy_activity},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
protocol::activities::following::{accept::AcceptFollow, follow::Follow},
|
||||
};
|
||||
use activitypub_federation::{
|
||||
|
@ -50,6 +50,7 @@ impl ActivityHandler for AcceptFollow {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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())?;
|
||||
self.object.verify(context).await?;
|
||||
if let Some(to) = &self.to {
|
||||
|
@ -60,7 +61,6 @@ impl ActivityHandler for AcceptFollow {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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 person = self.object.actor.dereference(context).await?;
|
||||
// This will throw an error if no follow was requested
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
fetcher::user_or_community::UserOrCommunity,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::activities::following::{
|
||||
accept::AcceptFollow,
|
||||
|
@ -90,6 +90,7 @@ impl ActivityHandler for Follow {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
verify_person(&self.actor, context).await?;
|
||||
let object = self.object.dereference(context).await?;
|
||||
if let UserOrCommunity::Community(c) = object {
|
||||
|
@ -103,7 +104,6 @@ impl ActivityHandler for Follow {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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 object = self.object.dereference(context).await?;
|
||||
match object {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
activities::{generate_activity_id, send_lemmy_activity, verify_person},
|
||||
fetcher::user_or_community::UserOrCommunity,
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::activities::following::{follow::Follow, undo_follow::UndoFollow},
|
||||
};
|
||||
|
@ -60,6 +60,7 @@ impl ActivityHandler for UndoFollow {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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_person(&self.actor, context).await?;
|
||||
self.object.verify(context).await?;
|
||||
|
@ -71,7 +72,6 @@ impl ActivityHandler for UndoFollow {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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 object = self.object.object.dereference(context).await?;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
insert_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
|
||||
CONTEXT,
|
||||
};
|
||||
use activitypub_federation::{
|
||||
|
@ -12,12 +12,28 @@ use activitypub_federation::{
|
|||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::{newtypes::CommunityId, source::community::Community};
|
||||
use lemmy_api_common::{
|
||||
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_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 std::ops::Deref;
|
||||
use std::{ops::Deref, sync::Arc, time::Duration};
|
||||
use tracing::info;
|
||||
use url::{ParseError, Url};
|
||||
use uuid::Uuid;
|
||||
|
@ -30,6 +46,10 @@ pub mod following;
|
|||
pub mod unfederated;
|
||||
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
|
||||
/// doesn't have a site ban.
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -148,7 +168,7 @@ async fn send_lemmy_activity<Activity, ActorT>(
|
|||
data: &Data<LemmyContext>,
|
||||
activity: Activity,
|
||||
actor: &ActorT,
|
||||
inbox: Vec<Url>,
|
||||
mut inbox: Vec<Url>,
|
||||
sensitive: bool,
|
||||
) -> Result<(), LemmyError>
|
||||
where
|
||||
|
@ -156,11 +176,62 @@ where
|
|||
ActorT: Actor,
|
||||
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());
|
||||
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?;
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
voting::{undo_vote_comment, undo_vote_post},
|
||||
},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{
|
||||
activities::voting::{undo_vote::UndoVote, vote::Vote},
|
||||
|
@ -57,6 +57,7 @@ impl ActivityHandler for UndoVote {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
let community = self.community(context).await?;
|
||||
verify_person_in_community(&self.actor, &community, context).await?;
|
||||
verify_urls_match(self.actor.inner(), self.object.actor.inner())?;
|
||||
|
@ -66,7 +67,6 @@ impl ActivityHandler for UndoVote {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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 object = self.object.object.dereference(context).await?;
|
||||
match object {
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
voting::{vote_comment, vote_post},
|
||||
},
|
||||
insert_activity,
|
||||
insert_received_activity,
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::{
|
||||
activities::voting::vote::{Vote, VoteType},
|
||||
|
@ -56,6 +56,7 @@ impl ActivityHandler for Vote {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn verify(&self, context: &Data<LemmyContext>) -> Result<(), LemmyError> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
let community = self.community(context).await?;
|
||||
verify_person_in_community(&self.actor, &community, context).await?;
|
||||
let enable_downvotes = LocalSite::read(&mut context.pool())
|
||||
|
@ -70,7 +71,6 @@ impl ActivityHandler for Vote {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
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 object = self.object.dereference(context).await?;
|
||||
match object {
|
||||
|
|
|
@ -134,6 +134,9 @@ impl InCommunity for AnnouncableActivities {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
activity_lists::{
|
||||
GroupInboxActivities,
|
||||
|
|
|
@ -39,7 +39,11 @@ pub async fn list_comments(
|
|||
let limit = data.limit;
|
||||
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
|
||||
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 post_id = data.post_id;
|
||||
let local_user = local_user_view.map(|l| l.local_user);
|
||||
let comments = CommentQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.listing_type(Some(listing_type))
|
||||
.sort(sort)
|
||||
.max_depth(max_depth)
|
||||
.saved_only(saved_only)
|
||||
.community_id(community_id)
|
||||
.parent_path(parent_path_cloned)
|
||||
.post_id(post_id)
|
||||
.local_user(local_user.as_ref())
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntGetComments)?;
|
||||
let comments = CommentQuery {
|
||||
listing_type,
|
||||
sort,
|
||||
max_depth,
|
||||
saved_only,
|
||||
community_id,
|
||||
parent_path: parent_path_cloned,
|
||||
post_id,
|
||||
local_user: local_user_view.as_ref(),
|
||||
page,
|
||||
limit,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntGetComments)?;
|
||||
|
||||
Ok(Json(GetCommentsResponse { comments }))
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use actix_web::web::{Json, Query};
|
|||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
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_views::post_view::PostQuery;
|
||||
|
@ -36,27 +36,25 @@ pub async fn list_posts(
|
|||
};
|
||||
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 =
|
||||
is_mod_or_admin_opt(&mut context.pool(), local_user_view.as_ref(), community_id)
|
||||
.await
|
||||
.is_ok();
|
||||
|
||||
let posts = PostQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.local_user(local_user_view.map(|l| l.local_user).as_ref())
|
||||
.listing_type(Some(listing_type))
|
||||
.sort(sort)
|
||||
.community_id(community_id)
|
||||
.saved_only(saved_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.is_mod_or_admin(Some(is_mod_or_admin))
|
||||
.build()
|
||||
.list()
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntGetPosts)?;
|
||||
let posts = PostQuery {
|
||||
local_user: local_user_view.as_ref(),
|
||||
listing_type,
|
||||
sort,
|
||||
community_id,
|
||||
saved_only,
|
||||
page,
|
||||
limit,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntGetPosts)?;
|
||||
|
||||
Ok(Json(GetPostsResponse { posts }))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use actix_web::web::{Json, Query};
|
|||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
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::{
|
||||
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_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)?;
|
||||
|
||||
|
@ -53,52 +52,42 @@ pub async fn read_person(
|
|||
let limit = data.limit;
|
||||
let saved_only = data.saved_only;
|
||||
let community_id = data.community_id;
|
||||
let local_user = local_user_view.map(|l| l.local_user);
|
||||
let local_user_clone = local_user.clone();
|
||||
// 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
|
||||
let creator_id = if !saved_only.unwrap_or(false) {
|
||||
Some(person_details_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let posts = PostQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.saved_only(saved_only)
|
||||
.local_user(local_user.as_ref())
|
||||
.community_id(community_id)
|
||||
.is_mod_or_admin(is_admin)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.creator_id(
|
||||
// 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
|
||||
if !saved_only.unwrap_or(false) {
|
||||
Some(person_details_id)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let posts = PostQuery {
|
||||
sort,
|
||||
saved_only,
|
||||
local_user: local_user_view.as_ref(),
|
||||
community_id,
|
||||
is_profile_view: Some(true),
|
||||
page,
|
||||
limit,
|
||||
creator_id,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
let comments = CommentQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.local_user(local_user_clone.as_ref())
|
||||
.sort(sort.map(post_to_comment_sort_type))
|
||||
.saved_only(saved_only)
|
||||
.show_deleted_and_removed(Some(false))
|
||||
.community_id(community_id)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.creator_id(
|
||||
// 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
|
||||
if !saved_only.unwrap_or(false) {
|
||||
Some(person_details_id)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let comments = CommentQuery {
|
||||
local_user: (local_user_view.as_ref()),
|
||||
sort: (sort.map(post_to_comment_sort_type)),
|
||||
saved_only: (saved_only),
|
||||
show_deleted_and_removed: (Some(false)),
|
||||
community_id: (community_id),
|
||||
is_profile_view: Some(true),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
creator_id,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
let moderates =
|
||||
CommunityModeratorView::for_person(&mut context.pool(), person_details_id).await?;
|
||||
|
|
|
@ -50,119 +50,116 @@ pub async fn search(
|
|||
data.community_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 {
|
||||
SearchType::Posts => {
|
||||
posts = PostQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.community_id(community_id)
|
||||
.creator_id(creator_id)
|
||||
.local_user(local_user.as_ref())
|
||||
.search_term(Some(q))
|
||||
.is_mod_or_admin(is_admin)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
posts = PostQuery {
|
||||
sort: (sort),
|
||||
listing_type: (listing_type),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
search_term: (Some(q)),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
}
|
||||
SearchType::Comments => {
|
||||
comments = CommentQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort.map(post_to_comment_sort_type))
|
||||
.listing_type(listing_type)
|
||||
.search_term(Some(q))
|
||||
.community_id(community_id)
|
||||
.creator_id(creator_id)
|
||||
.local_user(local_user.as_ref())
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
comments = CommentQuery {
|
||||
sort: (sort.map(post_to_comment_sort_type)),
|
||||
listing_type: (listing_type),
|
||||
search_term: (Some(q)),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
}
|
||||
SearchType::Communities => {
|
||||
communities = CommunityQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(Some(q))
|
||||
.local_user(local_user.as_ref())
|
||||
.is_mod_or_admin(is_admin)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
communities = CommunityQuery {
|
||||
sort: (sort),
|
||||
listing_type: (listing_type),
|
||||
search_term: (Some(q)),
|
||||
local_user: (local_user.as_ref()),
|
||||
is_mod_or_admin: (is_admin),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
}
|
||||
SearchType::Users => {
|
||||
users = PersonQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.search_term(Some(q))
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
users = PersonQuery {
|
||||
sort: (sort),
|
||||
search_term: (Some(q)),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
}
|
||||
SearchType::All => {
|
||||
// If the community or creator is included, dont search communities or users
|
||||
let community_or_creator_included =
|
||||
data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some();
|
||||
|
||||
let local_user_ = local_user.clone();
|
||||
posts = PostQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.community_id(community_id)
|
||||
.creator_id(creator_id)
|
||||
.local_user(local_user_.as_ref())
|
||||
.search_term(Some(q))
|
||||
.is_mod_or_admin(is_admin)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
let q = data.q.clone();
|
||||
|
||||
posts = PostQuery {
|
||||
sort: (sort),
|
||||
listing_type: (listing_type),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
search_term: (Some(q)),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
let q = data.q.clone();
|
||||
|
||||
let local_user_ = local_user.clone();
|
||||
comments = CommentQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort.map(post_to_comment_sort_type))
|
||||
.listing_type(listing_type)
|
||||
.search_term(Some(q))
|
||||
.community_id(community_id)
|
||||
.creator_id(creator_id)
|
||||
.local_user(local_user_.as_ref())
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
comments = CommentQuery {
|
||||
sort: (sort.map(post_to_comment_sort_type)),
|
||||
listing_type: (listing_type),
|
||||
search_term: (Some(q)),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
let q = data.q.clone();
|
||||
|
||||
communities = if community_or_creator_included {
|
||||
vec![]
|
||||
} else {
|
||||
CommunityQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.search_term(Some(q))
|
||||
.local_user(local_user.as_ref())
|
||||
.is_mod_or_admin(is_admin)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?
|
||||
CommunityQuery {
|
||||
sort: (sort),
|
||||
listing_type: (listing_type),
|
||||
search_term: (Some(q)),
|
||||
local_user: (local_user.as_ref()),
|
||||
is_mod_or_admin: (is_admin),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?
|
||||
};
|
||||
|
||||
let q = data.q.clone();
|
||||
|
@ -170,31 +167,29 @@ pub async fn search(
|
|||
users = if community_or_creator_included {
|
||||
vec![]
|
||||
} else {
|
||||
PersonQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.search_term(Some(q))
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?
|
||||
PersonQuery {
|
||||
sort: (sort),
|
||||
search_term: (Some(q)),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?
|
||||
};
|
||||
}
|
||||
SearchType::Url => {
|
||||
posts = PostQuery::builder()
|
||||
.pool(&mut context.pool())
|
||||
.sort(sort)
|
||||
.listing_type(listing_type)
|
||||
.community_id(community_id)
|
||||
.creator_id(creator_id)
|
||||
.url_search(Some(q))
|
||||
.is_mod_or_admin(is_admin)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.build()
|
||||
.list()
|
||||
.await?;
|
||||
posts = PostQuery {
|
||||
sort: (sort),
|
||||
listing_type: (listing_type),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
url_search: (Some(q)),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -78,18 +78,20 @@ impl Collection for ApubCommunityModerators {
|
|||
|
||||
// Add new mods to database which have been added to moderators collection
|
||||
for mod_id in apub.ordered_items {
|
||||
let mod_user: ApubPerson = mod_id.dereference(data).await?;
|
||||
|
||||
if !current_moderators
|
||||
.iter()
|
||||
.map(|c| c.moderator.actor_id.clone())
|
||||
.any(|x| x == mod_user.actor_id)
|
||||
{
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: owner.id,
|
||||
person_id: mod_user.id,
|
||||
};
|
||||
CommunityModerator::join(&mut data.pool(), &community_moderator_form).await?;
|
||||
// Ignore errors as mod accounts might be deleted or instances unavailable.
|
||||
let mod_user: Option<ApubPerson> = mod_id.dereference(data).await.ok();
|
||||
if let Some(mod_user) = mod_user {
|
||||
if !current_moderators
|
||||
.iter()
|
||||
.map(|c| c.moderator.actor_id.clone())
|
||||
.any(|x| x == mod_user.actor_id)
|
||||
{
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
community_id: owner.id,
|
||||
person_id: mod_user.id,
|
||||
};
|
||||
CommunityModerator::join(&mut data.pool(), &community_moderator_form).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +102,9 @@ impl Collection for ApubCommunityModerators {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
objects::{
|
||||
|
|
|
@ -13,7 +13,7 @@ use activitypub_federation::{
|
|||
use actix_web::{web, web::Bytes, HttpRequest, HttpResponse};
|
||||
use http::StatusCode;
|
||||
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 serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
|
@ -88,12 +88,10 @@ pub(crate) async fn get_activity(
|
|||
info.id
|
||||
))?
|
||||
.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;
|
||||
if !activity.local {
|
||||
Err(err_object_not_local())
|
||||
} else if sensitive {
|
||||
if sensitive {
|
||||
Ok(HttpResponse::Forbidden().finish())
|
||||
} else {
|
||||
create_apub_response(&activity.data)
|
||||
|
|
|
@ -3,18 +3,12 @@ use activitypub_federation::config::{Data, UrlVerifier};
|
|||
use async_trait::async_trait;
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
activity::{Activity, ActivityInsertForm},
|
||||
instance::Instance,
|
||||
local_site::LocalSite,
|
||||
},
|
||||
traits::Crud,
|
||||
source::{activity::ReceivedActivity, instance::Instance, local_site::LocalSite},
|
||||
utils::{ActualDbPool, DbPool},
|
||||
};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult};
|
||||
use moka::future::Cache;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Serialize;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use url::Url;
|
||||
|
||||
|
@ -178,30 +172,16 @@ pub(crate) async fn check_apub_id_valid_with_strictness(
|
|||
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
|
||||
/// ensures that the same activity cannot be received more than once.
|
||||
#[tracing::instrument(skip(data, activity))]
|
||||
async fn insert_activity<T>(
|
||||
/// This ensures that the same activity doesnt get received and processed more than once, which
|
||||
/// would be a waste of resources.
|
||||
#[tracing::instrument(skip(data))]
|
||||
async fn insert_received_activity(
|
||||
ap_id: &Url,
|
||||
activity: &T,
|
||||
local: bool,
|
||||
sensitive: bool,
|
||||
data: &Data<LemmyContext>,
|
||||
) -> Result<(), LemmyError>
|
||||
where
|
||||
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?;
|
||||
) -> Result<(), LemmyError> {
|
||||
ReceivedActivity::create(&mut data.pool(), &ap_id.clone().into()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -179,6 +179,9 @@ impl Object for ApubComment {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
objects::{
|
||||
|
|
|
@ -204,6 +204,9 @@ impl ApubCommunity {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
objects::{instance::tests::parse_lemmy_instance, tests::init_context},
|
||||
|
|
|
@ -206,6 +206,9 @@ pub(crate) async fn remote_instance_inboxes(pool: &mut DbPool<'_>) -> Result<Vec
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{objects::tests::init_context, protocol::tests::file_to_json_object};
|
||||
use lemmy_db_schema::traits::Crud;
|
||||
|
|
|
@ -54,6 +54,9 @@ pub(crate) fn verify_is_remote_object(id: &Url, settings: &Settings) -> Result<(
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use activitypub_federation::config::{Data, FederationConfig};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::{context::LemmyContext, request::build_user_agent};
|
||||
|
|
|
@ -195,6 +195,9 @@ impl Actor for ApubPerson {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
objects::{
|
||||
|
|
|
@ -280,6 +280,9 @@ impl Object for ApubPost {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
objects::{
|
||||
|
|
|
@ -136,6 +136,9 @@ impl Object for ApubPrivateMessage {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
objects::{
|
||||
|
|
|
@ -3,6 +3,9 @@ pub mod undo_block_user;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
|
||||
tests::test_parse_lemmy_item,
|
||||
|
|
|
@ -7,6 +7,9 @@ pub mod update;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
activities::community::{
|
||||
announce::AnnounceActivity,
|
||||
|
|
|
@ -4,6 +4,9 @@ pub mod page;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
activities::create_or_update::{
|
||||
chat_message::CreateOrUpdateChatMessage,
|
||||
|
|
|
@ -4,6 +4,9 @@ pub mod undo_delete;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
activities::deletion::{delete::Delete, delete_user::DeleteUser, undo_delete::UndoDelete},
|
||||
tests::test_parse_lemmy_item,
|
||||
|
|
|
@ -4,6 +4,9 @@ pub mod undo_follow;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
activities::following::{accept::AcceptFollow, follow::Follow, undo_follow::UndoFollow},
|
||||
tests::test_parse_lemmy_item,
|
||||
|
|
|
@ -16,6 +16,9 @@ pub enum CreateOrUpdateType {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
activities::{
|
||||
community::announce::AnnounceActivity,
|
||||
|
|
|
@ -3,6 +3,9 @@ pub mod vote;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
activities::voting::{undo_vote::UndoVote, vote::Vote},
|
||||
tests::test_parse_lemmy_item,
|
||||
|
|
|
@ -6,6 +6,9 @@ pub(crate) mod group_outbox;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
collections::{
|
||||
empty_outbox::EmptyOutbox,
|
||||
|
|
|
@ -89,6 +89,9 @@ pub trait InCommunity {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use activitypub_federation::protocol::context::WithContext;
|
||||
use assert_json_diff::assert_json_include;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
|
|
|
@ -95,6 +95,9 @@ impl LanguageTag {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{
|
||||
objects::{
|
||||
chat_message::ChatMessage,
|
||||
|
|
|
@ -242,6 +242,9 @@ where
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::protocol::{objects::page::Page, tests::test_parse_lemmy_item};
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -14,9 +14,27 @@ path = "src/lib.rs"
|
|||
doctest = false
|
||||
|
||||
[features]
|
||||
full = ["diesel", "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"]
|
||||
full = [
|
||||
"diesel",
|
||||
"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]
|
||||
chrono = { workspace = true }
|
||||
|
@ -29,25 +47,33 @@ serde_json = { workspace = true, optional = true }
|
|||
activitypub_federation = { workspace = true, optional = true }
|
||||
lemmy_utils = { 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-enum = { 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 }
|
||||
regex = { workspace = true, optional = true }
|
||||
once_cell = { workspace = true, optional = true }
|
||||
diesel_ltree = { workspace = true, optional = true }
|
||||
typed-builder = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
deadpool = { version = "0.9.5", features = ["rt_tokio_1"], optional = true }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
rustls = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
tokio-postgres = { workspace = true }
|
||||
tokio-postgres-rustls = { workspace = true }
|
||||
tokio = { workspace = true, optional = true }
|
||||
tokio-postgres = { workspace = true, optional = true }
|
||||
tokio-postgres-rustls = { workspace = true, optional = true }
|
||||
rustls = { workspace = true, optional = true }
|
||||
uuid = { workspace = true, features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -35,6 +35,9 @@ impl CommentAggregates {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
aggregates::comment_aggregates::CommentAggregates,
|
||||
source::{
|
||||
|
|
|
@ -19,6 +19,9 @@ impl CommunityAggregates {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
aggregates::community_aggregates::CommunityAggregates,
|
||||
source::{
|
||||
|
|
|
@ -19,6 +19,9 @@ impl PersonAggregates {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
aggregates::person_aggregates::PersonAggregates,
|
||||
source::{
|
||||
|
|
|
@ -35,6 +35,9 @@ impl PostAggregates {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
aggregates::post_aggregates::PostAggregates,
|
||||
source::{
|
||||
|
|
|
@ -15,6 +15,9 @@ impl SiteAggregates {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
aggregates::site_aggregates::SiteAggregates,
|
||||
source::{
|
||||
|
|
|
@ -96,6 +96,8 @@ pub struct PostAggregates {
|
|||
pub featured_local: bool,
|
||||
pub hot_rank: i32,
|
||||
pub hot_rank_active: i32,
|
||||
pub community_id: CommunityId,
|
||||
pub creator_id: PersonId,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
||||
|
|
|
@ -1,139 +1,115 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::DbUrl,
|
||||
schema::activity::dsl::{activity, ap_id},
|
||||
source::activity::{Activity, ActivityInsertForm, ActivityUpdateForm},
|
||||
traits::Crud,
|
||||
source::activity::{ReceivedActivity, SentActivity, SentActivityForm},
|
||||
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;
|
||||
|
||||
#[async_trait]
|
||||
impl Crud for Activity {
|
||||
type InsertForm = ActivityInsertForm;
|
||||
type UpdateForm = ActivityUpdateForm;
|
||||
type IdType = i32;
|
||||
|
||||
async fn create(pool: &mut DbPool<'_>, new_activity: &Self::InsertForm) -> Result<Self, Error> {
|
||||
pub async fn create(pool: &mut DbPool<'_>, form: SentActivityForm) -> Result<Self, Error> {
|
||||
use crate::schema::sent_activity::dsl::sent_activity;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(activity)
|
||||
.values(new_activity)
|
||||
insert_into(sent_activity)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
activity_id: i32,
|
||||
new_activity: &Self::UpdateForm,
|
||||
) -> Result<Self, Error> {
|
||||
pub async fn read_from_apub_id(pool: &mut DbPool<'_>, object_id: &DbUrl) -> Result<Self, Error> {
|
||||
use crate::schema::sent_activity::dsl::{ap_id, sent_activity};
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(activity.find(activity_id))
|
||||
.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
|
||||
sent_activity
|
||||
.filter(ap_id.eq(object_id))
|
||||
.first::<Self>(conn)
|
||||
.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)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
newtypes::DbUrl,
|
||||
source::{
|
||||
activity::{Activity, ActivityInsertForm},
|
||||
instance::Instance,
|
||||
person::{Person, PersonInsertForm},
|
||||
},
|
||||
utils::build_db_pool_for_tests,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use crate::utils::build_db_pool_for_tests;
|
||||
use serde_json::json;
|
||||
use serial_test::serial;
|
||||
use url::Url;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_crud() {
|
||||
async fn receive_activity_duplicate() {
|
||||
let pool = &build_db_pool_for_tests().await;
|
||||
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())
|
||||
.await
|
||||
.unwrap();
|
||||
// inserting activity for first time
|
||||
let res = ReceivedActivity::create(pool, &ap_id).await;
|
||||
assert!(res.is_ok());
|
||||
|
||||
let creator_form = PersonInsertForm::builder()
|
||||
.name("activity_creator_ pm".into())
|
||||
.public_key("pubkey".to_string())
|
||||
.instance_id(inserted_instance.id)
|
||||
.build();
|
||||
let res = ReceivedActivity::create(pool, &ap_id).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
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(
|
||||
"https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c",
|
||||
)
|
||||
.unwrap()
|
||||
.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 form = SentActivityForm {
|
||||
ap_id: ap_id.clone(),
|
||||
data: data.clone(),
|
||||
sensitive,
|
||||
};
|
||||
|
||||
let inserted_activity = Activity::create(pool, &activity_form).await.unwrap();
|
||||
SentActivity::create(pool, form).await.unwrap();
|
||||
|
||||
let expected_activity = Activity {
|
||||
ap_id: ap_id_.clone(),
|
||||
id: inserted_activity.id,
|
||||
data: test_json,
|
||||
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);
|
||||
let res = SentActivity::read_from_apub_id(pool, &ap_id).await.unwrap();
|
||||
assert_eq!(res.ap_id, ap_id);
|
||||
assert_eq!(res.data, data);
|
||||
assert_eq!(res.sensitive, sensitive);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,25 +275,37 @@ impl CommunityLanguage {
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let form = lang_ids
|
||||
.into_iter()
|
||||
.map(|language_id| CommunityLanguageForm {
|
||||
community_id: for_community_id,
|
||||
language_id,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
conn
|
||||
.build_transaction()
|
||||
.run(|conn| {
|
||||
Box::pin(async move {
|
||||
use crate::schema::community_language::dsl::{community_id, community_language};
|
||||
use diesel::result::DatabaseErrorKind::UniqueViolation;
|
||||
// Clear the current languages
|
||||
delete(community_language.filter(community_id.eq(for_community_id)))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
for l in lang_ids {
|
||||
let form = CommunityLanguageForm {
|
||||
community_id: for_community_id,
|
||||
language_id: l,
|
||||
};
|
||||
insert_into(community_language)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await?;
|
||||
let insert_res = insert_into(community_language)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await;
|
||||
|
||||
if let Err(Error::DatabaseError(UniqueViolation, _info)) = insert_res {
|
||||
// race condition: this function was probably called simultaneously from another caller. ignore error
|
||||
// tracing::warn!("unique error: {_info:#?}");
|
||||
// _info.constraint_name() should be = "community_language_community_id_language_id_key"
|
||||
return Ok(());
|
||||
} else {
|
||||
insert_res?;
|
||||
}
|
||||
Ok(())
|
||||
}) as _
|
||||
|
@ -372,6 +384,9 @@ async fn convert_read_languages(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
impls::actor_language::{
|
||||
|
|
|
@ -50,6 +50,9 @@ impl CaptchaAnswer {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
source::captcha_answer::{CaptchaAnswer, CaptchaAnswerForm, CheckCaptchaAnswer},
|
||||
utils::build_db_pool_for_tests,
|
||||
|
|
|
@ -247,6 +247,9 @@ impl Saveable for CommentSaved {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
newtypes::LanguageId,
|
||||
source::{
|
||||
|
|
|
@ -74,6 +74,9 @@ impl CommentReply {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
|
||||
use crate::{
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue