mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-06-10 09:29:22 +00:00
Compare commits
128 commits
0.19.4-bet
...
main
Author | SHA1 | Date | |
---|---|---|---|
b2a480f55c | |||
9236cf7d21 | |||
b559e0206b | |||
f5f2b5ffc6 | |||
1e11faf741 | |||
5d31f0d516 | |||
844b84a01a | |||
b0447ad94d | |||
3d25322089 | |||
16a82862b8 | |||
79e6dbf0de | |||
fda5ce4482 | |||
e8cfb5665f | |||
bb94fb1c79 | |||
92214a9364 | |||
78ae874b89 | |||
a947474c64 | |||
8bf17946bd | |||
9ceb5b6386 | |||
aefb41b551 | |||
4195a9b5a1 | |||
69b4c6647b | |||
f7fe0d46fc | |||
609a6411a7 | |||
44666a34a2 | |||
6db878f761 | |||
6031709fcf | |||
4d9e38d875 | |||
6a6c915014 | |||
96b7afc0b1 | |||
d2083f79d9 | |||
e8a7bb07a3 | |||
91e57ff954 | |||
7d80a3c7d6 | |||
abcfa266af | |||
51970ffc81 | |||
fd6a1283a5 | |||
af034f3b5e | |||
0d5db29bc9 | |||
ec77c00ef8 | |||
69bdcb3069 | |||
6a6108ac55 | |||
b2c1a14234 | |||
d8dc38eb06 | |||
c96017c009 | |||
9aa565b559 | |||
7d7cd8ded4 | |||
943c31cc72 | |||
973f39601c | |||
a39c19c9db | |||
55f84dd38a | |||
6b46a70535 | |||
4ffaa93431 | |||
a0ad7806cb | |||
99aac07714 | |||
1a4aa3eaba | |||
93c9a5f2b1 | |||
9a9d518153 | |||
7fb03c502e | |||
49bb17b583 | |||
723cb549d4 | |||
8b6a4c060e | |||
cb80980027 | |||
c4fc3a8ede | |||
b4f9ef24a5 | |||
866d752a3c | |||
e0b1d0553d | |||
7c146272c3 | |||
cfdc732d3a | |||
522f974e30 | |||
b152be7951 | |||
485b0f1a54 | |||
7540b02723 | |||
7746db4169 | |||
db2ce81fc4 | |||
4175a1af80 | |||
563280456e | |||
2fecb7ecdf | |||
2c6f9c7fd5 | |||
e338e59868 | |||
b0caa85ed4 | |||
ad60d91f5c | |||
6423d2dde5 | |||
12163701e7 | |||
5c35e97a75 | |||
b05f221565 | |||
beec080274 | |||
492d8f1b01 | |||
d3737d4453 | |||
b459949f57 | |||
93f5df2d2a | |||
cf426493e1 | |||
8e3ff0408e | |||
66e06b3952 | |||
6b9d9dfaa5 | |||
0eaf8d33e7 | |||
c31a29ec7f | |||
80635c9e24 | |||
95d75e07b2 | |||
efbfdc9340 | |||
1ae3aab764 | |||
f68881c552 | |||
2ba1ba88b8 | |||
079fa0b8f6 | |||
b0a740d5c5 | |||
ee46242a43 | |||
4ba6221e04 | |||
31829b6c05 | |||
b0370ae2fd | |||
6efab9aab1 | |||
d075acce43 | |||
3a0c1dca90 | |||
0f6b13a4ec | |||
64760ec960 | |||
555f789269 | |||
5dea21d531 | |||
d5622a65f8 | |||
9059de8569 | |||
0203b62a6d | |||
99d585b7be | |||
b4670988b5 | |||
1d0a6ac08f | |||
8e54a4a6cc | |||
a14ebefd24 | |||
705e86eb4c | |||
a1d632e582 | |||
087684658a | |||
aaaa362b98 |
|
@ -3,3 +3,5 @@ edition = "2021"
|
|||
imports_layout = "HorizontalVertical"
|
||||
imports_granularity = "Crate"
|
||||
group_imports = "One"
|
||||
wrap_comments = true
|
||||
comment_width = 100
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
||||
|
||||
variables:
|
||||
- &rust_image "rust:1.76"
|
||||
- &rust_image "rust:1.78"
|
||||
- &rust_nightly_image "rustlang/rust:nightly"
|
||||
- &install_pnpm "corepack enable pnpm"
|
||||
- &slow_check_paths
|
||||
- event: pull_request
|
||||
|
@ -24,15 +25,17 @@ variables:
|
|||
"diesel.toml",
|
||||
".gitmodules",
|
||||
]
|
||||
|
||||
# Broken for cron jobs currently, see
|
||||
# https://github.com/woodpecker-ci/woodpecker/issues/1716
|
||||
# clone:
|
||||
# git:
|
||||
# image: woodpeckerci/plugin-git
|
||||
# settings:
|
||||
# recursive: true
|
||||
# submodule_update_remote: true
|
||||
- install_binstall: &install_binstall
|
||||
- wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- cp cargo-binstall /usr/local/cargo/bin
|
||||
- install_diesel_cli: &install_diesel_cli
|
||||
- apt update && apt install -y lsb-release build-essential
|
||||
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
- apt update && apt install -y postgresql-client-16
|
||||
- cargo install diesel_cli --no-default-features --features postgres
|
||||
- export PATH="$CARGO_HOME/bin:$PATH"
|
||||
|
||||
steps:
|
||||
prepare_repo:
|
||||
|
@ -42,7 +45,7 @@ steps:
|
|||
- git submodule init
|
||||
- git submodule update
|
||||
when:
|
||||
- event: pull_request
|
||||
- event: [pull_request, tag]
|
||||
|
||||
prettier_check:
|
||||
image: tmknom/prettier:3.0.0
|
||||
|
@ -59,14 +62,14 @@ steps:
|
|||
- event: pull_request
|
||||
|
||||
sql_fmt:
|
||||
image: backplane/pgformatter:latest
|
||||
image: backplane/pgformatter
|
||||
commands:
|
||||
- ./scripts/sql_format_check.sh
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_fmt:
|
||||
image: rustlang/rust:nightly
|
||||
image: *rust_nightly_image
|
||||
environment:
|
||||
# store cargo data in repo folder so that it gets cached between steps
|
||||
CARGO_HOME: .cargo_home
|
||||
|
@ -77,11 +80,9 @@ steps:
|
|||
- event: pull_request
|
||||
|
||||
cargo_machete:
|
||||
image: rustlang/rust:nightly
|
||||
image: *rust_nightly_image
|
||||
commands:
|
||||
- wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- cp cargo-binstall /usr/local/cargo/bin
|
||||
- <<: *install_binstall
|
||||
- cargo binstall -y cargo-machete
|
||||
- cargo machete
|
||||
when:
|
||||
|
@ -133,26 +134,17 @@ steps:
|
|||
when: *slow_check_paths
|
||||
|
||||
check_diesel_schema:
|
||||
image: willsquire/diesel-cli
|
||||
image: *rust_image
|
||||
environment:
|
||||
CARGO_HOME: .cargo_home
|
||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
commands:
|
||||
- <<: *install_diesel_cli
|
||||
- diesel migration run
|
||||
- diesel print-schema --config-file=diesel.toml > tmp.schema
|
||||
- diff tmp.schema crates/db_schema/src/schema.rs
|
||||
when: *slow_check_paths
|
||||
|
||||
check_diesel_migration_revertable:
|
||||
image: willsquire/diesel-cli
|
||||
environment:
|
||||
CARGO_HOME: .cargo_home
|
||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
commands:
|
||||
- diesel migration run
|
||||
- diesel migration redo
|
||||
when: *slow_check_paths
|
||||
|
||||
check_db_perf_tool:
|
||||
image: *rust_image
|
||||
environment:
|
||||
|
@ -194,6 +186,44 @@ steps:
|
|||
- cargo test --workspace --no-fail-fast
|
||||
when: *slow_check_paths
|
||||
|
||||
check_diesel_migration:
|
||||
# TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server
|
||||
image: *rust_image
|
||||
environment:
|
||||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
RUST_BACKTRACE: "1"
|
||||
CARGO_HOME: .cargo_home
|
||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
PGUSER: lemmy
|
||||
PGPASSWORD: password
|
||||
PGHOST: database
|
||||
PGDATABASE: lemmy
|
||||
commands:
|
||||
# Install diesel_cli
|
||||
- <<: *install_diesel_cli
|
||||
# Run all migrations
|
||||
- diesel migration run
|
||||
# Dump schema to before.sqldump (PostgreSQL apt repo is used to prevent pg_dump version mismatch error)
|
||||
- apt update && apt install -y lsb-release
|
||||
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
- apt update && apt install -y postgresql-client-16
|
||||
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
|
||||
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f before.sqldump
|
||||
# Make sure that the newest migration is revertable without the `r` schema
|
||||
- diesel migration redo
|
||||
# Run schema setup twice, which fails on the 2nd time if `DROP SCHEMA IF EXISTS r CASCADE` drops the wrong things
|
||||
- alias lemmy_schema_setup="target/lemmy_server --disable-scheduled-tasks --disable-http-server --disable-activity-sending"
|
||||
- lemmy_schema_setup
|
||||
- lemmy_schema_setup
|
||||
# Make sure that the newest migration is revertable with the `r` schema
|
||||
- diesel migration redo
|
||||
# Check for changes in the schema, which would be caused by an incorrect migration
|
||||
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
|
||||
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f after.sqldump
|
||||
- diff before.sqldump after.sqldump
|
||||
when: *slow_check_paths
|
||||
|
||||
run_federation_tests:
|
||||
image: node:20-bookworm-slim
|
||||
environment:
|
||||
|
@ -248,11 +278,11 @@ steps:
|
|||
publish_to_crates_io:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- 'echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"'
|
||||
- cargo install cargo-workspaces
|
||||
- <<: *install_binstall
|
||||
# Install cargo-workspaces
|
||||
- cargo binstall -y 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}"
|
||||
- cargo workspaces publish --token "$CARGO_API_TOKEN" --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
|
||||
secrets: [cargo_api_token]
|
||||
when:
|
||||
- event: tag
|
||||
|
@ -263,7 +293,7 @@ steps:
|
|||
- apk add curl
|
||||
- "curl -d'Lemmy CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
|
||||
when:
|
||||
- event: pull_request
|
||||
- event: [pull_request, tag]
|
||||
status: failure
|
||||
|
||||
notify_on_tag_deploy:
|
||||
|
|
2378
Cargo.lock
generated
2378
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
104
Cargo.toml
104
Cargo.toml
|
@ -1,5 +1,5 @@
|
|||
[workspace.package]
|
||||
version = "0.19.4-beta.1"
|
||||
version = "0.19.4"
|
||||
edition = "2021"
|
||||
description = "A link aggregator for the fediverse"
|
||||
license = "AGPL-3.0"
|
||||
|
@ -67,8 +67,8 @@ members = [
|
|||
|
||||
[workspace.lints.clippy]
|
||||
cast_lossless = "deny"
|
||||
complexity = "deny"
|
||||
correctness = "deny"
|
||||
complexity = { level = "deny", priority = -1 }
|
||||
correctness = { level = "deny", priority = -1 }
|
||||
dbg_macro = "deny"
|
||||
explicit_into_iter_loop = "deny"
|
||||
explicit_iter_loop = "deny"
|
||||
|
@ -79,73 +79,74 @@ inefficient_to_string = "deny"
|
|||
items-after-statements = "deny"
|
||||
manual_string_new = "deny"
|
||||
needless_collect = "deny"
|
||||
perf = "deny"
|
||||
perf = { level = "deny", priority = -1 }
|
||||
redundant_closure_for_method_calls = "deny"
|
||||
style = "deny"
|
||||
suspicious = "deny"
|
||||
style = { level = "deny", priority = -1 }
|
||||
suspicious = { level = "deny", priority = -1 }
|
||||
uninlined_format_args = "allow"
|
||||
unused_self = "deny"
|
||||
unwrap_used = "deny"
|
||||
|
||||
[workspace.dependencies]
|
||||
lemmy_api = { version = "=0.19.4-beta.1", path = "./crates/api" }
|
||||
lemmy_api_crud = { version = "=0.19.4-beta.1", path = "./crates/api_crud" }
|
||||
lemmy_apub = { version = "=0.19.4-beta.1", path = "./crates/apub" }
|
||||
lemmy_utils = { version = "=0.19.4-beta.1", path = "./crates/utils", default-features = false }
|
||||
lemmy_db_schema = { version = "=0.19.4-beta.1", path = "./crates/db_schema" }
|
||||
lemmy_api_common = { version = "=0.19.4-beta.1", path = "./crates/api_common" }
|
||||
lemmy_routes = { version = "=0.19.4-beta.1", path = "./crates/routes" }
|
||||
lemmy_db_views = { version = "=0.19.4-beta.1", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.19.4-beta.1", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.19.4-beta.1", path = "./crates/db_views_moderator" }
|
||||
activitypub_federation = { version = "0.5.2", default-features = false, features = [
|
||||
lemmy_api = { version = "=0.19.4", path = "./crates/api" }
|
||||
lemmy_api_crud = { version = "=0.19.4", path = "./crates/api_crud" }
|
||||
lemmy_apub = { version = "=0.19.4", path = "./crates/apub" }
|
||||
lemmy_utils = { version = "=0.19.4", path = "./crates/utils", default-features = false }
|
||||
lemmy_db_schema = { version = "=0.19.4", path = "./crates/db_schema" }
|
||||
lemmy_api_common = { version = "=0.19.4", path = "./crates/api_common" }
|
||||
lemmy_routes = { version = "=0.19.4", path = "./crates/routes" }
|
||||
lemmy_db_views = { version = "=0.19.4", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.19.4", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.19.4", path = "./crates/db_views_moderator" }
|
||||
lemmy_federate = { version = "=0.19.4", path = "./crates/federate" }
|
||||
activitypub_federation = { version = "0.5.6", default-features = false, features = [
|
||||
"actix-web",
|
||||
] }
|
||||
diesel = "2.1.4"
|
||||
diesel = "2.1.6"
|
||||
diesel_migrations = "2.1.0"
|
||||
diesel-async = "0.4.1"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_with = "3.7.0"
|
||||
actix-web = { version = "4.5.1", default-features = false, features = [
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
serde_with = "3.8.1"
|
||||
actix-web = { version = "4.6.0", default-features = false, features = [
|
||||
"macros",
|
||||
"rustls",
|
||||
"rustls-0_23",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
"cookies",
|
||||
] }
|
||||
tracing = "0.1.40"
|
||||
tracing-actix-web = { version = "0.7.10", default-features = false }
|
||||
tracing-actix-web = { version = "0.7.11", default-features = false }
|
||||
tracing-error = "0.2.0"
|
||||
tracing-log = "0.2.0"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
reqwest = { version = "0.11.26", features = ["json", "blocking", "gzip"] }
|
||||
reqwest-middleware = "0.2.4"
|
||||
reqwest-tracing = "0.4.7"
|
||||
reqwest = { version = "0.11.27", features = ["json", "blocking", "gzip"] }
|
||||
reqwest-middleware = "0.2.5"
|
||||
reqwest-tracing = "0.4.8"
|
||||
clokwerk = "0.4.0"
|
||||
doku = { version = "0.21.1", features = ["url-2"] }
|
||||
bcrypt = "0.15.0"
|
||||
chrono = { version = "0.4.35", features = ["serde"], default-features = false }
|
||||
serde_json = { version = "1.0.114", features = ["preserve_order"] }
|
||||
base64 = "0.21.7"
|
||||
uuid = { version = "1.7.0", features = ["serde", "v4"] }
|
||||
async-trait = "0.1.77"
|
||||
bcrypt = "0.15.1"
|
||||
chrono = { version = "0.4.38", features = ["serde"], default-features = false }
|
||||
serde_json = { version = "1.0.117", features = ["preserve_order"] }
|
||||
base64 = "0.22.1"
|
||||
uuid = { version = "1.8.0", features = ["serde", "v4"] }
|
||||
async-trait = "0.1.80"
|
||||
captcha = "0.0.9"
|
||||
anyhow = { version = "1.0.81", features = [
|
||||
anyhow = { version = "1.0.86", features = [
|
||||
"backtrace",
|
||||
] } # backtrace is on by default on nightly, but not stable rust
|
||||
diesel_ltree = "0.3.1"
|
||||
typed-builder = "0.18.1"
|
||||
serial_test = "2.0.0"
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
regex = "1.10.3"
|
||||
typed-builder = "0.18.2"
|
||||
serial_test = "3.1.1"
|
||||
tokio = { version = "1.38.0", features = ["full"] }
|
||||
regex = "1.10.4"
|
||||
once_cell = "1.19.0"
|
||||
diesel-derive-newtype = "2.1.0"
|
||||
diesel-derive-newtype = "2.1.2"
|
||||
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||
strum = "0.25.0"
|
||||
strum_macros = "0.25.3"
|
||||
itertools = "0.12.1"
|
||||
strum = "0.26.2"
|
||||
strum_macros = "0.26.4"
|
||||
itertools = "0.13.0"
|
||||
futures = "0.3.30"
|
||||
http = "0.2.12"
|
||||
rosetta-i18n = "0.1.3"
|
||||
|
@ -156,16 +157,17 @@ ts-rs = { version = "7.1.1", features = [
|
|||
"chrono-impl",
|
||||
"no-serde-warnings",
|
||||
] }
|
||||
rustls = { version = "0.21.10", features = ["dangerous_configuration"] }
|
||||
rustls = { version = "0.23.9", features = ["ring"] }
|
||||
futures-util = "0.3.30"
|
||||
tokio-postgres = "0.7.10"
|
||||
tokio-postgres-rustls = "0.10.0"
|
||||
tokio-postgres-rustls = "0.12.0"
|
||||
urlencoding = "2.1.3"
|
||||
enum-map = "2.7"
|
||||
moka = { version = "0.12.5", features = ["future"] }
|
||||
moka = { version = "0.12.7", features = ["future"] }
|
||||
i-love-jesus = { version = "0.1.0" }
|
||||
clap = { version = "4.5.2", features = ["derive"] }
|
||||
clap = { version = "4.5.6", features = ["derive", "env"] }
|
||||
pretty_assertions = "1.4.0"
|
||||
derive-new = "0.6.0"
|
||||
|
||||
[dependencies]
|
||||
lemmy_api = { workspace = true }
|
||||
|
@ -175,7 +177,7 @@ lemmy_utils = { workspace = true }
|
|||
lemmy_db_schema = { workspace = true }
|
||||
lemmy_api_common = { workspace = true }
|
||||
lemmy_routes = { workspace = true }
|
||||
lemmy_federate = { version = "0.19.4-beta.1", path = "crates/federate" }
|
||||
lemmy_federate = { workspace = true }
|
||||
activitypub_federation = { workspace = true }
|
||||
diesel = { workspace = true }
|
||||
diesel-async = { workspace = true }
|
||||
|
@ -193,17 +195,17 @@ clokwerk = { workspace = true }
|
|||
serde_json = { workspace = true }
|
||||
tracing-opentelemetry = { workspace = true, optional = true }
|
||||
opentelemetry = { workspace = true, optional = true }
|
||||
console-subscriber = { version = "0.1.10", optional = true }
|
||||
console-subscriber = { version = "0.2.0", optional = true }
|
||||
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
||||
pict-rs = { version = "0.5.9", optional = true }
|
||||
pict-rs = { version = "0.5.15", optional = true }
|
||||
tokio.workspace = true
|
||||
actix-cors = "0.6.5"
|
||||
actix-cors = "0.7.0"
|
||||
futures-util = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
prometheus = { version = "0.13.3", features = ["process"] }
|
||||
prometheus = { version = "0.13.4", features = ["process"] }
|
||||
serial_test = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
actix-web-prom = "0.7.0"
|
||||
actix-web-prom = "0.8.0"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
|
1
api_tests/.npmrc
Normal file
1
api_tests/.npmrc
Normal file
|
@ -0,0 +1 @@
|
|||
package-manager-strict=false
|
|
@ -6,10 +6,11 @@
|
|||
"repository": "https://github.com/LemmyNet/lemmy",
|
||||
"author": "Dessalines",
|
||||
"license": "AGPL-3.0",
|
||||
"packageManager": "pnpm@9.1.4",
|
||||
"scripts": {
|
||||
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
|
||||
"fix": "prettier --write src && eslint --fix src",
|
||||
"api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts && jest -i image.spec.ts",
|
||||
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
|
||||
"api-test-follow": "jest -i follow.spec.ts",
|
||||
"api-test-comment": "jest -i comment.spec.ts",
|
||||
"api-test-post": "jest -i post.spec.ts",
|
||||
|
@ -20,16 +21,16 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.11.27",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
"@typescript-eslint/parser": "^7.2.0",
|
||||
"@types/node": "^20.12.4",
|
||||
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
||||
"@typescript-eslint/parser": "^7.5.0",
|
||||
"download-file-sync": "^1.0.4",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.5.0",
|
||||
"lemmy-js-client": "0.19.4-alpha.13",
|
||||
"lemmy-js-client": "0.19.4",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.4.2"
|
||||
"typescript": "^5.4.4"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,19 +3,19 @@
|
|||
# it is expected that this script is called by run-federation-test.sh script.
|
||||
set -e
|
||||
|
||||
if [ -n "$LEMMY_LOG_LEVEL" ];
|
||||
if [ -z "$LEMMY_LOG_LEVEL" ];
|
||||
then
|
||||
LEMMY_LOG_LEVEL=warn
|
||||
LEMMY_LOG_LEVEL=info
|
||||
fi
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
#export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
|
||||
export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
|
||||
|
||||
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
|
||||
|
||||
# pictrs setup
|
||||
if [ ! -f "api_tests/pict-rs" ]; then
|
||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.0-beta.2/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.13/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||
chmod +x api_tests/pict-rs
|
||||
fi
|
||||
./api_tests/pict-rs \
|
||||
|
|
|
@ -37,15 +37,15 @@ import {
|
|||
followCommunity,
|
||||
blockCommunity,
|
||||
delay,
|
||||
saveUserSettings,
|
||||
} from "./shared";
|
||||
import { CommentView, CommunityView } from "lemmy-js-client";
|
||||
import { CommentView, CommunityView, SaveUserSettings } from "lemmy-js-client";
|
||||
|
||||
let betaCommunity: CommunityView | undefined;
|
||||
let postOnAlphaRes: PostResponse;
|
||||
|
||||
beforeAll(async () => {
|
||||
await setupLogins();
|
||||
await unfollows();
|
||||
await Promise.all([followBeta(alpha), followBeta(gamma)]);
|
||||
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||
if (betaCommunity) {
|
||||
|
@ -53,9 +53,7 @@ beforeAll(async () => {
|
|||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
unfollows();
|
||||
});
|
||||
afterAll(unfollows);
|
||||
|
||||
function assertCommentFederation(
|
||||
commentOne?: CommentView,
|
||||
|
@ -446,6 +444,59 @@ test("Reply to a comment from another instance, get notification", async () => {
|
|||
assertCommentFederation(alphaReply, replyRes.comment_view);
|
||||
});
|
||||
|
||||
test("Bot reply notifications are filtered when bots are hidden", async () => {
|
||||
const newAlphaBot = await registerUser(alpha, alphaUrl);
|
||||
let form: SaveUserSettings = {
|
||||
bot_account: true,
|
||||
};
|
||||
await saveUserSettings(newAlphaBot, form);
|
||||
|
||||
const alphaCommunity = (
|
||||
await resolveCommunity(alpha, "!main@lemmy-alpha:8541")
|
||||
).community;
|
||||
|
||||
if (!alphaCommunity) {
|
||||
throw "Missing alpha community";
|
||||
}
|
||||
|
||||
await alpha.markAllAsRead();
|
||||
form = {
|
||||
show_bot_accounts: false,
|
||||
};
|
||||
await saveUserSettings(alpha, form);
|
||||
const postOnAlphaRes = await createPost(alpha, alphaCommunity.community.id);
|
||||
|
||||
// Bot reply to alpha's post
|
||||
let commentRes = await createComment(
|
||||
newAlphaBot,
|
||||
postOnAlphaRes.post_view.post.id,
|
||||
);
|
||||
expect(commentRes).toBeDefined();
|
||||
|
||||
let alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||
expect(alphaUnreadCountRes.replies).toBe(0);
|
||||
|
||||
let alphaUnreadRepliesRes = await getReplies(alpha, true);
|
||||
expect(alphaUnreadRepliesRes.replies.length).toBe(0);
|
||||
|
||||
// This both restores the original state that may be expected by other tests
|
||||
// implicitly and is used by the next steps to ensure replies are still
|
||||
// returned when a user later decides to show bot accounts again.
|
||||
form = {
|
||||
show_bot_accounts: true,
|
||||
};
|
||||
await saveUserSettings(alpha, form);
|
||||
|
||||
alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||
expect(alphaUnreadCountRes.replies).toBe(1);
|
||||
|
||||
alphaUnreadRepliesRes = await getReplies(alpha, true);
|
||||
expect(alphaUnreadRepliesRes.replies.length).toBe(1);
|
||||
expect(alphaUnreadRepliesRes.replies[0].comment.id).toBe(
|
||||
commentRes.comment_view.comment.id,
|
||||
);
|
||||
});
|
||||
|
||||
test("Mention beta from alpha", async () => {
|
||||
if (!betaCommunity) throw Error("no community");
|
||||
const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
|
||||
|
|
|
@ -31,10 +31,12 @@ import {
|
|||
searchPostLocal,
|
||||
longDelay,
|
||||
editCommunity,
|
||||
unfollows,
|
||||
} from "./shared";
|
||||
import { EditCommunity, EditSite } from "lemmy-js-client";
|
||||
|
||||
beforeAll(setupLogins);
|
||||
afterAll(unfollows);
|
||||
|
||||
function assertCommunityFederation(
|
||||
communityOne?: CommunityView,
|
||||
|
@ -240,7 +242,7 @@ test("Admin actions in remote community are not federated to origin", async () =
|
|||
);
|
||||
expect(banRes.banned).toBe(true);
|
||||
|
||||
// ban doesnt federate to community's origin instance alpha
|
||||
// ban doesn't federate to community's origin instance alpha
|
||||
let alphaPost = (await resolvePost(alpha, gammaPost.post)).post;
|
||||
expect(alphaPost?.creator_banned_from_community).toBe(false);
|
||||
|
||||
|
@ -378,8 +380,8 @@ test("User blocks instance, communities are hidden", async () => {
|
|||
test("Community follower count is federated", async () => {
|
||||
// Follow the beta community from alpha
|
||||
let community = await createCommunity(beta);
|
||||
let community_id = community.community_view.community.actor_id;
|
||||
let resolved = await resolveCommunity(alpha, community_id);
|
||||
let communityActorId = community.community_view.community.actor_id;
|
||||
let resolved = await resolveCommunity(alpha, communityActorId);
|
||||
if (!resolved.community) {
|
||||
throw "Missing beta community";
|
||||
}
|
||||
|
@ -387,7 +389,7 @@ test("Community follower count is federated", async () => {
|
|||
await followCommunity(alpha, true, resolved.community.community.id);
|
||||
let followed = (
|
||||
await waitUntil(
|
||||
() => resolveCommunity(alpha, community_id),
|
||||
() => resolveCommunity(alpha, communityActorId),
|
||||
c => c.community?.subscribed === "Subscribed",
|
||||
)
|
||||
).community;
|
||||
|
@ -396,7 +398,7 @@ test("Community follower count is federated", async () => {
|
|||
expect(followed?.counts.subscribers).toBe(1);
|
||||
|
||||
// Follow the community from gamma
|
||||
resolved = await resolveCommunity(gamma, community_id);
|
||||
resolved = await resolveCommunity(gamma, communityActorId);
|
||||
if (!resolved.community) {
|
||||
throw "Missing beta community";
|
||||
}
|
||||
|
@ -404,7 +406,7 @@ test("Community follower count is federated", async () => {
|
|||
await followCommunity(gamma, true, resolved.community.community.id);
|
||||
followed = (
|
||||
await waitUntil(
|
||||
() => resolveCommunity(gamma, community_id),
|
||||
() => resolveCommunity(gamma, communityActorId),
|
||||
c => c.community?.subscribed === "Subscribed",
|
||||
)
|
||||
).community;
|
||||
|
@ -413,7 +415,7 @@ test("Community follower count is federated", async () => {
|
|||
expect(followed?.counts?.subscribers).toBe(2);
|
||||
|
||||
// Follow the community from delta
|
||||
resolved = await resolveCommunity(delta, community_id);
|
||||
resolved = await resolveCommunity(delta, communityActorId);
|
||||
if (!resolved.community) {
|
||||
throw "Missing beta community";
|
||||
}
|
||||
|
@ -421,7 +423,7 @@ test("Community follower count is federated", async () => {
|
|||
await followCommunity(delta, true, resolved.community.community.id);
|
||||
followed = (
|
||||
await waitUntil(
|
||||
() => resolveCommunity(delta, community_id),
|
||||
() => resolveCommunity(delta, communityActorId),
|
||||
c => c.community?.subscribed === "Subscribed",
|
||||
)
|
||||
).community;
|
||||
|
@ -450,7 +452,7 @@ test("Dont receive community activities after unsubscribe", async () => {
|
|||
);
|
||||
expect(communityRes1.community_view.counts.subscribers).toBe(2);
|
||||
|
||||
// temporarily block alpha, so that it doesnt know about unfollow
|
||||
// temporarily block alpha, so that it doesn't know about unfollow
|
||||
let editSiteForm: EditSite = {};
|
||||
editSiteForm.allowed_instances = ["lemmy-epsilon"];
|
||||
await beta.editSite(editSiteForm);
|
||||
|
@ -511,7 +513,7 @@ test("Fetch community, includes posts", async () => {
|
|||
expect(post_listing.posts[0].post.ap_id).toBe(postRes.post_view.post.ap_id);
|
||||
});
|
||||
|
||||
test("Content in local-only community doesnt federate", async () => {
|
||||
test("Content in local-only community doesn't federate", async () => {
|
||||
// create a community and set it local-only
|
||||
let communityRes = (await createCommunity(alpha)).community_view.community;
|
||||
let form: EditCommunity = {
|
||||
|
|
|
@ -15,9 +15,7 @@ import {
|
|||
|
||||
beforeAll(setupLogins);
|
||||
|
||||
afterAll(() => {
|
||||
unfollows();
|
||||
});
|
||||
afterAll(unfollows);
|
||||
|
||||
test("Follow local community", async () => {
|
||||
let user = await registerUser(beta, betaUrl);
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
createCommunity,
|
||||
createPost,
|
||||
deleteAllImages,
|
||||
delta,
|
||||
epsilon,
|
||||
followCommunity,
|
||||
gamma,
|
||||
|
@ -28,20 +27,25 @@ import {
|
|||
setupLogins,
|
||||
waitForPost,
|
||||
unfollows,
|
||||
getPost,
|
||||
waitUntil,
|
||||
createPostWithThumbnail,
|
||||
sampleImage,
|
||||
sampleSite,
|
||||
} from "./shared";
|
||||
const downloadFileSync = require("download-file-sync");
|
||||
|
||||
beforeAll(setupLogins);
|
||||
|
||||
afterAll(() => {
|
||||
unfollows();
|
||||
afterAll(async () => {
|
||||
await Promise.all([unfollows(), deleteAllImages(alpha)]);
|
||||
});
|
||||
|
||||
test("Upload image and delete it", async () => {
|
||||
// Before running this test, you need to delete all previous images in the DB
|
||||
await deleteAllImages(alpha);
|
||||
|
||||
// Upload test image. We use a simple string buffer as pictrs doesnt require an actual image
|
||||
// Upload test image. We use a simple string buffer as pictrs doesn't require an actual image
|
||||
// in testing mode.
|
||||
const upload_form: UploadImage = {
|
||||
image: Buffer.from("test"),
|
||||
|
@ -71,9 +75,14 @@ test("Upload image and delete it", async () => {
|
|||
|
||||
// The deleteUrl is a combination of the endpoint, delete token, and alias
|
||||
let firstImage = listMediaRes.images[0];
|
||||
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.pictrs_delete_token}/${firstImage.pictrs_alias}`;
|
||||
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.local_image.pictrs_delete_token}/${firstImage.local_image.pictrs_alias}`;
|
||||
expect(deleteUrl).toBe(upload.delete_url);
|
||||
|
||||
// Make sure the uploader is correct
|
||||
expect(firstImage.person.actor_id).toBe(
|
||||
`http://lemmy-alpha:8541/u/lemmy_alpha`,
|
||||
);
|
||||
|
||||
// delete image
|
||||
const delete_form: DeleteImage = {
|
||||
token: upload.files![0].delete_token,
|
||||
|
@ -153,7 +162,6 @@ test("Purge post, linked image removed", async () => {
|
|||
expect(post.post_view.post.url).toBe(upload.url);
|
||||
|
||||
// purge post
|
||||
|
||||
const purgeForm: PurgePost = {
|
||||
post_id: post.post_view.post.id,
|
||||
};
|
||||
|
@ -165,48 +173,94 @@ test("Purge post, linked image removed", async () => {
|
|||
expect(content2).toBe("");
|
||||
});
|
||||
|
||||
test("Images in remote post are proxied if setting enabled", async () => {
|
||||
let user = await registerUser(beta, betaUrl);
|
||||
test("Images in remote image post are proxied if setting enabled", async () => {
|
||||
let community = await createCommunity(gamma);
|
||||
|
||||
const upload_form: UploadImage = {
|
||||
image: Buffer.from("test"),
|
||||
};
|
||||
const upload = await user.uploadImage(upload_form);
|
||||
let post = await createPost(
|
||||
let postRes = await createPost(
|
||||
gamma,
|
||||
community.community_view.community.id,
|
||||
upload.url,
|
||||
"![](http://example.com/image2.png)",
|
||||
sampleImage,
|
||||
`![](${sampleImage})`,
|
||||
);
|
||||
expect(post.post_view.post).toBeDefined();
|
||||
const post = postRes.post_view.post;
|
||||
expect(post).toBeDefined();
|
||||
|
||||
// remote image gets proxied after upload
|
||||
expect(
|
||||
post.post_view.post.url?.startsWith(
|
||||
post.thumbnail_url?.startsWith(
|
||||
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
||||
),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
post.post_view.post.body?.startsWith(
|
||||
"![](http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
||||
),
|
||||
post.body?.startsWith("![](http://lemmy-gamma:8561/api/v3/image_proxy?url"),
|
||||
).toBeTruthy();
|
||||
|
||||
let epsilonPost = await resolvePost(epsilon, post.post_view.post);
|
||||
expect(epsilonPost.post).toBeDefined();
|
||||
// Make sure that it ends with jpg, to be sure its an image
|
||||
expect(post.thumbnail_url?.endsWith(".jpg")).toBeTruthy();
|
||||
|
||||
let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post);
|
||||
expect(epsilonPostRes.post).toBeDefined();
|
||||
|
||||
// Fetch the post again, the metadata should be backgrounded now
|
||||
// Wait for the metadata to get fetched, since this is backgrounded now
|
||||
let epsilonPostRes2 = await waitUntil(
|
||||
() => getPost(epsilon, epsilonPostRes.post!.post.id),
|
||||
p => p.post_view.post.thumbnail_url != undefined,
|
||||
);
|
||||
const epsilonPost = epsilonPostRes2.post_view.post;
|
||||
|
||||
// remote image gets proxied after federation
|
||||
expect(
|
||||
epsilonPost.post!.post.url?.startsWith(
|
||||
epsilonPost.thumbnail_url?.startsWith(
|
||||
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
||||
),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
epsilonPost.post!.post.body?.startsWith(
|
||||
epsilonPost.body?.startsWith(
|
||||
"![](http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
||||
),
|
||||
).toBeTruthy();
|
||||
|
||||
// Make sure that it ends with jpg, to be sure its an image
|
||||
expect(epsilonPost.thumbnail_url?.endsWith(".jpg")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Thumbnail of remote image link is proxied if setting enabled", async () => {
|
||||
let community = await createCommunity(gamma);
|
||||
let postRes = await createPost(
|
||||
gamma,
|
||||
community.community_view.community.id,
|
||||
// The sample site metadata thumbnail ends in png
|
||||
sampleSite,
|
||||
);
|
||||
const post = postRes.post_view.post;
|
||||
expect(post).toBeDefined();
|
||||
|
||||
// remote image gets proxied after upload
|
||||
expect(
|
||||
post.thumbnail_url?.startsWith(
|
||||
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
||||
),
|
||||
).toBeTruthy();
|
||||
|
||||
// Make sure that it ends with png, to be sure its an image
|
||||
expect(post.thumbnail_url?.endsWith(".png")).toBeTruthy();
|
||||
|
||||
let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post);
|
||||
expect(epsilonPostRes.post).toBeDefined();
|
||||
|
||||
let epsilonPostRes2 = await waitUntil(
|
||||
() => getPost(epsilon, epsilonPostRes.post!.post.id),
|
||||
p => p.post_view.post.thumbnail_url != undefined,
|
||||
);
|
||||
const epsilonPost = epsilonPostRes2.post_view.post;
|
||||
|
||||
expect(
|
||||
epsilonPost.thumbnail_url?.startsWith(
|
||||
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
||||
),
|
||||
).toBeTruthy();
|
||||
|
||||
// Make sure that it ends with png, to be sure its an image
|
||||
expect(epsilonPost.thumbnail_url?.endsWith(".png")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("No image proxying if setting is disabled", async () => {
|
||||
|
@ -226,15 +280,15 @@ test("No image proxying if setting is disabled", async () => {
|
|||
alpha,
|
||||
community.community_view.community.id,
|
||||
upload.url,
|
||||
"![](http://example.com/image2.png)",
|
||||
`![](${sampleImage})`,
|
||||
);
|
||||
expect(post.post_view.post).toBeDefined();
|
||||
|
||||
// remote image doesnt get proxied after upload
|
||||
// remote image doesn't get proxied after upload
|
||||
expect(
|
||||
post.post_view.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
||||
).toBeTruthy();
|
||||
expect(post.post_view.post.body).toBe("![](http://example.com/image2.png)");
|
||||
expect(post.post_view.post.body).toBe(`![](${sampleImage})`);
|
||||
|
||||
let betaPost = await waitForPost(
|
||||
beta,
|
||||
|
@ -243,12 +297,67 @@ test("No image proxying if setting is disabled", async () => {
|
|||
);
|
||||
expect(betaPost.post).toBeDefined();
|
||||
|
||||
// remote image doesnt get proxied after federation
|
||||
// remote image doesn't get proxied after federation
|
||||
expect(
|
||||
betaPost.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
||||
).toBeTruthy();
|
||||
expect(betaPost.post.body).toBe("![](http://example.com/image2.png)");
|
||||
|
||||
expect(betaPost.post.body).toBe(`![](${sampleImage})`);
|
||||
// Make sure the alt text got federated
|
||||
expect(post.post_view.post.alt_text).toBe(betaPost.post.alt_text);
|
||||
});
|
||||
|
||||
test("Make regular post, and give it a custom thumbnail", async () => {
|
||||
const uploadForm1: UploadImage = {
|
||||
image: Buffer.from("testRegular1"),
|
||||
};
|
||||
const upload1 = await alphaImage.uploadImage(uploadForm1);
|
||||
|
||||
const community = await createCommunity(alphaImage);
|
||||
|
||||
// Use wikipedia since it has an opengraph image
|
||||
const wikipediaUrl = "https://wikipedia.org/";
|
||||
|
||||
let post = await createPostWithThumbnail(
|
||||
alphaImage,
|
||||
community.community_view.community.id,
|
||||
wikipediaUrl,
|
||||
upload1.url!,
|
||||
);
|
||||
|
||||
// Wait for the metadata to get fetched, since this is backgrounded now
|
||||
post = await waitUntil(
|
||||
() => getPost(alphaImage, post.post_view.post.id),
|
||||
p => p.post_view.post.thumbnail_url != undefined,
|
||||
);
|
||||
expect(post.post_view.post.url).toBe(wikipediaUrl);
|
||||
// Make sure it uses custom thumbnail
|
||||
expect(post.post_view.post.thumbnail_url).toBe(upload1.url);
|
||||
});
|
||||
|
||||
test("Create an image post, and make sure a custom thumbnail doesn't overwrite it", async () => {
|
||||
const uploadForm1: UploadImage = {
|
||||
image: Buffer.from("test1"),
|
||||
};
|
||||
const upload1 = await alphaImage.uploadImage(uploadForm1);
|
||||
|
||||
const uploadForm2: UploadImage = {
|
||||
image: Buffer.from("test2"),
|
||||
};
|
||||
const upload2 = await alphaImage.uploadImage(uploadForm2);
|
||||
|
||||
const community = await createCommunity(alphaImage);
|
||||
|
||||
let post = await createPostWithThumbnail(
|
||||
alphaImage,
|
||||
community.community_view.community.id,
|
||||
upload1.url!,
|
||||
upload2.url!,
|
||||
);
|
||||
post = await waitUntil(
|
||||
() => getPost(alphaImage, post.post_view.post.id),
|
||||
p => p.post_view.post.thumbnail_url != undefined,
|
||||
);
|
||||
expect(post.post_view.post.url).toBe(upload1.url);
|
||||
// Make sure the custom thumbnail is ignored
|
||||
expect(post.post_view.post.thumbnail_url == upload2.url).toBe(false);
|
||||
});
|
||||
|
|
|
@ -48,12 +48,9 @@ beforeAll(async () => {
|
|||
await setupLogins();
|
||||
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||
expect(betaCommunity).toBeDefined();
|
||||
await unfollows();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
unfollows();
|
||||
});
|
||||
afterAll(unfollows);
|
||||
|
||||
async function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
||||
// Link metadata is generated in background task and may not be ready yet at this time,
|
||||
|
@ -85,10 +82,7 @@ async function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
|||
|
||||
test("Create a post", async () => {
|
||||
// Setup some allowlists and blocklists
|
||||
let editSiteForm: EditSite = {
|
||||
allowed_instances: ["lemmy-beta"],
|
||||
};
|
||||
await delta.editSite(editSiteForm);
|
||||
const editSiteForm: EditSite = {};
|
||||
|
||||
editSiteForm.allowed_instances = [];
|
||||
editSiteForm.blocked_instances = ["lemmy-alpha"];
|
||||
|
@ -663,40 +657,60 @@ test("A and G subscribe to B (center) A posts, it gets announced to G", async ()
|
|||
});
|
||||
|
||||
test("Report a post", async () => {
|
||||
// Note, this is a different one from the setup
|
||||
let betaCommunity = (await resolveBetaCommunity(beta)).community;
|
||||
if (!betaCommunity) {
|
||||
throw "Missing beta community";
|
||||
}
|
||||
// Create post from alpha
|
||||
let alphaCommunity = (await resolveBetaCommunity(alpha)).community!;
|
||||
await followBeta(alpha);
|
||||
let postRes = await createPost(beta, betaCommunity.community.id);
|
||||
let postRes = await createPost(alpha, alphaCommunity.community.id);
|
||||
expect(postRes.post_view.post).toBeDefined();
|
||||
|
||||
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
|
||||
if (!alphaPost) {
|
||||
throw "Missing alpha post";
|
||||
}
|
||||
let alphaReport = (
|
||||
await reportPost(alpha, alphaPost.post.id, randomString(10))
|
||||
).post_report_view.post_report;
|
||||
|
||||
// Send report from gamma
|
||||
let gammaPost = (await resolvePost(gamma, alphaPost.post)).post!;
|
||||
let gammaReport = (
|
||||
await reportPost(gamma, gammaPost.post.id, randomString(10))
|
||||
).post_report_view.post_report;
|
||||
expect(gammaReport).toBeDefined();
|
||||
|
||||
// Report was federated to community instance
|
||||
let betaReport = (await waitUntil(
|
||||
() =>
|
||||
listPostReports(beta).then(p =>
|
||||
p.post_reports.find(
|
||||
r =>
|
||||
r.post_report.original_post_name === alphaReport.original_post_name,
|
||||
r.post_report.original_post_name === gammaReport.original_post_name,
|
||||
),
|
||||
),
|
||||
res => !!res,
|
||||
))!.post_report;
|
||||
expect(betaReport).toBeDefined();
|
||||
expect(betaReport.resolved).toBe(false);
|
||||
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name);
|
||||
expect(betaReport.original_post_url).toBe(alphaReport.original_post_url);
|
||||
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body);
|
||||
expect(betaReport.reason).toBe(alphaReport.reason);
|
||||
expect(betaReport.original_post_name).toBe(gammaReport.original_post_name);
|
||||
//expect(betaReport.original_post_url).toBe(gammaReport.original_post_url);
|
||||
expect(betaReport.original_post_body).toBe(gammaReport.original_post_body);
|
||||
expect(betaReport.reason).toBe(gammaReport.reason);
|
||||
await unfollowRemotes(alpha);
|
||||
|
||||
// Report was federated to poster's instance
|
||||
let alphaReport = (await waitUntil(
|
||||
() =>
|
||||
listPostReports(alpha).then(p =>
|
||||
p.post_reports.find(
|
||||
r =>
|
||||
r.post_report.original_post_name === gammaReport.original_post_name,
|
||||
),
|
||||
),
|
||||
res => !!res,
|
||||
))!.post_report;
|
||||
expect(alphaReport).toBeDefined();
|
||||
expect(alphaReport.resolved).toBe(false);
|
||||
expect(alphaReport.original_post_name).toBe(gammaReport.original_post_name);
|
||||
//expect(alphaReport.original_post_url).toBe(gammaReport.original_post_url);
|
||||
expect(alphaReport.original_post_body).toBe(gammaReport.original_post_body);
|
||||
expect(alphaReport.reason).toBe(gammaReport.reason);
|
||||
});
|
||||
|
||||
test("Fetch post via redirect", async () => {
|
||||
|
@ -731,7 +745,7 @@ test("Block post that contains banned URL", async () => {
|
|||
|
||||
await epsilon.editSite(editSiteForm);
|
||||
|
||||
await delay(500);
|
||||
await delay();
|
||||
|
||||
if (!betaCommunity) {
|
||||
throw "Missing beta community";
|
||||
|
@ -745,3 +759,23 @@ test("Block post that contains banned URL", async () => {
|
|||
editSiteForm.blocked_urls = [];
|
||||
await epsilon.editSite(editSiteForm);
|
||||
});
|
||||
|
||||
test("Fetch post with redirect", async () => {
|
||||
let alphaPost = await createPost(alpha, betaCommunity!.community.id);
|
||||
expect(alphaPost.post_view.post).toBeDefined();
|
||||
|
||||
// beta fetches from alpha as usual
|
||||
let betaPost = await resolvePost(beta, alphaPost.post_view.post);
|
||||
expect(betaPost.post).toBeDefined();
|
||||
|
||||
// gamma fetches from beta, and gets redirected to alpha
|
||||
let gammaPost = await resolvePost(gamma, betaPost.post!.post);
|
||||
expect(gammaPost.post).toBeDefined();
|
||||
|
||||
// fetch remote object from local url, which redirects to the original url
|
||||
let form: ResolveObject = {
|
||||
q: `http://lemmy-gamma:8561/post/${gammaPost.post!.post.id}`,
|
||||
};
|
||||
let gammaPost2 = await gamma.resolveObject(form);
|
||||
expect(gammaPost2.post).toBeDefined();
|
||||
});
|
||||
|
|
|
@ -21,9 +21,7 @@ beforeAll(async () => {
|
|||
recipient_id = 3;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
unfollows();
|
||||
});
|
||||
afterAll(unfollows);
|
||||
|
||||
test("Create a private message", async () => {
|
||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||
|
|
|
@ -81,21 +81,24 @@ import { ListingType } from "lemmy-js-client/dist/types/ListingType";
|
|||
|
||||
export const fetchFunction = fetch;
|
||||
export const imageFetchLimit = 50;
|
||||
export const sampleImage =
|
||||
"https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg";
|
||||
export const sampleSite = "https://yahoo.com";
|
||||
|
||||
export let alphaUrl = "http://127.0.0.1:8541";
|
||||
export let betaUrl = "http://127.0.0.1:8551";
|
||||
export let gammaUrl = "http://127.0.0.1:8561";
|
||||
export let deltaUrl = "http://127.0.0.1:8571";
|
||||
export let epsilonUrl = "http://127.0.0.1:8581";
|
||||
export const alphaUrl = "http://127.0.0.1:8541";
|
||||
export const betaUrl = "http://127.0.0.1:8551";
|
||||
export const gammaUrl = "http://127.0.0.1:8561";
|
||||
export const deltaUrl = "http://127.0.0.1:8571";
|
||||
export const epsilonUrl = "http://127.0.0.1:8581";
|
||||
|
||||
export let alpha = new LemmyHttp(alphaUrl, { fetchFunction });
|
||||
export let alphaImage = new LemmyHttp(alphaUrl);
|
||||
export let beta = new LemmyHttp(betaUrl, { fetchFunction });
|
||||
export let gamma = new LemmyHttp(gammaUrl, { fetchFunction });
|
||||
export let delta = new LemmyHttp(deltaUrl, { fetchFunction });
|
||||
export let epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
|
||||
export const alpha = new LemmyHttp(alphaUrl, { fetchFunction });
|
||||
export const alphaImage = new LemmyHttp(alphaUrl);
|
||||
export const beta = new LemmyHttp(betaUrl, { fetchFunction });
|
||||
export const gamma = new LemmyHttp(gammaUrl, { fetchFunction });
|
||||
export const delta = new LemmyHttp(deltaUrl, { fetchFunction });
|
||||
export const epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
|
||||
|
||||
export let betaAllowedInstances = [
|
||||
export const betaAllowedInstances = [
|
||||
"lemmy-alpha",
|
||||
"lemmy-gamma",
|
||||
"lemmy-delta",
|
||||
|
@ -180,6 +183,10 @@ export async function setupLogins() {
|
|||
];
|
||||
await gamma.editSite(editSiteForm);
|
||||
|
||||
// Setup delta allowed instance
|
||||
editSiteForm.allowed_instances = ["lemmy-beta"];
|
||||
await delta.editSite(editSiteForm);
|
||||
|
||||
// Create the main alpha/beta communities
|
||||
// Ignore thrown errors of duplicates
|
||||
try {
|
||||
|
@ -203,6 +210,7 @@ export async function createPost(
|
|||
// use example.com for consistent title and embed description
|
||||
name: string = randomString(5),
|
||||
alt_text = randomString(10),
|
||||
custom_thumbnail: string | undefined = undefined,
|
||||
): Promise<PostResponse> {
|
||||
let form: CreatePost = {
|
||||
name,
|
||||
|
@ -210,6 +218,7 @@ export async function createPost(
|
|||
body,
|
||||
alt_text,
|
||||
community_id,
|
||||
custom_thumbnail,
|
||||
};
|
||||
return api.createPost(form);
|
||||
}
|
||||
|
@ -226,6 +235,21 @@ export async function editPost(
|
|||
return api.editPost(form);
|
||||
}
|
||||
|
||||
export async function createPostWithThumbnail(
|
||||
api: LemmyHttp,
|
||||
community_id: number,
|
||||
url: string,
|
||||
custom_thumbnail: string,
|
||||
): Promise<PostResponse> {
|
||||
let form: CreatePost = {
|
||||
name: randomString(10),
|
||||
url,
|
||||
community_id,
|
||||
custom_thumbnail,
|
||||
};
|
||||
return api.createPost(form);
|
||||
}
|
||||
|
||||
export async function deletePost(
|
||||
api: LemmyHttp,
|
||||
deleted: boolean,
|
||||
|
@ -340,10 +364,13 @@ export async function getUnreadCount(
|
|||
return api.getUnreadCount();
|
||||
}
|
||||
|
||||
export async function getReplies(api: LemmyHttp): Promise<GetRepliesResponse> {
|
||||
export async function getReplies(
|
||||
api: LemmyHttp,
|
||||
unread_only: boolean = false,
|
||||
): Promise<GetRepliesResponse> {
|
||||
let form: GetReplies = {
|
||||
sort: "New",
|
||||
unread_only: false,
|
||||
unread_only,
|
||||
};
|
||||
return api.getReplies(form);
|
||||
}
|
||||
|
@ -676,8 +703,8 @@ export async function saveUserSettingsBio(
|
|||
export async function saveUserSettingsFederated(
|
||||
api: LemmyHttp,
|
||||
): Promise<SuccessResponse> {
|
||||
let avatar = "https://image.flaticon.com/icons/png/512/35/35896.png";
|
||||
let banner = "https://image.flaticon.com/icons/png/512/36/35896.png";
|
||||
let avatar = sampleImage;
|
||||
let banner = sampleImage;
|
||||
let bio = "a changed bio";
|
||||
let form: SaveUserSettings = {
|
||||
show_nsfw: false,
|
||||
|
@ -743,6 +770,7 @@ export async function unfollowRemotes(
|
|||
await Promise.all(
|
||||
remoteFollowed.map(cu => followCommunity(api, false, cu.community.id)),
|
||||
);
|
||||
|
||||
let siteRes = await getSite(api);
|
||||
return siteRes;
|
||||
}
|
||||
|
@ -872,14 +900,17 @@ export async function deleteAllImages(api: LemmyHttp) {
|
|||
limit: imageFetchLimit,
|
||||
});
|
||||
imagesRes.images;
|
||||
|
||||
for (const image of imagesRes.images) {
|
||||
Promise.all(
|
||||
imagesRes.images
|
||||
.map(image => {
|
||||
const form: DeleteImage = {
|
||||
token: image.pictrs_delete_token,
|
||||
filename: image.pictrs_alias,
|
||||
token: image.local_image.pictrs_delete_token,
|
||||
filename: image.local_image.pictrs_alias,
|
||||
};
|
||||
await api.deleteImage(form);
|
||||
}
|
||||
return form;
|
||||
})
|
||||
.map(form => api.deleteImage(form)),
|
||||
);
|
||||
}
|
||||
|
||||
export async function unfollows() {
|
||||
|
@ -890,6 +921,24 @@ export async function unfollows() {
|
|||
unfollowRemotes(delta),
|
||||
unfollowRemotes(epsilon),
|
||||
]);
|
||||
await Promise.all([
|
||||
purgeAllPosts(alpha),
|
||||
purgeAllPosts(beta),
|
||||
purgeAllPosts(gamma),
|
||||
purgeAllPosts(delta),
|
||||
purgeAllPosts(epsilon),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function purgeAllPosts(api: LemmyHttp) {
|
||||
// The best way to get all federated items, is to find the posts
|
||||
let res = await api.getPosts({ type_: "All", limit: 50 });
|
||||
await Promise.all(
|
||||
Array.from(new Set(res.posts.map(p => p.post.id)))
|
||||
.map(post_id => api.purgePost({ post_id }))
|
||||
// Ignore errors
|
||||
.map(p => p.catch(e => e)),
|
||||
);
|
||||
}
|
||||
|
||||
export function getCommentParentId(comment: Comment): number | undefined {
|
||||
|
|
|
@ -20,11 +20,14 @@ import {
|
|||
getComments,
|
||||
fetchFunction,
|
||||
alphaImage,
|
||||
unfollows,
|
||||
saveUserSettingsBio,
|
||||
} from "./shared";
|
||||
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||
|
||||
beforeAll(setupLogins);
|
||||
afterAll(unfollows);
|
||||
|
||||
let apShortname: string;
|
||||
|
||||
|
@ -184,10 +187,26 @@ test("Set a new avatar, old avatar is deleted", async () => {
|
|||
expect(upload2.url).toBeDefined();
|
||||
|
||||
let form2 = {
|
||||
avatar: upload1.url,
|
||||
avatar: upload2.url,
|
||||
};
|
||||
await saveUserSettings(alpha, form2);
|
||||
// make sure only the new avatar is kept
|
||||
const listMediaRes2 = await alphaImage.listMedia();
|
||||
expect(listMediaRes2.images.length).toBe(1);
|
||||
|
||||
// Upload that same form2 avatar, make sure it isn't replaced / deleted
|
||||
await saveUserSettings(alpha, form2);
|
||||
// make sure only the new avatar is kept
|
||||
const listMediaRes3 = await alphaImage.listMedia();
|
||||
expect(listMediaRes3.images.length).toBe(1);
|
||||
|
||||
// Now try to save a user settings, with the icon missing,
|
||||
// and make sure it doesn't clear the data, or delete the image
|
||||
await saveUserSettingsBio(alpha);
|
||||
let site = await getSite(alpha);
|
||||
expect(site.my_user?.local_user_view.person.avatar).toBe(upload2.url);
|
||||
|
||||
// make sure only the new avatar is kept
|
||||
const listMediaRes4 = await alphaImage.listMedia();
|
||||
expect(listMediaRes4.images.length).toBe(1);
|
||||
});
|
||||
|
|
|
@ -47,9 +47,10 @@
|
|||
#
|
||||
# To be removed in 0.20
|
||||
cache_external_link_previews: true
|
||||
# Specifies how to handle remote images, so that users don't have to connect directly to remote servers.
|
||||
# Specifies how to handle remote images, so that users don't have to connect directly to remote
|
||||
# servers.
|
||||
image_mode:
|
||||
# Leave images unchanged, don't generate any local thumbnails for post urls. Instead the the
|
||||
# Leave images unchanged, don't generate any local thumbnails for post urls. Instead the
|
||||
# Opengraph image is directly returned as thumbnail
|
||||
"None"
|
||||
|
||||
|
@ -64,10 +65,11 @@
|
|||
|
||||
# or
|
||||
|
||||
# If enabled, all images from remote domains are rewritten to pass through `/api/v3/image_proxy`,
|
||||
# including embedded images in markdown. Images are stored temporarily in pict-rs for caching.
|
||||
# This improves privacy as users don't expose their IP to untrusted servers, and decreases load
|
||||
# on other servers. However it increases bandwidth use for the local server.
|
||||
# If enabled, all images from remote domains are rewritten to pass through
|
||||
# `/api/v3/image_proxy`, including embedded images in markdown. Images are stored temporarily
|
||||
# in pict-rs for caching. This improves privacy as users don't expose their IP to untrusted
|
||||
# servers, and decreases load on other servers. However it increases bandwidth use for the
|
||||
# local server.
|
||||
#
|
||||
# Requires pict-rs 0.5
|
||||
"ProxyAllImages"
|
||||
|
|
|
@ -33,7 +33,7 @@ anyhow = { workspace = true }
|
|||
tracing = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
url = { workspace = true }
|
||||
wav = "1.0.0"
|
||||
hound = "3.5.1"
|
||||
sitemap-rs = "0.2.1"
|
||||
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
|
||||
actix-web-httpauth = "0.8.1"
|
||||
|
|
|
@ -9,15 +9,17 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn distinguish_comment(
|
||||
data: Json<DistinguishComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None).await?;
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -54,7 +56,8 @@ pub async fn distinguish_comment(
|
|||
data.comment_id,
|
||||
Some(local_user_view.person.id),
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
Ok(Json(CommentResponse {
|
||||
comment_view,
|
||||
|
|
|
@ -17,7 +17,7 @@ use lemmy_db_schema::{
|
|||
traits::Likeable,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -25,7 +25,7 @@ pub async fn like_comment(
|
|||
data: Json<CreateCommentLike>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
||||
|
@ -35,7 +35,9 @@ pub async fn like_comment(
|
|||
check_bot_account(&local_user_view.person)?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -46,9 +48,10 @@ pub async fn like_comment(
|
|||
|
||||
// Add parent poster or commenter to recipients
|
||||
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
|
||||
if let Ok(reply) = comment_reply {
|
||||
if let Ok(Some(reply)) = comment_reply {
|
||||
let recipient_id = reply.recipient_id;
|
||||
if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
|
||||
if let Ok(Some(local_recipient)) =
|
||||
LocalUserView::read_person(&mut context.pool(), recipient_id).await
|
||||
{
|
||||
recipient_ids.push(local_recipient.local_user.id);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
|||
utils::is_mod_or_admin,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
/// Lists likes for a comment
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -13,13 +13,15 @@ pub async fn list_comment_likes(
|
|||
data: Query<ListCommentLikes>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListCommentLikesResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<ListCommentLikesResponse>> {
|
||||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
data.comment_id,
|
||||
Some(local_user_view.person.id),
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
is_mod_or_admin(
|
||||
&mut context.pool(),
|
||||
&local_user_view.person,
|
||||
|
|
|
@ -8,14 +8,14 @@ use lemmy_db_schema::{
|
|||
traits::Saveable,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn save_comment(
|
||||
data: Json<SaveComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let comment_saved_form = CommentSavedForm {
|
||||
comment_id: data.comment_id,
|
||||
person_id: local_user_view.person.id,
|
||||
|
@ -33,7 +33,9 @@ pub async fn save_comment(
|
|||
|
||||
let comment_id = data.comment_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, Some(person_id)).await?;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, Some(person_id))
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
Ok(Json(CommentResponse {
|
||||
comment_view,
|
||||
|
|
|
@ -19,7 +19,7 @@ use lemmy_db_schema::{
|
|||
traits::Reportable,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentReportView, CommentView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
/// Creates a comment report and notifies the moderators of the community
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -27,7 +27,7 @@ pub async fn create_comment_report(
|
|||
data: Json<CreateCommentReport>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentReportResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentReportResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
let reason = data.reason.trim().to_string();
|
||||
|
@ -35,7 +35,9 @@ pub async fn create_comment_report(
|
|||
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_id = data.comment_id;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -58,8 +60,9 @@ pub async fn create_comment_report(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
||||
|
||||
let comment_report_view =
|
||||
CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
|
||||
let comment_report_view = CommentReportView::read(&mut context.pool(), report.id, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
|
||||
|
||||
// Email the admins
|
||||
if local_site.reports_email_admins {
|
||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
|||
utils::check_community_mod_of_any_or_admin_action,
|
||||
};
|
||||
use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
/// Lists comment reports for a community if an id is supplied
|
||||
/// or returns all comment reports for communities a user moderates
|
||||
|
@ -14,7 +14,7 @@ pub async fn list_comment_reports(
|
|||
data: Query<ListCommentReports>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListCommentReportsResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<ListCommentReportsResponse>> {
|
||||
let community_id = data.community_id;
|
||||
let comment_id = data.comment_id;
|
||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
|
||||
use lemmy_db_views::structs::{CommentReportView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
/// Resolves or unresolves a comment report and notifies the moderators of the community
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -14,10 +14,12 @@ pub async fn resolve_comment_report(
|
|||
data: Json<ResolveCommentReport>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentReportResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentReportResponse>> {
|
||||
let report_id = data.report_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||
let report = CommentReportView::read(&mut context.pool(), report_id, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
check_community_mod_action(
|
||||
|
@ -39,8 +41,9 @@ pub async fn resolve_comment_report(
|
|||
}
|
||||
|
||||
let report_id = data.report_id;
|
||||
let comment_report_view =
|
||||
CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||
let comment_report_view = CommentReportView::read(&mut context.pool(), report_id, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
|
||||
|
||||
Ok(Json(CommentReportResponse {
|
||||
comment_report_view,
|
||||
|
|
|
@ -15,14 +15,14 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn add_mod_to_community(
|
||||
data: Json<AddModToCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<AddModToCommunityResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<AddModToCommunityResponse>> {
|
||||
let community_id = data.community_id;
|
||||
|
||||
// Verify that only mods or admins can add mod
|
||||
|
@ -33,10 +33,24 @@ pub async fn add_mod_to_community(
|
|||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
let community = Community::read(&mut context.pool(), community_id).await?;
|
||||
let community = Community::read(&mut context.pool(), community_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
// If user is admin and community is remote, explicitly check that he is a
|
||||
// moderator. This is necessary because otherwise the action would be rejected
|
||||
// by the community's home instance.
|
||||
if local_user_view.local_user.admin && !community.local {
|
||||
let is_mod = CommunityModeratorView::is_community_moderator(
|
||||
&mut context.pool(),
|
||||
community.id,
|
||||
local_user_view.person.id,
|
||||
)
|
||||
.await?;
|
||||
if !is_mod {
|
||||
Err(LemmyErrorType::NotAModerator)?
|
||||
}
|
||||
}
|
||||
|
||||
// Update in local database
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
|||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PersonView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::validation::is_valid_body_field,
|
||||
};
|
||||
|
||||
|
@ -30,7 +30,7 @@ pub async fn ban_from_community(
|
|||
data: Json<BanFromCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<BanFromCommunityResponse>> {
|
||||
let banned_person_id = data.person_id;
|
||||
let remove_data = data.remove_data.unwrap_or(false);
|
||||
let expires = check_expire_time(data.expires)?;
|
||||
|
@ -43,7 +43,10 @@ pub async fn ban_from_community(
|
|||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
is_valid_body_field(&data.reason, false)?;
|
||||
|
||||
if let Some(reason) = &data.reason {
|
||||
is_valid_body_field(reason, false)?;
|
||||
}
|
||||
|
||||
let community_user_ban_form = CommunityPersonBanForm {
|
||||
community_id: data.community_id,
|
||||
|
@ -89,7 +92,9 @@ pub async fn ban_from_community(
|
|||
|
||||
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
|
||||
|
||||
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
|
||||
let person_view = PersonView::read(&mut context.pool(), data.person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
SendActivityData::BanFromCommunity {
|
||||
|
|
|
@ -14,14 +14,14 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommunityView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn block_community(
|
||||
data: Json<BlockCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<BlockCommunityResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<BlockCommunityResponse>> {
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_block_form = CommunityBlockForm {
|
||||
|
@ -51,7 +51,9 @@ pub async fn block_community(
|
|||
}
|
||||
|
||||
let community_view =
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?;
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
SendActivityData::FollowCommunity(
|
||||
|
|
|
@ -15,15 +15,17 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommunityView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn follow_community(
|
||||
data: Json<FollowCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
||||
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||
) -> LemmyResult<Json<CommunityResponse>> {
|
||||
let community = Community::read(&mut context.pool(), data.community_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
let mut community_follower_form = CommunityFollowerForm {
|
||||
community_id: community.id,
|
||||
person_id: local_user_view.person.id,
|
||||
|
@ -62,7 +64,10 @@ pub async fn follow_community(
|
|||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view =
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?;
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
||||
|
||||
Ok(Json(CommunityResponse {
|
||||
|
|
|
@ -15,14 +15,14 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn hide_community(
|
||||
data: Json<HideCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Verify its a admin (only admin can hide or unhide it)
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ use lemmy_db_schema::{
|
|||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
location_info,
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,7 @@ pub async fn transfer_community(
|
|||
data: Json<TransferCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetCommunityResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetCommunityResponse>> {
|
||||
let community_id = data.community_id;
|
||||
let mut community_mods =
|
||||
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
||||
|
@ -79,8 +79,8 @@ pub async fn transfer_community(
|
|||
let person_id = local_user_view.person.id;
|
||||
let community_view =
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id)
|
||||
|
|
|
@ -26,7 +26,7 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||
utils::slurs::check_slurs,
|
||||
};
|
||||
use std::io::Cursor;
|
||||
|
@ -44,31 +44,37 @@ pub mod site;
|
|||
pub mod sitemap;
|
||||
|
||||
/// Converts the captcha to a base64 encoded wav audio file
|
||||
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyError> {
|
||||
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
|
||||
let letters = captcha.as_wav();
|
||||
|
||||
// Decode each wav file, concatenate the samples
|
||||
let mut concat_samples: Vec<i16> = Vec::new();
|
||||
let mut any_header: Option<wav::Header> = None;
|
||||
let mut any_header: Option<hound::WavSpec> = None;
|
||||
for letter in letters {
|
||||
let mut cursor = Cursor::new(letter.unwrap_or_default());
|
||||
let (header, samples) = wav::read(&mut cursor)?;
|
||||
any_header = Some(header);
|
||||
if let Some(samples16) = samples.as_sixteen() {
|
||||
let reader = hound::WavReader::new(&mut cursor)?;
|
||||
any_header = Some(reader.spec());
|
||||
let samples16 = reader
|
||||
.into_samples::<i16>()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
concat_samples.extend(samples16);
|
||||
} else {
|
||||
Err(LemmyErrorType::CouldntCreateAudioCaptcha)?
|
||||
}
|
||||
}
|
||||
|
||||
// Encode the concatenated result as a wav file
|
||||
let mut output_buffer = Cursor::new(vec![]);
|
||||
if let Some(header) = any_header {
|
||||
wav::write(
|
||||
header,
|
||||
&wav::BitDepth::Sixteen(concat_samples),
|
||||
&mut output_buffer,
|
||||
)
|
||||
let mut writer = hound::WavWriter::new(&mut output_buffer, header)
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
let mut writer16 = writer.get_i16_writer(concat_samples.len() as u32);
|
||||
for sample in concat_samples {
|
||||
writer16.write_sample(sample);
|
||||
}
|
||||
writer16
|
||||
.flush()
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
writer
|
||||
.finalize()
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
|
||||
Ok(base64.encode(output_buffer.into_inner()))
|
||||
|
@ -78,7 +84,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyEr
|
|||
}
|
||||
|
||||
/// Check size of report
|
||||
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
|
||||
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> LemmyResult<()> {
|
||||
let slur_regex = &local_site_to_slur_regex(local_site);
|
||||
|
||||
check_slurs(reason, slur_regex)?;
|
||||
|
@ -91,7 +97,7 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_auth_token(req: &HttpRequest) -> Result<Option<String>, LemmyError> {
|
||||
pub fn read_auth_token(req: &HttpRequest) -> LemmyResult<Option<String>> {
|
||||
// Try reading jwt from auth header
|
||||
if let Ok(header) = Authorization::<Bearer>::parse(req) {
|
||||
Ok(Some(header.as_ref().token().to_string()))
|
||||
|
@ -135,7 +141,7 @@ pub(crate) fn generate_totp_2fa_secret() -> String {
|
|||
Secret::generate_secret().to_string()
|
||||
}
|
||||
|
||||
fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> Result<TOTP, LemmyError> {
|
||||
fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> LemmyResult<TOTP> {
|
||||
let sec = Secret::Raw(secret.as_bytes().to_vec());
|
||||
let sec_bytes = sec
|
||||
.to_bytes()
|
||||
|
@ -248,11 +254,13 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
|||
pub async fn local_user_view_from_jwt(
|
||||
jwt: &str,
|
||||
context: &LemmyContext,
|
||||
) -> Result<LocalUserView, LemmyError> {
|
||||
) -> LemmyResult<LocalUserView> {
|
||||
let local_user_id = Claims::validate(jwt, context)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
||||
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
|
||||
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
|
||||
check_user_valid(&local_user_view.person)?;
|
||||
|
||||
Ok(local_user_view)
|
||||
|
|
|
@ -13,23 +13,23 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PersonView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn add_admin(
|
||||
data: Json<AddAdmin>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<AddAdminResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<AddAdminResponse>> {
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure that the person_id added is local
|
||||
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::ObjectNotLocal)?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::ObjectNotLocal)?;
|
||||
|
||||
let added_admin = LocalUser::update(
|
||||
LocalUser::update(
|
||||
&mut context.pool(),
|
||||
added_local_user.local_user.id,
|
||||
&LocalUserUpdateForm {
|
||||
|
@ -43,7 +43,7 @@ pub async fn add_admin(
|
|||
// Mod tables
|
||||
let form = ModAddForm {
|
||||
mod_person_id: local_user_view.person.id,
|
||||
other_person_id: added_admin.person_id,
|
||||
other_person_id: added_local_user.person.id,
|
||||
removed: Some(!data.added),
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ use lemmy_db_schema::{
|
|||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PersonView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::validation::is_valid_body_field,
|
||||
};
|
||||
|
||||
|
@ -27,11 +27,13 @@ pub async fn ban_from_site(
|
|||
data: Json<BanPerson>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<BanPersonResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<BanPersonResponse>> {
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
is_valid_body_field(&data.reason, false)?;
|
||||
if let Some(reason) = &data.reason {
|
||||
is_valid_body_field(reason, false)?;
|
||||
}
|
||||
|
||||
let expires = check_expire_time(data.expires)?;
|
||||
|
||||
|
@ -49,7 +51,7 @@ pub async fn ban_from_site(
|
|||
|
||||
// if its a local user, invalidate logins
|
||||
let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await;
|
||||
if let Ok(local_user) = local_user {
|
||||
if let Ok(Some(local_user)) = local_user {
|
||||
LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?;
|
||||
}
|
||||
|
||||
|
@ -70,7 +72,9 @@ pub async fn ban_from_site(
|
|||
|
||||
ModBan::create(&mut context.pool(), &form).await?;
|
||||
|
||||
let person_view = PersonView::read(&mut context.pool(), person.id).await?;
|
||||
let person_view = PersonView::read(&mut context.pool(), person.id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
|
||||
ban_nonlocal_user_from_local_communities(
|
||||
&local_user_view,
|
||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PersonView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn block_person(
|
||||
data: Json<BlockPerson>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<BlockPersonResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<BlockPersonResponse>> {
|
||||
let target_id = data.person_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
|
@ -30,8 +30,12 @@ pub async fn block_person(
|
|||
target_id,
|
||||
};
|
||||
|
||||
let target_user = LocalUserView::read_person(&mut context.pool(), target_id).await;
|
||||
if target_user.map(|t| t.local_user.admin) == Ok(true) {
|
||||
let target_user = LocalUserView::read_person(&mut context.pool(), target_id)
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
if target_user.is_some_and(|t| t.local_user.admin) {
|
||||
Err(LemmyErrorType::CantBlockAdmin)?
|
||||
}
|
||||
|
||||
|
@ -45,7 +49,9 @@ pub async fn block_person(
|
|||
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
|
||||
}
|
||||
|
||||
let person_view = PersonView::read(&mut context.pool(), target_id).await?;
|
||||
let person_view = PersonView::read(&mut context.pool(), target_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
Ok(Json(BlockPersonResponse {
|
||||
person_view,
|
||||
blocked: data.block,
|
||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::source::{local_user::LocalUser, login_token::LoginToken};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn change_password(
|
||||
|
@ -19,7 +19,7 @@ pub async fn change_password(
|
|||
req: HttpRequest,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<LoginResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<LoginResponse>> {
|
||||
password_length_check(&data.new_password)?;
|
||||
|
||||
// Make sure passwords match
|
||||
|
|
|
@ -10,18 +10,19 @@ use lemmy_db_schema::source::{
|
|||
login_token::LoginToken,
|
||||
password_reset_request::PasswordResetRequest,
|
||||
};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn change_password_after_reset(
|
||||
data: Json<PasswordChangeAfterReset>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Fetch the user_id from the token
|
||||
let token = data.token.clone();
|
||||
let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token)
|
||||
.await
|
||||
.map(|p| p.local_user_id)?;
|
||||
let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::TokenNotFound)?
|
||||
.local_user_id;
|
||||
|
||||
password_length_check(&data.password)?;
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
use crate::{build_totp_2fa, generate_totp_2fa_secret};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::GenerateTotpSecretResponse,
|
||||
sensitive::Sensitive,
|
||||
};
|
||||
use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse};
|
||||
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
|
||||
/// to enable it. This can only be called if 2FA is currently disabled.
|
||||
|
@ -16,8 +12,10 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
|||
pub async fn generate_totp_secret(
|
||||
local_user_view: LocalUserView,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<GenerateTotpSecretResponse>, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
) -> LemmyResult<Json<GenerateTotpSecretResponse>> {
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
|
||||
if local_user_view.local_user.totp_2fa_enabled {
|
||||
return Err(LemmyErrorType::TotpAlreadyEnabled)?;
|
||||
|
@ -39,6 +37,6 @@ pub async fn generate_totp_secret(
|
|||
.await?;
|
||||
|
||||
Ok(Json(GenerateTotpSecretResponse {
|
||||
totp_secret_url: Sensitive::new(secret_url),
|
||||
totp_secret_url: secret_url.into(),
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -17,10 +17,10 @@ use lemmy_db_schema::source::{
|
|||
captcha_answer::{CaptchaAnswer, CaptchaAnswerForm},
|
||||
local_site::LocalSite,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_captcha(context: Data<LemmyContext>) -> Result<HttpResponse, LemmyError> {
|
||||
pub async fn get_captcha(context: Data<LemmyContext>) -> LemmyResult<HttpResponse> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
let mut res = HttpResponseBuilder::new(StatusCode::OK);
|
||||
res.insert_header(CacheControl(vec![CacheDirective::NoStore]));
|
||||
|
|
|
@ -2,12 +2,12 @@ use actix_web::web::{Data, Json};
|
|||
use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PersonView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn list_banned_users(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<BannedPersonsResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<BannedPersonsResponse>> {
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@ use actix_web::web::{Data, Json};
|
|||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::source::login_token::LoginToken;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn list_logins(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<Vec<LoginToken>>, LemmyError> {
|
||||
) -> LemmyResult<Json<Vec<LoginToken>>> {
|
||||
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
|
||||
|
||||
Ok(Json(logins))
|
||||
|
|
|
@ -3,19 +3,18 @@ use lemmy_api_common::{
|
|||
context::LemmyContext,
|
||||
person::{ListMedia, ListMediaResponse},
|
||||
};
|
||||
use lemmy_db_schema::source::images::LocalImage;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_media(
|
||||
data: Query<ListMedia>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListMediaResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<ListMediaResponse>> {
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let images = LocalImage::get_all_paged_by_local_user_id(
|
||||
let images = LocalImageView::get_all_paged_by_local_user_id(
|
||||
&mut context.pool(),
|
||||
local_user_view.local_user.id,
|
||||
page,
|
||||
|
|
|
@ -16,22 +16,24 @@ use lemmy_db_schema::{
|
|||
RegistrationMode,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn login(
|
||||
data: Json<Login>,
|
||||
req: HttpRequest,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<LoginResponse>, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
) -> LemmyResult<Json<LoginResponse>> {
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
|
||||
// Fetch that username / email
|
||||
let username_or_email = data.username_or_email.clone();
|
||||
let local_user_view =
|
||||
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::IncorrectLogin)?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
||||
|
||||
// Verify the password
|
||||
let valid: bool = verify(
|
||||
|
@ -70,7 +72,7 @@ async fn check_registration_application(
|
|||
local_user_view: &LocalUserView,
|
||||
local_site: &LocalSite,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
if (local_site.registration_mode == RegistrationMode::RequireApplication
|
||||
|| local_site.registration_mode == RegistrationMode::Closed)
|
||||
&& !local_user_view.local_user.accepted_application
|
||||
|
@ -79,7 +81,9 @@ async fn check_registration_application(
|
|||
// Fetch the registration application. If no admin id is present its still pending. Otherwise it
|
||||
// was processed (either accepted or denied).
|
||||
let local_user_id = local_user_view.local_user.id;
|
||||
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?;
|
||||
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
||||
if registration.admin_id.is_some() {
|
||||
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
|
||||
} else {
|
||||
|
|
|
@ -5,14 +5,14 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::person_mention_view::PersonMentionQuery;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_mentions(
|
||||
data: Query<GetPersonMentions>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetPersonMentionsResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetPersonMentionsResponse>> {
|
||||
let sort = data.sort;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
|
|
|
@ -5,14 +5,14 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::comment_reply_view::CommentReplyQuery;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_replies(
|
||||
data: Query<GetReplies>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetRepliesResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetRepliesResponse>> {
|
||||
let sort = data.sort;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
|
|
|
@ -6,13 +6,13 @@ use lemmy_db_schema::source::{
|
|||
private_message::PrivateMessage,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_all_notifications_read(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetRepliesResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetRepliesResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
// Mark all comment_replies as read
|
||||
|
|
|
@ -9,16 +9,18 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PersonMentionView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_person_mention_as_read(
|
||||
data: Json<MarkPersonMentionAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PersonMentionResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PersonMentionResponse>> {
|
||||
let person_mention_id = data.person_mention_id;
|
||||
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;
|
||||
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
|
||||
|
||||
if local_user_view.person.id != read_person_mention.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||
|
@ -37,7 +39,9 @@ pub async fn mark_person_mention_as_read(
|
|||
let person_mention_id = read_person_mention.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let person_mention_view =
|
||||
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)).await?;
|
||||
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id))
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
|
||||
|
||||
Ok(Json(PersonMentionResponse {
|
||||
person_mention_view,
|
||||
|
|
|
@ -9,16 +9,18 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommentReplyView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_reply_as_read(
|
||||
data: Json<MarkCommentReplyAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentReplyResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentReplyResponse>> {
|
||||
let comment_reply_id = data.comment_reply_id;
|
||||
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;
|
||||
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
|
||||
|
||||
if local_user_view.person.id != read_comment_reply.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||
|
@ -38,7 +40,9 @@ pub async fn mark_reply_as_read(
|
|||
let comment_reply_id = read_comment_reply.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_reply_view =
|
||||
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?;
|
||||
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id))
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
|
||||
|
||||
Ok(Json(CommentReplyResponse { comment_reply_view }))
|
||||
}
|
||||
|
|
|
@ -2,18 +2,21 @@ use actix_web::web::{Data, Json};
|
|||
use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn unread_count(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetUnreadCountResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetUnreadCountResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
let replies = CommentReplyView::get_unread_replies(&mut context.pool(), person_id).await?;
|
||||
let replies =
|
||||
CommentReplyView::get_unread_replies(&mut context.pool(), &local_user_view.local_user).await?;
|
||||
|
||||
let mentions = PersonMentionView::get_unread_mentions(&mut context.pool(), person_id).await?;
|
||||
let mentions =
|
||||
PersonMentionView::get_unread_mentions(&mut context.pool(), &local_user_view.local_user)
|
||||
.await?;
|
||||
|
||||
let private_messages =
|
||||
PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?;
|
||||
|
|
|
@ -10,14 +10,14 @@ use lemmy_db_views::structs::{
|
|||
PostReportView,
|
||||
PrivateMessageReportView,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn report_count(
|
||||
data: Query<GetReportCount>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetReportCountResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetReportCountResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
let admin = local_user_view.local_user.admin;
|
||||
let community_id = data.community_id;
|
||||
|
|
|
@ -6,9 +6,8 @@ use lemmy_api_common::{
|
|||
utils::send_password_reset_email,
|
||||
SuccessResponse,
|
||||
};
|
||||
use lemmy_db_schema::source::password_reset_request::PasswordResetRequest;
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn reset_password(
|
||||
|
@ -18,19 +17,12 @@ pub async fn reset_password(
|
|||
// Fetch that email
|
||||
let email = data.email.to_lowercase();
|
||||
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::IncorrectLogin)?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
||||
|
||||
// Check for too many attempts (to limit potential abuse)
|
||||
let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count(
|
||||
&mut context.pool(),
|
||||
local_user_view.local_user.id,
|
||||
)
|
||||
.await?;
|
||||
if recent_resets_count >= 3 {
|
||||
Err(LemmyErrorType::PasswordResetLimitReached)?
|
||||
}
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
check_email_verified(&local_user_view, &site_view)?;
|
||||
|
||||
// Email the pure token to the user.
|
||||
|
|
|
@ -21,41 +21,50 @@ use lemmy_db_schema::{
|
|||
person::{Person, PersonUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::diesel_option_overwrite,
|
||||
utils::{diesel_string_update, diesel_url_update},
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorType},
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id},
|
||||
};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn save_user_settings(
|
||||
data: Json<SaveUserSettings>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
|
||||
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let bio = diesel_option_overwrite(
|
||||
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?,
|
||||
let bio = diesel_string_update(
|
||||
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context)
|
||||
.await?
|
||||
.as_deref(),
|
||||
);
|
||||
replace_image(&data.avatar, &local_user_view.person.avatar, &context).await?;
|
||||
replace_image(&data.banner, &local_user_view.person.banner, &context).await?;
|
||||
|
||||
let avatar = proxy_image_link_opt_api(&data.avatar, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
||||
let display_name = diesel_option_overwrite(data.display_name.clone());
|
||||
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
|
||||
let avatar = diesel_url_update(data.avatar.as_deref())?;
|
||||
replace_image(&avatar, &local_user_view.person.avatar, &context).await?;
|
||||
let avatar = proxy_image_link_opt_api(avatar, &context).await?;
|
||||
|
||||
let banner = diesel_url_update(data.banner.as_deref())?;
|
||||
replace_image(&banner, &local_user_view.person.banner, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(banner, &context).await?;
|
||||
|
||||
let display_name = diesel_string_update(data.display_name.as_deref());
|
||||
let matrix_user_id = diesel_string_update(data.matrix_user_id.as_deref());
|
||||
let email_deref = data.email.as_deref().map(str::to_lowercase);
|
||||
let email = diesel_option_overwrite(email_deref.clone());
|
||||
let email = diesel_string_update(email_deref.as_deref());
|
||||
|
||||
if let Some(Some(email)) = &email {
|
||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
||||
// if email was changed, check that it is not taken and send verification mail
|
||||
if &previous_email != email {
|
||||
if previous_email.deref() != email {
|
||||
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
|
||||
return Err(LemmyErrorType::EmailAlreadyExists)?;
|
||||
}
|
||||
|
@ -69,7 +78,8 @@ pub async fn save_user_settings(
|
|||
}
|
||||
}
|
||||
|
||||
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
|
||||
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None
|
||||
// value
|
||||
if let Some(email) = &email {
|
||||
if email.is_none() && site_view.local_site.require_email_verification {
|
||||
Err(LemmyErrorType::EmailRequired)?
|
||||
|
@ -139,11 +149,7 @@ pub async fn save_user_settings(
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
// Ignore errors, because 'no fields updated' will return an error.
|
||||
// https://github.com/LemmyNet/lemmy/issues/4076
|
||||
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form)
|
||||
.await
|
||||
.ok();
|
||||
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;
|
||||
|
||||
// Update the vote display modes
|
||||
let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm {
|
||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
/// Enable or disable two-factor-authentication. The current setting is determined from
|
||||
/// [LocalUser.totp_2fa_enabled].
|
||||
|
@ -21,7 +21,7 @@ pub async fn update_totp(
|
|||
data: Json<UpdateTotp>,
|
||||
local_user_view: LocalUserView,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<UpdateTotpResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<UpdateTotpResponse>> {
|
||||
check_totp_2fa_valid(
|
||||
&local_user_view,
|
||||
&Some(data.totp_token.clone()),
|
||||
|
|
|
@ -4,7 +4,7 @@ use actix_web::{
|
|||
HttpRequest,
|
||||
};
|
||||
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
/// Returns an error message if the auth token is invalid for any reason. Necessary because other
|
||||
/// endpoints silently treat any call with invalid auth as unauthenticated.
|
||||
|
@ -12,7 +12,7 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
|||
pub async fn validate_auth(
|
||||
req: HttpRequest,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let jwt = read_auth_token(&req)?;
|
||||
if let Some(jwt) = jwt {
|
||||
local_user_view_from_jwt(&jwt, &context).await?;
|
||||
|
|
|
@ -9,23 +9,23 @@ use lemmy_db_schema::{
|
|||
source::{
|
||||
email_verification::EmailVerification,
|
||||
local_user::{LocalUser, LocalUserUpdateForm},
|
||||
person::Person,
|
||||
},
|
||||
traits::Crud,
|
||||
RegistrationMode,
|
||||
};
|
||||
use lemmy_db_views::structs::SiteView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
pub async fn verify_email(
|
||||
data: Json<VerifyEmail>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
let token = data.token.clone();
|
||||
let verification = EmailVerification::read_for_token(&mut context.pool(), &token)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::TokenNotFound)?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::TokenNotFound)?;
|
||||
|
||||
let form = LocalUserUpdateForm {
|
||||
// necessary in case this is a new signup
|
||||
|
@ -36,7 +36,7 @@ pub async fn verify_email(
|
|||
};
|
||||
let local_user_id = verification.local_user_id;
|
||||
|
||||
let local_user = LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
|
||||
LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
|
||||
|
||||
EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?;
|
||||
|
||||
|
@ -44,8 +44,15 @@ pub async fn verify_email(
|
|||
if site_view.local_site.registration_mode == RegistrationMode::RequireApplication
|
||||
&& site_view.local_site.application_email_admins
|
||||
{
|
||||
let person = Person::read(&mut context.pool(), local_user.person_id).await?;
|
||||
send_new_applicant_email_to_admins(&person.name, &mut context.pool(), context.settings())
|
||||
let local_user = LocalUserView::read(&mut context.pool(), local_user_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
|
||||
send_new_applicant_email_to_admins(
|
||||
&local_user.person.name,
|
||||
&mut context.pool(),
|
||||
context.settings(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,16 +16,18 @@ use lemmy_db_schema::{
|
|||
PostFeatureType,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn feature_post(
|
||||
data: Json<FeaturePost>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PostResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let post_id = data.post_id;
|
||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||
let orig_post = Post::read(&mut context.pool(), post_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
check_community_mod_action(
|
||||
&local_user_view.person,
|
||||
|
|
|
@ -4,14 +4,19 @@ use lemmy_api_common::{
|
|||
post::{GetSiteMetadata, GetSiteMetadataResponse},
|
||||
request::fetch_link_metadata,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyResult},
|
||||
LemmyErrorType,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_link_metadata(
|
||||
data: Query<GetSiteMetadata>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<GetSiteMetadataResponse>, LemmyError> {
|
||||
let metadata = fetch_link_metadata(&data.url, false, &context).await?;
|
||||
) -> LemmyResult<Json<GetSiteMetadataResponse>> {
|
||||
let url = Url::parse(&data.url).with_lemmy_type(LemmyErrorType::InvalidUrl)?;
|
||||
let metadata = fetch_link_metadata(&url, &context).await?;
|
||||
|
||||
Ok(Json(GetSiteMetadataResponse { metadata }))
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
|||
use lemmy_api_common::{context::LemmyContext, post::HidePost, SuccessResponse};
|
||||
use lemmy_db_schema::source::post::PostHide;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -10,7 +10,7 @@ pub async fn hide_post(
|
|||
data: Json<HidePost>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let post_ids = HashSet::from_iter(data.post_ids.clone());
|
||||
|
||||
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
|||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -29,7 +29,7 @@ pub async fn like_post(
|
|||
data: Json<CreatePostLike>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PostResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
|
@ -38,7 +38,9 @@ pub async fn like_post(
|
|||
|
||||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
let post = Post::read(&mut context.pool(), post_id).await?;
|
||||
let post = Post::read(&mut context.pool(), post_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -66,14 +68,17 @@ pub async fn like_post(
|
|||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
||||
}
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
|
||||
let community = Community::read(&mut context.pool(), post.community_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
SendActivityData::LikePostOrComment {
|
||||
object_id: post.ap_id,
|
||||
actor: local_user_view.person.clone(),
|
||||
community: Community::read(&mut context.pool(), post.community_id).await?,
|
||||
community,
|
||||
score: data.score,
|
||||
},
|
||||
&context,
|
||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{source::post::Post, traits::Crud};
|
||||
use lemmy_db_views::structs::{LocalUserView, VoteView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
/// Lists likes for a post
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -14,8 +14,10 @@ pub async fn list_post_likes(
|
|||
data: Query<ListPostLikes>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListPostLikesResponse>, LemmyError> {
|
||||
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
||||
) -> LemmyResult<Json<ListPostLikesResponse>> {
|
||||
let post = Post::read(&mut context.pool(), data.post_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
is_mod_or_admin(
|
||||
&mut context.pool(),
|
||||
&local_user_view.person,
|
||||
|
|
|
@ -15,16 +15,18 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn lock_post(
|
||||
data: Json<LockPost>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PostResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let post_id = data.post_id;
|
||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||
let orig_post = Post::read(&mut context.pool(), post_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
check_community_mod_action(
|
||||
&local_user_view.person,
|
||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
|||
use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse};
|
||||
use lemmy_db_schema::source::post::PostRead;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -10,7 +10,7 @@ pub async fn mark_post_as_read(
|
|||
data: Json<MarkPostAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let post_ids = HashSet::from_iter(data.post_ids.clone());
|
||||
|
||||
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
|||
traits::Saveable,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PostView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn save_post(
|
||||
data: Json<SavePost>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PostResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let post_saved_form = PostSavedForm {
|
||||
post_id: data.post_id,
|
||||
person_id: local_user_view.person.id,
|
||||
|
@ -34,9 +34,10 @@ pub async fn save_post(
|
|||
|
||||
let post_id = data.post_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false).await?;
|
||||
let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
|
||||
Ok(Json(PostResponse { post_view }))
|
||||
|
|
|
@ -19,7 +19,7 @@ use lemmy_db_schema::{
|
|||
traits::Reportable,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PostReportView, PostView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
/// Creates a post report and notifies the moderators of the community
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -27,7 +27,7 @@ pub async fn create_post_report(
|
|||
data: Json<CreatePostReport>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PostReportResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PostReportResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
let reason = data.reason.trim().to_string();
|
||||
|
@ -35,7 +35,9 @@ pub async fn create_post_report(
|
|||
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_id = data.post_id;
|
||||
let post_view = PostView::read(&mut context.pool(), post_id, None, false).await?;
|
||||
let post_view = PostView::read(&mut context.pool(), post_id, None, false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -59,7 +61,9 @@ pub async fn create_post_report(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
||||
|
||||
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?;
|
||||
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
|
||||
|
||||
// Email the admins
|
||||
if local_site.reports_email_admins {
|
||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
|||
utils::check_community_mod_of_any_or_admin_action,
|
||||
};
|
||||
use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
/// Lists post reports for a community if an id is supplied
|
||||
/// or returns all post reports for communities a user moderates
|
||||
|
@ -14,7 +14,7 @@ pub async fn list_post_reports(
|
|||
data: Query<ListPostReports>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListPostReportsResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<ListPostReportsResponse>> {
|
||||
let community_id = data.community_id;
|
||||
let post_id = data.post_id;
|
||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
|
||||
use lemmy_db_views::structs::{LocalUserView, PostReportView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
/// Resolves or unresolves a post report and notifies the moderators of the community
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -14,10 +14,12 @@ pub async fn resolve_post_report(
|
|||
data: Json<ResolvePostReport>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PostReportResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PostReportResponse>> {
|
||||
let report_id = data.report_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||
let report = PostReportView::read(&mut context.pool(), report_id, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
|
||||
|
||||
let person_id = local_user_view.person.id;
|
||||
check_community_mod_action(
|
||||
|
@ -38,7 +40,9 @@ pub async fn resolve_post_report(
|
|||
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
|
||||
}
|
||||
|
||||
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
|
||||
|
||||
Ok(Json(PostReportResponse { post_report_view }))
|
||||
}
|
||||
|
|
|
@ -8,17 +8,19 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_pm_as_read(
|
||||
data: Json<MarkPrivateMessageAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PrivateMessageResponse>> {
|
||||
// Checking permissions
|
||||
let private_message_id = data.private_message_id;
|
||||
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
||||
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
|
||||
if local_user_view.person.id != orig_private_message.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdatePrivateMessage)?
|
||||
}
|
||||
|
@ -37,7 +39,9 @@ pub async fn mark_pm_as_read(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
|
||||
|
||||
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
|
||||
let view = PrivateMessageView::read(&mut context.pool(), private_message_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
|
||||
Ok(Json(PrivateMessageResponse {
|
||||
private_message_view: view,
|
||||
}))
|
||||
|
|
|
@ -14,14 +14,14 @@ use lemmy_db_schema::{
|
|||
traits::{Crud, Reportable},
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn create_pm_report(
|
||||
data: Json<CreatePrivateMessageReport>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PrivateMessageReportResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
let reason = data.reason.trim().to_string();
|
||||
|
@ -29,7 +29,9 @@ pub async fn create_pm_report(
|
|||
|
||||
let person_id = local_user_view.person.id;
|
||||
let private_message_id = data.private_message_id;
|
||||
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
||||
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
|
||||
|
||||
// Make sure that only the recipient of the private message can create a report
|
||||
if person_id != private_message.recipient_id {
|
||||
|
@ -47,8 +49,9 @@ pub async fn create_pm_report(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
||||
|
||||
let private_message_report_view =
|
||||
PrivateMessageReportView::read(&mut context.pool(), report.id).await?;
|
||||
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report.id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
|
||||
|
||||
// Email the admins
|
||||
if local_site.reports_email_admins {
|
||||
|
|
|
@ -8,14 +8,14 @@ use lemmy_db_views::{
|
|||
private_message_report_view::PrivateMessageReportQuery,
|
||||
structs::LocalUserView,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_pm_reports(
|
||||
data: Query<ListPrivateMessageReports>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListPrivateMessageReportsResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<ListPrivateMessageReportsResponse>> {
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||
|
|
|
@ -6,14 +6,14 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, traits::Reportable};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn resolve_pm_report(
|
||||
data: Json<ResolvePrivateMessageReport>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PrivateMessageReportResponse>> {
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let report_id = data.report_id;
|
||||
|
@ -28,8 +28,9 @@ pub async fn resolve_pm_report(
|
|||
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
|
||||
}
|
||||
|
||||
let private_message_report_view =
|
||||
PrivateMessageReportView::read(&mut context.pool(), report_id).await?;
|
||||
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
|
||||
|
||||
Ok(Json(PrivateMessageReportResponse {
|
||||
private_message_report_view,
|
||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
|||
traits::Blockable,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn block_instance(
|
||||
data: Json<BlockInstance>,
|
||||
local_user_view: LocalUserView,
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<BlockInstanceResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<BlockInstanceResponse>> {
|
||||
let instance_id = data.instance_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
if local_user_view.person.instance_id == instance_id {
|
||||
|
|
|
@ -5,13 +5,15 @@ use lemmy_api_common::{
|
|||
utils::build_federated_instances,
|
||||
};
|
||||
use lemmy_db_views::structs::SiteView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_federated_instances(
|
||||
context: Data<LemmyContext>,
|
||||
) -> Result<Json<GetFederatedInstancesResponse>, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
) -> LemmyResult<Json<GetFederatedInstancesResponse>> {
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
let federated_instances =
|
||||
build_federated_instances(&site_view.local_site, &mut context.pool()).await?;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use lemmy_db_schema::{
|
|||
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
|
||||
use lemmy_db_views_actor::structs::PersonView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorType},
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
VERSION,
|
||||
};
|
||||
|
||||
|
@ -22,7 +22,7 @@ use lemmy_utils::{
|
|||
pub async fn leave_admin(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetSiteResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetSiteResponse>> {
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
|
||||
|
@ -55,7 +55,9 @@ pub async fn leave_admin(
|
|||
ModAdd::create(&mut context.pool(), &form).await?;
|
||||
|
||||
// Reread site and admins
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
|
|
|
@ -4,21 +4,20 @@ use lemmy_api_common::{
|
|||
person::{ListMedia, ListMediaResponse},
|
||||
utils::is_admin,
|
||||
};
|
||||
use lemmy_db_schema::source::images::LocalImage;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_all_media(
|
||||
data: Query<ListMedia>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListMediaResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<ListMediaResponse>> {
|
||||
// Only let admins view all media
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let images = LocalImage::get_all(&mut context.pool(), page, limit).await?;
|
||||
let images = LocalImageView::get_all(&mut context.pool(), page, limit).await?;
|
||||
Ok(Json(ListMediaResponse { images }))
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use lemmy_db_views_moderator::structs::{
|
|||
ModTransferCommunityView,
|
||||
ModlogListParams,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use ModlogActionType::*;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -32,7 +32,7 @@ pub async fn get_mod_log(
|
|||
data: Query<GetModlog>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
) -> Result<Json<GetModlogResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetModlogResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
|
|
|
@ -15,21 +15,23 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn purge_comment(
|
||||
data: Json<PurgeComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Only let admin purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
|
||||
// Read the comment to get the post_id and community
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
let post_id = comment_view.comment.post_id;
|
||||
|
||||
|
|
|
@ -16,19 +16,21 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn purge_community(
|
||||
data: Json<PurgeCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Only let admin purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Read the community to get its images
|
||||
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||
let community = Community::read(&mut context.pool(), data.community_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
if let Some(banner) = &community.banner {
|
||||
purge_image_from_pictrs(banner, &context).await.ok();
|
||||
|
|
|
@ -16,18 +16,20 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn purge_person(
|
||||
data: Json<PurgePerson>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Only let admin purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let person = Person::read(&mut context.pool(), data.person_id).await?;
|
||||
let person = Person::read(&mut context.pool(), data.person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
ban_nonlocal_user_from_local_communities(
|
||||
&local_user_view,
|
||||
&person,
|
||||
|
|
|
@ -16,19 +16,21 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn purge_post(
|
||||
data: Json<PurgePost>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Only let admin purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Read the post to get the community_id
|
||||
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
||||
let post = Post::read(&mut context.pool(), data.post_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
// Purge image
|
||||
if let Some(url) = &post.url {
|
||||
|
|
|
@ -10,23 +10,23 @@ use lemmy_db_schema::{
|
|||
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::diesel_option_overwrite,
|
||||
utils::diesel_string_update,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
pub async fn approve_registration_application(
|
||||
data: Json<ApproveRegistrationApplication>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<RegistrationApplicationResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<RegistrationApplicationResponse>> {
|
||||
let app_id = data.id;
|
||||
|
||||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Update the registration with reason, admin_id
|
||||
let deny_reason = diesel_option_overwrite(data.deny_reason.clone());
|
||||
let deny_reason = diesel_string_update(data.deny_reason.as_deref());
|
||||
let app_form = RegistrationApplicationUpdateForm {
|
||||
admin_id: Some(Some(local_user_view.person.id)),
|
||||
deny_reason,
|
||||
|
@ -45,8 +45,9 @@ pub async fn approve_registration_application(
|
|||
LocalUser::update(&mut context.pool(), approved_user_id, &local_user_form).await?;
|
||||
|
||||
if data.approve {
|
||||
let approved_local_user_view =
|
||||
LocalUserView::read(&mut context.pool(), approved_user_id).await?;
|
||||
let approved_local_user_view = LocalUserView::read(&mut context.pool(), approved_user_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
|
||||
|
||||
if approved_local_user_view.local_user.email.is_some() {
|
||||
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
|
||||
|
@ -54,8 +55,9 @@ pub async fn approve_registration_application(
|
|||
}
|
||||
|
||||
// Read the view
|
||||
let registration_application =
|
||||
RegistrationApplicationView::read(&mut context.pool(), app_id).await?;
|
||||
let registration_application = RegistrationApplicationView::read(&mut context.pool(), app_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
||||
|
||||
Ok(Json(RegistrationApplicationResponse {
|
||||
registration_application,
|
||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_views::{
|
|||
registration_application_view::RegistrationApplicationQuery,
|
||||
structs::LocalUserView,
|
||||
};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
/// Lists registration applications, filterable by undenied only.
|
||||
pub async fn list_registration_applications(
|
||||
data: Query<ListRegistrationApplications>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<ListRegistrationApplicationsResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<ListRegistrationApplicationsResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
|
|
|
@ -6,12 +6,12 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::source::local_site::LocalSite;
|
||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn get_unread_registration_application_count(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<GetUnreadRegistrationApplicationCountResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<GetUnreadRegistrationApplicationCountResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
// Only let admins do this
|
||||
|
|
|
@ -25,7 +25,7 @@ full = [
|
|||
"lemmy_db_views_moderator/full",
|
||||
"lemmy_utils/full",
|
||||
"activitypub_federation",
|
||||
"encoding",
|
||||
"encoding_rs",
|
||||
"reqwest-middleware",
|
||||
"webpage",
|
||||
"ts-rs",
|
||||
|
@ -66,13 +66,13 @@ actix-web = { workspace = true, optional = true }
|
|||
enum-map = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
mime = { version = "0.3.17", optional = true }
|
||||
webpage = { version = "1.6", default-features = false, features = [
|
||||
webpage = { version = "2.0", default-features = false, features = [
|
||||
"serde",
|
||||
], optional = true }
|
||||
encoding = { version = "0.2.33", optional = true }
|
||||
jsonwebtoken = { version = "8.3.0", optional = true }
|
||||
encoding_rs = { version = "0.8.34", optional = true }
|
||||
jsonwebtoken = { version = "9.3.0", optional = true }
|
||||
# necessary for wasmt compilation
|
||||
getrandom = { version = "0.2.12", features = ["js"] }
|
||||
getrandom = { version = "0.2.15", features = ["js"] }
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["getrandom"]
|
||||
|
|
|
@ -19,15 +19,15 @@ use lemmy_db_schema::{
|
|||
comment_reply::{CommentReply, CommentReplyInsertForm},
|
||||
person::Person,
|
||||
person_mention::{PersonMention, PersonMentionInsertForm},
|
||||
post::Post,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
|
||||
use lemmy_db_views_actor::structs::CommunityView;
|
||||
use lemmy_utils::{
|
||||
error::LemmyError,
|
||||
error::LemmyResult,
|
||||
utils::{markdown::markdown_to_html, mention::MentionData},
|
||||
LemmyErrorType,
|
||||
};
|
||||
|
||||
pub async fn build_comment_response(
|
||||
|
@ -35,9 +35,11 @@ pub async fn build_comment_response(
|
|||
comment_id: CommentId,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
recipient_ids: Vec<LocalUserId>,
|
||||
) -> Result<CommentResponse, LemmyError> {
|
||||
) -> LemmyResult<CommentResponse> {
|
||||
let person_id = local_user_view.map(|l| l.person.id);
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id).await?;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
Ok(CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids,
|
||||
|
@ -48,7 +50,7 @@ pub async fn build_community_response(
|
|||
context: &LemmyContext,
|
||||
local_user_view: LocalUserView,
|
||||
community_id: CommunityId,
|
||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommunityResponse>> {
|
||||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
|
||||
.await
|
||||
.is_ok();
|
||||
|
@ -59,7 +61,8 @@ pub async fn build_community_response(
|
|||
Some(person_id),
|
||||
is_mod_or_admin,
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
||||
|
||||
Ok(Json(CommunityResponse {
|
||||
|
@ -73,7 +76,7 @@ pub async fn build_post_response(
|
|||
community_id: CommunityId,
|
||||
person: &Person,
|
||||
post_id: PostId,
|
||||
) -> Result<Json<PostResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id)
|
||||
.await
|
||||
.is_ok();
|
||||
|
@ -83,7 +86,8 @@ pub async fn build_post_response(
|
|||
Some(person.id),
|
||||
is_mod_or_admin,
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
Ok(Json(PostResponse { post_view }))
|
||||
}
|
||||
|
||||
|
@ -91,16 +95,21 @@ pub async fn build_post_response(
|
|||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send_local_notifs(
|
||||
mentions: Vec<MentionData>,
|
||||
comment: &Comment,
|
||||
comment_id: CommentId,
|
||||
person: &Person,
|
||||
post: &Post,
|
||||
do_send_email: bool,
|
||||
context: &LemmyContext,
|
||||
) -> Result<Vec<LocalUserId>, LemmyError> {
|
||||
) -> LemmyResult<Vec<LocalUserId>> {
|
||||
let mut recipient_ids = Vec::new();
|
||||
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||
|
||||
let community_id = post.community_id;
|
||||
// Read the comment view to get extra info
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let comment = comment_view.comment;
|
||||
let post = comment_view.post;
|
||||
let community = comment_view.community;
|
||||
|
||||
// Send the local mentions
|
||||
for mention in mentions
|
||||
|
@ -109,15 +118,16 @@ pub async fn send_local_notifs(
|
|||
{
|
||||
let mention_name = mention.name.clone();
|
||||
let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await;
|
||||
if let Ok(mention_user_view) = user_view {
|
||||
if let Ok(Some(mention_user_view)) = user_view {
|
||||
// TODO
|
||||
// At some point, make it so you can't tag the parent creator either
|
||||
// Potential duplication of notifications, one for reply and the other for mention, is handled below by checking recipient ids
|
||||
// Potential duplication of notifications, one for reply and the other for mention, is handled
|
||||
// below by checking recipient ids
|
||||
recipient_ids.push(mention_user_view.local_user.id);
|
||||
|
||||
let user_mention_form = PersonMentionInsertForm {
|
||||
recipient_id: mention_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
comment_id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
|
@ -144,7 +154,9 @@ pub async fn send_local_notifs(
|
|||
|
||||
// Send comment_reply to the parent commenter / poster
|
||||
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
|
||||
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
// Get the parent commenter local_user
|
||||
let parent_creator_id = parent_comment.creator_id;
|
||||
|
@ -152,8 +164,9 @@ pub async fn send_local_notifs(
|
|||
let check_blocks = check_person_instance_community_block(
|
||||
person.id,
|
||||
parent_creator_id,
|
||||
person.instance_id,
|
||||
community_id,
|
||||
// Only block from the community's instance_id
|
||||
community.instance_id,
|
||||
community.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await
|
||||
|
@ -162,7 +175,7 @@ pub async fn send_local_notifs(
|
|||
// Don't send a notif to yourself
|
||||
if parent_comment.creator_id != person.id && !check_blocks {
|
||||
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
|
||||
if let Ok(parent_user_view) = user_view {
|
||||
if let Ok(Some(parent_user_view)) = user_view {
|
||||
// Don't duplicate notif if already mentioned by checking recipient ids
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
@ -194,11 +207,13 @@ pub async fn send_local_notifs(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Use the post creator to check blocks
|
||||
let check_blocks = check_person_instance_community_block(
|
||||
person.id,
|
||||
post.creator_id,
|
||||
person.instance_id,
|
||||
community_id,
|
||||
// Only block from the community's instance_id
|
||||
community.instance_id,
|
||||
community.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await
|
||||
|
@ -207,7 +222,7 @@ pub async fn send_local_notifs(
|
|||
if post.creator_id != person.id && !check_blocks {
|
||||
let creator_id = post.creator_id;
|
||||
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
|
||||
if let Ok(parent_user_view) = parent_user {
|
||||
if let Ok(Some(parent_user_view)) = parent_user {
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::{context::LemmyContext, sensitive::Sensitive};
|
||||
use crate::context::LemmyContext;
|
||||
use actix_web::{http::header::USER_AGENT, HttpRequest};
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::LocalUserId,
|
||||
sensitive::SensitiveString,
|
||||
source::login_token::{LoginToken, LoginTokenCreateForm},
|
||||
};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
@ -40,7 +41,7 @@ impl Claims {
|
|||
user_id: LocalUserId,
|
||||
req: HttpRequest,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Sensitive<String>> {
|
||||
) -> LemmyResult<SensitiveString> {
|
||||
let hostname = context.settings().hostname.clone();
|
||||
let my_claims = Claims {
|
||||
sub: user_id.0.to_string(),
|
||||
|
@ -50,7 +51,7 @@ impl Claims {
|
|||
|
||||
let secret = &context.secret().jwt_secret;
|
||||
let key = EncodingKey::from_secret(secret.as_ref());
|
||||
let token = encode(&Header::default(), &my_claims, &key)?;
|
||||
let token: SensitiveString = encode(&Header::default(), &my_claims, &key)?.into();
|
||||
let ip = req
|
||||
.connection_info()
|
||||
.realip_remote_addr()
|
||||
|
@ -67,7 +68,7 @@ impl Claims {
|
|||
user_agent,
|
||||
};
|
||||
LoginToken::create(&mut context.pool(), form).await?;
|
||||
Ok(Sensitive::new(token))
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +100,7 @@ mod tests {
|
|||
async fn test_should_not_validate_user_token_after_password_change() {
|
||||
let pool_ = build_db_pool_for_tests().await;
|
||||
let pool = &mut (&pool_).into();
|
||||
let secret = Secret::init(pool).await.unwrap();
|
||||
let secret = Secret::init(pool).await.unwrap().unwrap();
|
||||
let context = LemmyContext::create(
|
||||
pool_.clone(),
|
||||
ClientBuilder::new(Client::default()).build(),
|
||||
|
@ -111,11 +112,7 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let new_person = PersonInsertForm::builder()
|
||||
.name("Gerry9812".into())
|
||||
.public_key("pubkey".to_string())
|
||||
.instance_id(inserted_instance.id)
|
||||
.build();
|
||||
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
|
||||
|
||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ impl LemmyContext {
|
|||
let client = ClientBuilder::new(client).build();
|
||||
let secret = Secret {
|
||||
id: 0,
|
||||
jwt_secret: String::new(),
|
||||
jwt_secret: String::new().into(),
|
||||
};
|
||||
|
||||
let rate_limit_cell = RateLimitCell::with_test_config();
|
||||
|
|
|
@ -14,7 +14,6 @@ pub mod private_message;
|
|||
pub mod request;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod send_activity;
|
||||
pub mod sensitive;
|
||||
pub mod site;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod utils;
|
||||
|
@ -27,7 +26,7 @@ pub extern crate lemmy_utils;
|
|||
|
||||
pub use lemmy_utils::LemmyErrorType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use std::{cmp::min, time::Duration};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(ts_rs::TS))]
|
||||
|
@ -43,7 +42,39 @@ impl Default for SuccessResponse {
|
|||
}
|
||||
}
|
||||
|
||||
/// how long to sleep based on how many retries have already happened
|
||||
// TODO: use from_days once stabilized
|
||||
// https://github.com/rust-lang/rust/issues/120301
|
||||
const DAY: Duration = Duration::from_secs(24 * 60 * 60);
|
||||
|
||||
/// Calculate how long to sleep until next federation send based on how many
|
||||
/// retries have already happened. Uses exponential backoff with maximum of one day. The first
|
||||
/// error is ignored.
|
||||
pub fn federate_retry_sleep_duration(retry_count: i32) -> Duration {
|
||||
Duration::from_secs_f64(2.0_f64.powf(f64::from(retry_count)))
|
||||
debug_assert!(retry_count != 0);
|
||||
if retry_count == 1 {
|
||||
return Duration::from_secs(0);
|
||||
}
|
||||
let retry_count = retry_count - 1;
|
||||
let pow = 1.25_f64.powf(retry_count.into());
|
||||
let pow = Duration::try_from_secs_f64(pow).unwrap_or(DAY);
|
||||
min(DAY, pow)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_federate_retry_sleep_duration() {
|
||||
assert_eq!(Duration::from_secs(0), federate_retry_sleep_duration(1));
|
||||
assert_eq!(
|
||||
Duration::new(1, 250000000),
|
||||
federate_retry_sleep_duration(2)
|
||||
);
|
||||
assert_eq!(
|
||||
Duration::new(2, 441406250),
|
||||
federate_retry_sleep_duration(5)
|
||||
);
|
||||
assert_eq!(DAY, federate_retry_sleep_duration(100));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use crate::sensitive::Sensitive;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||
source::{images::LocalImage, site::Site},
|
||||
sensitive::SensitiveString,
|
||||
source::site::Site,
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
PostListingMode,
|
||||
SortType,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, PostView};
|
||||
use lemmy_db_views::structs::{CommentView, LocalImageView, PostView};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommentReplyView,
|
||||
CommunityModeratorView,
|
||||
|
@ -25,8 +25,8 @@ use ts_rs::TS;
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Logging into lemmy.
|
||||
pub struct Login {
|
||||
pub username_or_email: Sensitive<String>,
|
||||
pub password: Sensitive<String>,
|
||||
pub username_or_email: SensitiveString,
|
||||
pub password: SensitiveString,
|
||||
/// May be required, if totp is enabled for their account.
|
||||
pub totp_2fa_token: Option<String>,
|
||||
}
|
||||
|
@ -38,11 +38,11 @@ pub struct Login {
|
|||
/// Register / Sign up to lemmy.
|
||||
pub struct Register {
|
||||
pub username: String,
|
||||
pub password: Sensitive<String>,
|
||||
pub password_verify: Sensitive<String>,
|
||||
pub show_nsfw: bool,
|
||||
pub password: SensitiveString,
|
||||
pub password_verify: SensitiveString,
|
||||
pub show_nsfw: Option<bool>,
|
||||
/// email is mandatory if email verification is enabled on the server
|
||||
pub email: Option<Sensitive<String>>,
|
||||
pub email: Option<SensitiveString>,
|
||||
/// The UUID of the captcha item.
|
||||
pub captcha_uuid: Option<String>,
|
||||
/// Your captcha answer.
|
||||
|
@ -99,7 +99,7 @@ pub struct SaveUserSettings {
|
|||
/// Your display name, which can contain strange characters, and does not need to be unique.
|
||||
pub display_name: Option<String>,
|
||||
/// Your email.
|
||||
pub email: Option<Sensitive<String>>,
|
||||
pub email: Option<SensitiveString>,
|
||||
/// Your bio / info, in markdown.
|
||||
pub bio: Option<String>,
|
||||
/// Your matrix user id. Ex: @my_user:matrix.org
|
||||
|
@ -124,7 +124,8 @@ pub struct SaveUserSettings {
|
|||
pub post_listing_mode: Option<PostListingMode>,
|
||||
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
|
||||
pub enable_keyboard_navigation: Option<bool>,
|
||||
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or should be paused
|
||||
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or
|
||||
/// should be paused
|
||||
pub enable_animated_images: Option<bool>,
|
||||
/// Whether to auto-collapse bot comments.
|
||||
pub collapse_bot_comments: Option<bool>,
|
||||
|
@ -140,9 +141,9 @@ pub struct SaveUserSettings {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Changes your account password.
|
||||
pub struct ChangePassword {
|
||||
pub new_password: Sensitive<String>,
|
||||
pub new_password_verify: Sensitive<String>,
|
||||
pub old_password: Sensitive<String>,
|
||||
pub new_password: SensitiveString,
|
||||
pub new_password_verify: SensitiveString,
|
||||
pub old_password: SensitiveString,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
|
@ -151,8 +152,9 @@ pub struct ChangePassword {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A response for your login.
|
||||
pub struct LoginResponse {
|
||||
/// This is None in response to `Register` if email verification is enabled, or the server requires registration applications.
|
||||
pub jwt: Option<Sensitive<String>>,
|
||||
/// This is None in response to `Register` if email verification is enabled, or the server
|
||||
/// requires registration applications.
|
||||
pub jwt: Option<SensitiveString>,
|
||||
/// If registration applications are required, this will return true for a signup response.
|
||||
pub registration_created: bool,
|
||||
/// If email verifications are required, this will return true for a signup response.
|
||||
|
@ -340,7 +342,7 @@ pub struct CommentReplyResponse {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Delete your account.
|
||||
pub struct DeleteAccount {
|
||||
pub password: Sensitive<String>,
|
||||
pub password: SensitiveString,
|
||||
pub delete_content: bool,
|
||||
}
|
||||
|
||||
|
@ -349,7 +351,7 @@ pub struct DeleteAccount {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Reset your password via email.
|
||||
pub struct PasswordReset {
|
||||
pub email: Sensitive<String>,
|
||||
pub email: SensitiveString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -357,9 +359,9 @@ pub struct PasswordReset {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Change your password after receiving a reset request.
|
||||
pub struct PasswordChangeAfterReset {
|
||||
pub token: Sensitive<String>,
|
||||
pub password: Sensitive<String>,
|
||||
pub password_verify: Sensitive<String>,
|
||||
pub token: SensitiveString,
|
||||
pub password: SensitiveString,
|
||||
pub password_verify: SensitiveString,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
|
@ -405,7 +407,7 @@ pub struct VerifyEmail {
|
|||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct GenerateTotpSecretResponse {
|
||||
pub totp_secret_url: Sensitive<String>,
|
||||
pub totp_secret_url: SensitiveString,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -437,5 +439,5 @@ pub struct ListMedia {
|
|||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct ListMediaResponse {
|
||||
pub images: Vec<LocalImage>,
|
||||
pub images: Vec<LocalImageView>,
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -20,8 +19,7 @@ use url::Url;
|
|||
pub struct CreatePost {
|
||||
pub name: String,
|
||||
pub community_id: CommunityId,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub url: Option<Url>,
|
||||
pub url: Option<String>,
|
||||
/// An optional body for the post in markdown.
|
||||
pub body: Option<String>,
|
||||
/// An optional alt_text, usable for image posts.
|
||||
|
@ -30,9 +28,8 @@ pub struct CreatePost {
|
|||
pub honeypot: Option<String>,
|
||||
pub nsfw: Option<bool>,
|
||||
pub language_id: Option<LanguageId>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
/// Instead of fetching a thumbnail, use a custom one.
|
||||
pub custom_thumbnail: Option<Url>,
|
||||
pub custom_thumbnail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -114,17 +111,15 @@ pub struct CreatePostLike {
|
|||
pub struct EditPost {
|
||||
pub post_id: PostId,
|
||||
pub name: Option<String>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub url: Option<Url>,
|
||||
pub url: Option<String>,
|
||||
/// An optional body for the post in markdown.
|
||||
pub body: Option<String>,
|
||||
/// An optional alt_text, usable for image posts.
|
||||
pub alt_text: Option<String>,
|
||||
pub nsfw: Option<bool>,
|
||||
pub language_id: Option<LanguageId>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
/// Instead of fetching a thumbnail, use a custom one.
|
||||
pub custom_thumbnail: Option<Url>,
|
||||
pub custom_thumbnail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -249,8 +244,7 @@ pub struct ListPostReportsResponse {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Get metadata for a given site.
|
||||
pub struct GetSiteMetadata {
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub url: Url,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -270,8 +264,6 @@ pub struct LinkMetadata {
|
|||
#[serde(flatten)]
|
||||
pub opengraph_data: OpenGraphData,
|
||||
pub content_type: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub thumbnail: Option<DbUrl>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
|
|
|
@ -3,10 +3,11 @@ use crate::{
|
|||
lemmy_db_schema::traits::Crud,
|
||||
post::{LinkMetadata, OpenGraphData},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{local_site_opt_to_sensitive, proxy_image_link, proxy_image_link_opt_apub},
|
||||
utils::{local_site_opt_to_sensitive, proxy_image_link},
|
||||
};
|
||||
use activitypub_federation::config::Data;
|
||||
use encoding::{all::encodings, DecoderTrap};
|
||||
use chrono::{DateTime, Utc};
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::DbUrl,
|
||||
source::{
|
||||
|
@ -16,16 +17,15 @@ use lemmy_db_schema::{
|
|||
},
|
||||
};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorType},
|
||||
error::{LemmyError, LemmyErrorType, LemmyResult},
|
||||
settings::structs::{PictrsImageMode, Settings},
|
||||
spawn_try_task,
|
||||
REQWEST_TIMEOUT,
|
||||
VERSION,
|
||||
};
|
||||
use mime::Mime;
|
||||
use reqwest::{header::CONTENT_TYPE, Client, ClientBuilder};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
use urlencoding::encode;
|
||||
|
@ -42,11 +42,7 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder {
|
|||
|
||||
/// Fetches metadata for the given link and optionally generates thumbnail.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn fetch_link_metadata(
|
||||
url: &Url,
|
||||
generate_thumbnail: bool,
|
||||
context: &LemmyContext,
|
||||
) -> Result<LinkMetadata, LemmyError> {
|
||||
pub async fn fetch_link_metadata(url: &Url, context: &LemmyContext) -> LemmyResult<LinkMetadata> {
|
||||
info!("Fetching site metadata for url: {}", url);
|
||||
let response = context.client().get(url.as_str()).send().await?;
|
||||
|
||||
|
@ -63,57 +59,56 @@ pub async fn fetch_link_metadata(
|
|||
let opengraph_data = extract_opengraph_data(&html_bytes, url)
|
||||
.map_err(|e| info!("{e}"))
|
||||
.unwrap_or_default();
|
||||
let thumbnail =
|
||||
extract_thumbnail_from_opengraph_data(url, &opengraph_data, generate_thumbnail, context).await;
|
||||
|
||||
Ok(LinkMetadata {
|
||||
opengraph_data,
|
||||
content_type: content_type.map(|c| c.to_string()),
|
||||
thumbnail,
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn fetch_link_metadata_opt(
|
||||
url: Option<&Url>,
|
||||
generate_thumbnail: bool,
|
||||
context: &LemmyContext,
|
||||
) -> LinkMetadata {
|
||||
match &url {
|
||||
Some(url) => fetch_link_metadata(url, generate_thumbnail, context)
|
||||
.await
|
||||
.unwrap_or_default(),
|
||||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
/// Generate post thumbnail in background task, because some sites can be very slow to respond.
|
||||
/// Generates and saves a post thumbnail and metadata.
|
||||
///
|
||||
/// Takes a callback to generate a send activity task, so that post can be federated with metadata.
|
||||
pub fn generate_post_link_metadata(
|
||||
///
|
||||
/// TODO: `federated_thumbnail` param can be removed once we federate full metadata and can
|
||||
/// write it to db directly, without calling this function.
|
||||
/// https://github.com/LemmyNet/lemmy/issues/4598
|
||||
pub async fn generate_post_link_metadata(
|
||||
post: Post,
|
||||
custom_thumbnail: Option<Url>,
|
||||
send_activity: impl FnOnce(Post) -> Option<SendActivityData> + Send + 'static,
|
||||
local_site: Option<LocalSite>,
|
||||
context: Data<LemmyContext>,
|
||||
) {
|
||||
spawn_try_task(async move {
|
||||
let allow_sensitive = local_site_opt_to_sensitive(&local_site);
|
||||
let page_is_sensitive = post.nsfw;
|
||||
let allow_generate_thumbnail = allow_sensitive || !page_is_sensitive;
|
||||
let mut thumbnail_url = custom_thumbnail.or_else(|| post.thumbnail_url.map(Into::into));
|
||||
let do_generate_thumbnail = thumbnail_url.is_none() && allow_generate_thumbnail;
|
||||
) -> LemmyResult<()> {
|
||||
let metadata = match &post.url {
|
||||
Some(url) => fetch_link_metadata(url, &context).await.unwrap_or_default(),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
// Generate local thumbnail only if no thumbnail was federated and 'sensitive' attributes allow it.
|
||||
let metadata = fetch_link_metadata_opt(
|
||||
post.url.map(Into::into).as_ref(),
|
||||
do_generate_thumbnail,
|
||||
&context,
|
||||
)
|
||||
.await;
|
||||
if let Some(thumbnail_url_) = metadata.thumbnail {
|
||||
thumbnail_url = Some(thumbnail_url_.into());
|
||||
}
|
||||
let thumbnail_url = proxy_image_link_opt_apub(thumbnail_url, &context).await?;
|
||||
let is_image_post = metadata
|
||||
.content_type
|
||||
.as_ref()
|
||||
.is_some_and(|content_type| content_type.starts_with("image"));
|
||||
|
||||
// Decide if we are allowed to generate local thumbnail
|
||||
let allow_sensitive = local_site_opt_to_sensitive(&local_site);
|
||||
let allow_generate_thumbnail = allow_sensitive || !post.nsfw;
|
||||
|
||||
let image_url = if is_image_post {
|
||||
post.url
|
||||
} else {
|
||||
metadata.opengraph_data.image.clone()
|
||||
};
|
||||
|
||||
let thumbnail_url = if let (false, Some(url)) = (is_image_post, custom_thumbnail) {
|
||||
proxy_image_link(url, &context).await.ok()
|
||||
} else if let (true, Some(url)) = (allow_generate_thumbnail, image_url) {
|
||||
generate_pictrs_thumbnail(&url, &context)
|
||||
.await
|
||||
.ok()
|
||||
.map(Into::into)
|
||||
} else {
|
||||
metadata.opengraph_data.image.clone()
|
||||
};
|
||||
|
||||
let form = PostUpdateForm {
|
||||
embed_title: Some(metadata.opengraph_data.title),
|
||||
|
@ -128,36 +123,21 @@ pub fn generate_post_link_metadata(
|
|||
ActivityChannel::submit_activity(send_activity, &context).await?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
/// Extract site metadata from HTML Opengraph attributes.
|
||||
fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result<OpenGraphData, LemmyError> {
|
||||
fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> LemmyResult<OpenGraphData> {
|
||||
let html = String::from_utf8_lossy(html_bytes);
|
||||
|
||||
// Make sure the first line is doctype html
|
||||
let first_line = html
|
||||
.trim_start()
|
||||
.lines()
|
||||
.next()
|
||||
.ok_or(LemmyErrorType::NoLinesInHtml)?
|
||||
.to_lowercase();
|
||||
|
||||
if !first_line.starts_with("<!doctype html") {
|
||||
Err(LemmyErrorType::SiteMetadataPageIsNotDoctypeHtml)?
|
||||
}
|
||||
|
||||
let mut page = HTML::from_string(html.to_string(), None)?;
|
||||
|
||||
// If the web page specifies that it isn't actually UTF-8, re-decode the received bytes with the
|
||||
// proper encoding. If the specified encoding cannot be found, fall back to the original UTF-8
|
||||
// version.
|
||||
if let Some(charset) = page.meta.get("charset") {
|
||||
if charset.to_lowercase() != "utf-8" {
|
||||
if let Some(encoding_ref) = encodings().iter().find(|e| e.name() == charset) {
|
||||
if let Ok(html_with_encoding) = encoding_ref.decode(html_bytes, DecoderTrap::Replace) {
|
||||
page = HTML::from_string(html_with_encoding, None)?;
|
||||
}
|
||||
if charset != UTF_8.name() {
|
||||
if let Some(encoding) = Encoding::for_label(charset.as_bytes()) {
|
||||
page = HTML::from_string(encoding.decode(html_bytes).0.into(), None)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,41 +176,40 @@ fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result<OpenGraphData,
|
|||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn extract_thumbnail_from_opengraph_data(
|
||||
url: &Url,
|
||||
opengraph_data: &OpenGraphData,
|
||||
generate_thumbnail: bool,
|
||||
context: &LemmyContext,
|
||||
) -> Option<DbUrl> {
|
||||
if generate_thumbnail {
|
||||
let image_url = opengraph_data
|
||||
.image
|
||||
.as_ref()
|
||||
.map(DbUrl::inner)
|
||||
.unwrap_or(url);
|
||||
generate_pictrs_thumbnail(image_url, context)
|
||||
.await
|
||||
.ok()
|
||||
.map(Into::into)
|
||||
} else {
|
||||
opengraph_data.image.clone()
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct PictrsResponse {
|
||||
pub files: Option<Vec<PictrsFile>>,
|
||||
pub msg: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct PictrsFile {
|
||||
pub file: String,
|
||||
pub delete_token: String,
|
||||
pub details: PictrsFileDetails,
|
||||
}
|
||||
|
||||
impl PictrsFile {
|
||||
pub fn thumbnail_url(&self, protocol_and_hostname: &str) -> Result<Url, url::ParseError> {
|
||||
Url::parse(&format!(
|
||||
"{protocol_and_hostname}/pictrs/image/{}",
|
||||
self.file
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct PictrsResponse {
|
||||
files: Vec<PictrsFile>,
|
||||
msg: String,
|
||||
/// Stores extra details about a Pictrs image.
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct PictrsFileDetails {
|
||||
/// In pixels
|
||||
pub width: u16,
|
||||
/// In pixels
|
||||
pub height: u16,
|
||||
pub content_type: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct PictrsFile {
|
||||
file: String,
|
||||
delete_token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct PictrsPurgeResponse {
|
||||
msg: String,
|
||||
}
|
||||
|
@ -240,10 +219,7 @@ struct PictrsPurgeResponse {
|
|||
/// - It might fail due to image being not local
|
||||
/// - It might not be an image
|
||||
/// - Pictrs might not be set up
|
||||
pub async fn purge_image_from_pictrs(
|
||||
image_url: &Url,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
pub async fn purge_image_from_pictrs(image_url: &Url, context: &LemmyContext) -> LemmyResult<()> {
|
||||
is_image_content_type(context.client(), image_url).await?;
|
||||
|
||||
let alias = image_url
|
||||
|
@ -278,7 +254,7 @@ pub async fn delete_image_from_pictrs(
|
|||
alias: &str,
|
||||
delete_token: &str,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let pictrs_config = context.settings().pictrs_config()?;
|
||||
let url = format!(
|
||||
"{}image/delete/{}/{}",
|
||||
|
@ -296,15 +272,16 @@ pub async fn delete_image_from_pictrs(
|
|||
|
||||
/// Retrieves the image with local pict-rs and generates a thumbnail. Returns the thumbnail url.
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn generate_pictrs_thumbnail(
|
||||
image_url: &Url,
|
||||
context: &LemmyContext,
|
||||
) -> Result<Url, LemmyError> {
|
||||
async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> LemmyResult<Url> {
|
||||
let pictrs_config = context.settings().pictrs_config()?;
|
||||
|
||||
if pictrs_config.image_mode() == PictrsImageMode::ProxyAllImages {
|
||||
return Ok(proxy_image_link(image_url.clone(), context).await?.into());
|
||||
match pictrs_config.image_mode() {
|
||||
PictrsImageMode::None => return Ok(image_url.clone()),
|
||||
PictrsImageMode::ProxyAllImages => {
|
||||
return Ok(proxy_image_link(image_url.clone(), context).await?.into())
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
// fetch remote non-pictrs images for persistent thumbnail link
|
||||
// TODO: should limit size once supported by pictrs
|
||||
|
@ -314,38 +291,39 @@ async fn generate_pictrs_thumbnail(
|
|||
encode(image_url.as_str())
|
||||
);
|
||||
|
||||
let response = context
|
||||
let res = context
|
||||
.client()
|
||||
.get(&fetch_url)
|
||||
.timeout(REQWEST_TIMEOUT)
|
||||
.send()
|
||||
.await?
|
||||
.json::<PictrsResponse>()
|
||||
.await?;
|
||||
|
||||
let response: PictrsResponse = response.json().await?;
|
||||
let files = res.files.unwrap_or_default();
|
||||
|
||||
let image = files
|
||||
.first()
|
||||
.ok_or(LemmyErrorType::PictrsResponseError(res.msg))?;
|
||||
|
||||
if response.msg == "ok" {
|
||||
let thumbnail_url = Url::parse(&format!(
|
||||
"{}/pictrs/image/{}",
|
||||
context.settings().get_protocol_and_hostname(),
|
||||
response.files.first().expect("missing pictrs file").file
|
||||
))?;
|
||||
for uploaded_image in response.files {
|
||||
let form = LocalImageForm {
|
||||
// This is none because its an internal request.
|
||||
// IE, a local user shouldn't get to delete the thumbnails for their link posts
|
||||
local_user_id: None,
|
||||
pictrs_alias: uploaded_image.file.to_string(),
|
||||
pictrs_delete_token: uploaded_image.delete_token.to_string(),
|
||||
pictrs_alias: image.file.clone(),
|
||||
pictrs_delete_token: image.delete_token.clone(),
|
||||
};
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?;
|
||||
|
||||
LocalImage::create(&mut context.pool(), &form).await?;
|
||||
}
|
||||
|
||||
Ok(thumbnail_url)
|
||||
} else {
|
||||
Err(LemmyErrorType::PictrsResponseError(response.msg))?
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: get rid of this by reading content type from db
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Result<(), LemmyError> {
|
||||
async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> LemmyResult<()> {
|
||||
let response = client.get(url.as_str()).send().await?;
|
||||
if response
|
||||
.headers()
|
||||
|
@ -360,16 +338,19 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Resu
|
|||
}
|
||||
}
|
||||
|
||||
/// When adding a new avatar or similar image, delete the old one.
|
||||
/// When adding a new avatar, banner or similar image, delete the old one.
|
||||
pub async fn replace_image(
|
||||
new_image: &Option<String>,
|
||||
new_image: &Option<Option<DbUrl>>,
|
||||
old_image: &Option<DbUrl>,
|
||||
context: &Data<LemmyContext>,
|
||||
) -> Result<(), LemmyError> {
|
||||
if new_image.is_some() {
|
||||
) -> LemmyResult<()> {
|
||||
if let (Some(Some(new_image)), Some(old_image)) = (new_image, old_image) {
|
||||
// Note: Oftentimes front ends will include the current image in the form.
|
||||
// In this case, deleting `old_image` would also be deletion of `new_image`,
|
||||
// so the deletion must be skipped for the image to be kept.
|
||||
if new_image != old_image {
|
||||
// Ignore errors because image may be stored externally.
|
||||
if let Some(avatar) = &old_image {
|
||||
let image = LocalImage::delete_by_url(&mut context.pool(), avatar)
|
||||
let image = LocalImage::delete_by_url(&mut context.pool(), old_image)
|
||||
.await
|
||||
.ok();
|
||||
if let Some(image) = image {
|
||||
|
@ -399,9 +380,7 @@ mod tests {
|
|||
async fn test_link_metadata() {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ").unwrap();
|
||||
let sample_res = fetch_link_metadata(&sample_url, false, &context)
|
||||
.await
|
||||
.unwrap();
|
||||
let sample_res = fetch_link_metadata(&sample_url, &context).await.unwrap();
|
||||
assert_eq!(
|
||||
Some("FAQ · Wiki · IzzyOnDroid / repo · GitLab".to_string()),
|
||||
sample_res.opengraph_data.title
|
||||
|
@ -423,17 +402,8 @@ mod tests {
|
|||
Some(mime::TEXT_HTML_UTF_8.to_string()),
|
||||
sample_res.content_type
|
||||
);
|
||||
assert!(sample_res.thumbnail.is_some());
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_pictshare() {
|
||||
// let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg");
|
||||
// assert!(res.is_ok());
|
||||
// let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu");
|
||||
// assert!(res_other.is_err());
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_resolve_image_url() {
|
||||
// url that lists the opengraph fields
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
#[cfg(feature = "full")]
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Default)]
|
||||
#[serde(transparent)]
|
||||
pub struct Sensitive<T>(T);
|
||||
|
||||
impl<T> Sensitive<T> {
|
||||
pub fn new(item: T) -> Self {
|
||||
Sensitive(item)
|
||||
}
|
||||
pub fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Sensitive<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Sensitive").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Sensitive<T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Sensitive<String> {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Sensitive<String> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Sensitive<Vec<u8>> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for Sensitive<T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<str> for Sensitive<String> {
|
||||
fn as_mut(&mut self) -> &mut str {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Sensitive<String> {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Sensitive<String> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Sensitive<T> {
|
||||
fn from(t: T) -> Self {
|
||||
Sensitive(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Sensitive<String> {
|
||||
fn from(s: &str) -> Self {
|
||||
Sensitive(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<T> for Sensitive<T> {
|
||||
fn borrow(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for Sensitive<String> {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "full")]
|
||||
impl TS for Sensitive<String> {
|
||||
fn name() -> String {
|
||||
"string".to_string()
|
||||
}
|
||||
fn name_with_type_args(_args: Vec<String>) -> String {
|
||||
"string".to_string()
|
||||
}
|
||||
fn dependencies() -> Vec<ts_rs::Dependency> {
|
||||
Vec::new()
|
||||
}
|
||||
fn transparent() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
|
@ -375,7 +375,8 @@ impl From<FederationQueueState> for ReadableFederationState {
|
|||
pub struct InstanceWithFederationState {
|
||||
#[serde(flatten)]
|
||||
pub instance: Instance,
|
||||
/// if federation to this instance is or was active, show state of outgoing federation to this instance
|
||||
/// if federation to this instance is or was active, show state of outgoing federation to this
|
||||
/// instance
|
||||
pub federation_state: Option<ReadableFederationState>,
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,14 @@ use crate::{
|
|||
use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
||||
use enum_map::{enum_map, EnumMap};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||
newtypes::{CommunityId, DbUrl, InstanceId, PersonId, PostId},
|
||||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
community::{Community, CommunityModerator, CommunityUpdateForm},
|
||||
community_block::CommunityBlock,
|
||||
email_verification::{EmailVerification, EmailVerificationForm},
|
||||
images::{LocalImage, RemoteImage},
|
||||
images::RemoteImage,
|
||||
instance::Instance,
|
||||
instance_block::InstanceBlock,
|
||||
local_site::LocalSite,
|
||||
|
@ -27,7 +28,10 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
utils::DbPool,
|
||||
};
|
||||
use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView};
|
||||
use lemmy_db_views::{
|
||||
comment_view::CommentQuery,
|
||||
structs::{LocalImageView, LocalUserView},
|
||||
};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommunityModeratorView,
|
||||
CommunityPersonBanView,
|
||||
|
@ -42,28 +46,25 @@ use lemmy_utils::{
|
|||
markdown::{markdown_check_for_blocked_urls, markdown_rewrite_image_links},
|
||||
slurs::{build_slur_regex, remove_slurs},
|
||||
},
|
||||
CACHE_DURATION_FEDERATION,
|
||||
};
|
||||
use moka::future::Cache;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::{escape, Regex, RegexSet};
|
||||
use rosetta_i18n::{Language, LanguageId};
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use std::collections::HashSet;
|
||||
use tracing::warn;
|
||||
use url::{ParseError, Url};
|
||||
use urlencoding::encode;
|
||||
|
||||
pub static AUTH_COOKIE_NAME: &str = "jwt";
|
||||
#[cfg(debug_assertions)]
|
||||
static URL_BLOCKLIST_RECHECK_DELAY: Duration = Duration::from_millis(500);
|
||||
#[cfg(not(debug_assertions))]
|
||||
static URL_BLOCKLIST_RECHECK_DELAY: Duration = Duration::from_secs(60);
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn is_mod_or_admin(
|
||||
pool: &mut DbPool<'_>,
|
||||
person: &Person,
|
||||
community_id: CommunityId,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
check_user_valid(person)?;
|
||||
|
||||
let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?;
|
||||
|
@ -79,7 +80,7 @@ pub async fn is_mod_or_admin_opt(
|
|||
pool: &mut DbPool<'_>,
|
||||
local_user_view: Option<&LocalUserView>,
|
||||
community_id: Option<CommunityId>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
if let Some(local_user_view) = local_user_view {
|
||||
if let Some(community_id) = community_id {
|
||||
is_mod_or_admin(pool, &local_user_view.person, community_id).await
|
||||
|
@ -111,7 +112,7 @@ pub async fn check_community_mod_of_any_or_admin_action(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
|
||||
pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
|
||||
check_user_valid(&local_user_view.person)?;
|
||||
if !local_user_view.local_user.admin {
|
||||
Err(LemmyErrorType::NotAnAdmin)?
|
||||
|
@ -125,7 +126,7 @@ pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
|
|||
pub fn is_top_mod(
|
||||
local_user_view: &LocalUserView,
|
||||
community_mods: &[CommunityModeratorView],
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
check_user_valid(&local_user_view.person)?;
|
||||
if local_user_view.person.id
|
||||
!= community_mods
|
||||
|
@ -139,26 +140,42 @@ pub fn is_top_mod(
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn get_post(post_id: PostId, pool: &mut DbPool<'_>) -> Result<Post, LemmyError> {
|
||||
Post::read(pool, post_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntFindPost)
|
||||
}
|
||||
|
||||
/// Marks a post as read for a given person.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn mark_post_as_read(
|
||||
person_id: PersonId,
|
||||
post_id: PostId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
PostRead::mark_as_read(pool, HashSet::from([post_id]), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_user_valid(person: &Person) -> Result<(), LemmyError> {
|
||||
/// Updates the read comment count for a post. Usually done when reading or creating a new comment.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn update_read_comments(
|
||||
person_id: PersonId,
|
||||
post_id: PostId,
|
||||
read_comments: i64,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let person_post_agg_form = PersonPostAggregatesForm {
|
||||
person_id,
|
||||
post_id,
|
||||
read_comments,
|
||||
..PersonPostAggregatesForm::default()
|
||||
};
|
||||
|
||||
PersonPostAggregates::upsert(pool, &person_post_agg_form)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_user_valid(person: &Person) -> LemmyResult<()> {
|
||||
// Check for a site ban
|
||||
if person.banned {
|
||||
Err(LemmyErrorType::SiteBan)?
|
||||
|
@ -191,8 +208,8 @@ async fn check_community_deleted_removed(
|
|||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let community = Community::read(pool, community_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
if community.deleted || community.removed {
|
||||
Err(LemmyErrorType::Deleted)?
|
||||
}
|
||||
|
@ -233,7 +250,7 @@ pub async fn check_community_mod_action(
|
|||
}
|
||||
|
||||
/// Don't allow creating reports for removed / deleted posts
|
||||
pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
|
||||
pub fn check_post_deleted_or_removed(post: &Post) -> LemmyResult<()> {
|
||||
if post.deleted || post.removed {
|
||||
Err(LemmyErrorType::Deleted)?
|
||||
} else {
|
||||
|
@ -241,7 +258,7 @@ pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn check_comment_deleted_or_removed(comment: &Comment) -> Result<(), LemmyError> {
|
||||
pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> {
|
||||
if comment.deleted || comment.removed {
|
||||
Err(LemmyErrorType::Deleted)?
|
||||
} else {
|
||||
|
@ -255,7 +272,7 @@ pub async fn check_person_block(
|
|||
my_id: PersonId,
|
||||
potential_blocker_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id).await?;
|
||||
if is_blocked {
|
||||
Err(LemmyErrorType::PersonIsBlocked)?
|
||||
|
@ -270,7 +287,7 @@ async fn check_community_block(
|
|||
community_id: CommunityId,
|
||||
person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let is_blocked = CommunityBlock::read(pool, person_id, community_id).await?;
|
||||
if is_blocked {
|
||||
Err(LemmyErrorType::CommunityIsBlocked)?
|
||||
|
@ -285,7 +302,7 @@ async fn check_instance_block(
|
|||
instance_id: InstanceId,
|
||||
person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let is_blocked = InstanceBlock::read(pool, person_id, instance_id).await?;
|
||||
if is_blocked {
|
||||
Err(LemmyErrorType::InstanceIsBlocked)?
|
||||
|
@ -298,18 +315,18 @@ async fn check_instance_block(
|
|||
pub async fn check_person_instance_community_block(
|
||||
my_id: PersonId,
|
||||
potential_blocker_id: PersonId,
|
||||
instance_id: InstanceId,
|
||||
community_instance_id: InstanceId,
|
||||
community_id: CommunityId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
check_person_block(my_id, potential_blocker_id, pool).await?;
|
||||
check_instance_block(instance_id, potential_blocker_id, pool).await?;
|
||||
check_instance_block(community_instance_id, potential_blocker_id, pool).await?;
|
||||
check_community_block(community_id, potential_blocker_id, pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> {
|
||||
pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> LemmyResult<()> {
|
||||
if score == -1 && !local_site.enable_downvotes {
|
||||
Err(LemmyErrorType::DownvotesAreDisabled)?
|
||||
} else {
|
||||
|
@ -319,7 +336,7 @@ pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(),
|
|||
|
||||
/// Dont allow bots to do certain actions, like voting
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn check_bot_account(person: &Person) -> Result<(), LemmyError> {
|
||||
pub fn check_bot_account(person: &Person) -> LemmyResult<()> {
|
||||
if person.bot_account {
|
||||
Err(LemmyErrorType::InvalidBotAction)?
|
||||
} else {
|
||||
|
@ -331,7 +348,7 @@ pub fn check_bot_account(person: &Person) -> Result<(), LemmyError> {
|
|||
pub fn check_private_instance(
|
||||
local_user_view: &Option<LocalUserView>,
|
||||
local_site: &LocalSite,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
if local_user_view.is_none() && local_site.private_instance {
|
||||
Err(LemmyErrorType::InstanceIsPrivate)?
|
||||
} else {
|
||||
|
@ -343,7 +360,7 @@ pub fn check_private_instance(
|
|||
pub async fn build_federated_instances(
|
||||
local_site: &LocalSite,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<Option<FederatedInstances>, LemmyError> {
|
||||
) -> LemmyResult<Option<FederatedInstances>> {
|
||||
if local_site.federation_enabled {
|
||||
let mut linked = Vec::new();
|
||||
let mut allowed = Vec::new();
|
||||
|
@ -356,7 +373,8 @@ pub async fn build_federated_instances(
|
|||
federation_state: federation_state.map(std::convert::Into::into),
|
||||
};
|
||||
if is_blocked {
|
||||
// blocked instances will only have an entry here if they had been federated with in the past.
|
||||
// blocked instances will only have an entry here if they had been federated with in the
|
||||
// past.
|
||||
blocked.push(i);
|
||||
} else if is_allowed {
|
||||
allowed.push(i.clone());
|
||||
|
@ -378,7 +396,7 @@ pub async fn build_federated_instances(
|
|||
}
|
||||
|
||||
/// Checks the password length
|
||||
pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
|
||||
pub fn password_length_check(pass: &str) -> LemmyResult<()> {
|
||||
if !(10..=60).contains(&pass.chars().count()) {
|
||||
Err(LemmyErrorType::InvalidPassword)?
|
||||
} else {
|
||||
|
@ -387,7 +405,7 @@ pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
|
|||
}
|
||||
|
||||
/// Checks for a honeypot. If this field is filled, fail the rest of the function
|
||||
pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {
|
||||
pub fn honeypot_check(honeypot: &Option<String>) -> LemmyResult<()> {
|
||||
if honeypot.is_some() && honeypot != &Some(String::new()) {
|
||||
Err(LemmyErrorType::HoneypotFailed)?
|
||||
} else {
|
||||
|
@ -425,7 +443,7 @@ pub async fn send_password_reset_email(
|
|||
user: &LocalUserView,
|
||||
pool: &mut DbPool<'_>,
|
||||
settings: &Settings,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
// Generate a random token
|
||||
let token = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
|
@ -440,7 +458,7 @@ pub async fn send_password_reset_email(
|
|||
// Insert the row after successful send, to avoid using daily reset limit while
|
||||
// email sending is broken.
|
||||
let local_user_id = user.local_user.id;
|
||||
PasswordResetRequest::create_token(pool, local_user_id, token.clone()).await?;
|
||||
PasswordResetRequest::create(pool, local_user_id, token.clone()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -450,7 +468,7 @@ pub async fn send_verification_email(
|
|||
new_email: &str,
|
||||
pool: &mut DbPool<'_>,
|
||||
settings: &Settings,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let form = EmailVerificationForm {
|
||||
local_user_id: user.local_user.id,
|
||||
email: new_email.to_string(),
|
||||
|
@ -527,7 +545,7 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet>
|
|||
static URL_BLOCKLIST: Lazy<Cache<(), RegexSet>> = Lazy::new(|| {
|
||||
Cache::builder()
|
||||
.max_capacity(1)
|
||||
.time_to_live(URL_BLOCKLIST_RECHECK_DELAY)
|
||||
.time_to_live(CACHE_DURATION_FEDERATION)
|
||||
.build()
|
||||
});
|
||||
|
||||
|
@ -536,25 +554,8 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet>
|
|||
.try_get_with::<_, LemmyError>((), async {
|
||||
let urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||
|
||||
let regexes = urls.iter().map(|url| {
|
||||
let url = &url.url;
|
||||
let parsed = Url::parse(url).expect("Coundln't parse URL.");
|
||||
if url.ends_with('/') {
|
||||
format!(
|
||||
"({}://)?{}{}?",
|
||||
parsed.scheme(),
|
||||
escape(parsed.domain().expect("No domain.")),
|
||||
escape(parsed.path())
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"({}://)?{}{}",
|
||||
parsed.scheme(),
|
||||
escape(parsed.domain().expect("No domain.")),
|
||||
escape(parsed.path())
|
||||
)
|
||||
}
|
||||
});
|
||||
// The urls are already validated on saving, so just escape them.
|
||||
let regexes = urls.iter().map(|url| escape(&url.url));
|
||||
|
||||
let set = RegexSet::new(regexes)?;
|
||||
Ok(set)
|
||||
|
@ -567,7 +568,7 @@ pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet>
|
|||
pub async fn send_application_approved_email(
|
||||
user: &LocalUserView,
|
||||
settings: &Settings,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let email = &user.local_user.email.clone().expect("email");
|
||||
let lang = get_interface_language(user);
|
||||
let subject = lang.registration_approved_subject(&user.person.actor_id);
|
||||
|
@ -580,7 +581,7 @@ pub async fn send_new_applicant_email_to_admins(
|
|||
applicant_username: &str,
|
||||
pool: &mut DbPool<'_>,
|
||||
settings: &Settings,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
// Collect the admins with emails
|
||||
let admins = LocalUserView::list_admins_with_emails(pool).await?;
|
||||
|
||||
|
@ -605,7 +606,7 @@ pub async fn send_new_report_email_to_admins(
|
|||
reported_username: &str,
|
||||
pool: &mut DbPool<'_>,
|
||||
settings: &Settings,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
// Collect the admins with emails
|
||||
let admins = LocalUserView::list_admins_with_emails(pool).await?;
|
||||
|
||||
|
@ -621,9 +622,7 @@ pub async fn send_new_report_email_to_admins(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_private_instance_and_federation_enabled(
|
||||
local_site: &LocalSite,
|
||||
) -> Result<(), LemmyError> {
|
||||
pub fn check_private_instance_and_federation_enabled(local_site: &LocalSite) -> LemmyResult<()> {
|
||||
if local_site.private_instance && local_site.federation_enabled {
|
||||
Err(LemmyErrorType::CantEnablePrivateInstanceAndFederationTogether)?
|
||||
} else {
|
||||
|
@ -637,7 +636,7 @@ pub fn check_private_instance_and_federation_enabled(
|
|||
pub async fn read_site_for_actor(
|
||||
actor_id: DbUrl,
|
||||
context: &LemmyContext,
|
||||
) -> Result<Option<Site>, LemmyError> {
|
||||
) -> LemmyResult<Option<Site>> {
|
||||
let site_id = Site::instance_actor_id_from_url(actor_id.clone().into());
|
||||
let site = Site::read_from_apub_id(&mut context.pool(), &site_id.into()).await?;
|
||||
Ok(site)
|
||||
|
@ -646,7 +645,7 @@ pub async fn read_site_for_actor(
|
|||
pub async fn purge_image_posts_for_person(
|
||||
banned_person_id: PersonId,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let pool = &mut context.pool();
|
||||
let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?;
|
||||
for post in posts {
|
||||
|
@ -664,17 +663,19 @@ pub async fn purge_image_posts_for_person(
|
|||
}
|
||||
|
||||
/// Delete a local_user's images
|
||||
async fn delete_local_user_images(
|
||||
person_id: PersonId,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await {
|
||||
async fn delete_local_user_images(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> {
|
||||
if let Ok(Some(local_user)) = LocalUserView::read_person(&mut context.pool(), person_id).await {
|
||||
let pictrs_uploads =
|
||||
LocalImage::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id).await?;
|
||||
LocalImageView::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id)
|
||||
.await?;
|
||||
|
||||
// Delete their images
|
||||
for upload in pictrs_uploads {
|
||||
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, context)
|
||||
delete_image_from_pictrs(
|
||||
&upload.local_image.pictrs_alias,
|
||||
&upload.local_image.pictrs_delete_token,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
@ -685,7 +686,7 @@ async fn delete_local_user_images(
|
|||
pub async fn purge_image_posts_for_community(
|
||||
banned_community_id: CommunityId,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let pool = &mut context.pool();
|
||||
let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?;
|
||||
for post in posts {
|
||||
|
@ -705,10 +706,12 @@ pub async fn purge_image_posts_for_community(
|
|||
pub async fn remove_user_data(
|
||||
banned_person_id: PersonId,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
let pool = &mut context.pool();
|
||||
// Purge user images
|
||||
let person = Person::read(pool, banned_person_id).await?;
|
||||
let person = Person::read(pool, banned_person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
if let Some(avatar) = person.avatar {
|
||||
purge_image_from_pictrs(&avatar, context).await.ok();
|
||||
}
|
||||
|
@ -788,7 +791,7 @@ pub async fn remove_user_data_in_community(
|
|||
community_id: CommunityId,
|
||||
banned_person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> Result<(), LemmyError> {
|
||||
) -> LemmyResult<()> {
|
||||
// Posts
|
||||
Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), true).await?;
|
||||
|
||||
|
@ -818,13 +821,12 @@ pub async fn remove_user_data_in_community(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn purge_user_account(
|
||||
person_id: PersonId,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> {
|
||||
let pool = &mut context.pool();
|
||||
|
||||
let person = Person::read(pool, person_id).await?;
|
||||
let person = Person::read(pool, person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
|
||||
// Delete their local images, if they're a local user
|
||||
delete_local_user_images(person_id, context).await.ok();
|
||||
|
@ -891,7 +893,7 @@ pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
|||
Ok(Url::parse(&format!("{actor_id}/inbox"))?.into())
|
||||
}
|
||||
|
||||
pub fn generate_shared_inbox_url(settings: &Settings) -> Result<DbUrl, LemmyError> {
|
||||
pub fn generate_shared_inbox_url(settings: &Settings) -> LemmyResult<DbUrl> {
|
||||
let url = format!("{}/inbox", settings.get_protocol_and_hostname());
|
||||
Ok(Url::parse(&url)?.into())
|
||||
}
|
||||
|
@ -904,7 +906,7 @@ pub fn generate_featured_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
|||
Ok(Url::parse(&format!("{actor_id}/featured"))?.into())
|
||||
}
|
||||
|
||||
pub fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
|
||||
pub fn generate_moderators_url(community_id: &DbUrl) -> LemmyResult<DbUrl> {
|
||||
Ok(Url::parse(&format!("{community_id}/moderators"))?.into())
|
||||
}
|
||||
|
||||
|
@ -970,8 +972,8 @@ pub async fn process_markdown_opt(
|
|||
|
||||
/// A wrapper for `proxy_image_link` for use in tests.
|
||||
///
|
||||
/// The parameter `force_image_proxy` is the config value of `pictrs.image_proxy`. Its necessary to pass
|
||||
/// as separate parameter so it can be changed in tests.
|
||||
/// The parameter `force_image_proxy` is the config value of `pictrs.image_proxy`. Its necessary to
|
||||
/// pass as separate parameter so it can be changed in tests.
|
||||
async fn proxy_image_link_internal(
|
||||
link: Url,
|
||||
image_mode: PictrsImageMode,
|
||||
|
@ -981,13 +983,10 @@ async fn proxy_image_link_internal(
|
|||
if link.domain() == Some(&context.settings().hostname) {
|
||||
Ok(link.into())
|
||||
} else if image_mode == PictrsImageMode::ProxyAllImages {
|
||||
let proxied = format!(
|
||||
"{}/api/v3/image_proxy?url={}",
|
||||
context.settings().get_protocol_and_hostname(),
|
||||
encode(link.as_str())
|
||||
);
|
||||
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
||||
|
||||
RemoteImage::create(&mut context.pool(), vec![link]).await?;
|
||||
Ok(Url::parse(&proxied)?.into())
|
||||
Ok(proxied.into())
|
||||
} else {
|
||||
Ok(link.into())
|
||||
}
|
||||
|
@ -1005,26 +1004,25 @@ pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> Lemmy
|
|||
}
|
||||
|
||||
pub async fn proxy_image_link_opt_api(
|
||||
link: &Option<String>,
|
||||
link: Option<Option<DbUrl>>,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Option<Option<DbUrl>>> {
|
||||
proxy_image_link_api(link, context).await.map(Some)
|
||||
if let Some(Some(link)) = link {
|
||||
proxy_image_link(link.into(), context)
|
||||
.await
|
||||
.map(Some)
|
||||
.map(Some)
|
||||
} else {
|
||||
Ok(link)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn proxy_image_link_api(
|
||||
link: &Option<String>,
|
||||
link: Option<DbUrl>,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Option<DbUrl>> {
|
||||
let link: Option<DbUrl> = match link.as_ref().map(String::as_str) {
|
||||
// An empty string is an erase
|
||||
Some("") => None,
|
||||
Some(str_url) => Url::parse(str_url)
|
||||
.map(|u| Some(u.into()))
|
||||
.with_lemmy_type(LemmyErrorType::InvalidUrl)?,
|
||||
None => None,
|
||||
};
|
||||
if let Some(l) = link {
|
||||
proxy_image_link(l.into(), context).await.map(Some)
|
||||
if let Some(link) = link {
|
||||
proxy_image_link(link.into(), context).await.map(Some)
|
||||
} else {
|
||||
Ok(link)
|
||||
}
|
||||
|
@ -1041,6 +1039,17 @@ pub async fn proxy_image_link_opt_apub(
|
|||
}
|
||||
}
|
||||
|
||||
fn build_proxied_image_url(
|
||||
link: &Url,
|
||||
protocol_and_hostname: &str,
|
||||
) -> Result<Url, url::ParseError> {
|
||||
Url::parse(&format!(
|
||||
"{}/api/v3/image_proxy?url={}",
|
||||
protocol_and_hostname,
|
||||
encode(link.as_str())
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
|
@ -1120,29 +1129,4 @@ mod tests {
|
|||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_diesel_option_overwrite_to_url() {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
|
||||
assert!(matches!(
|
||||
proxy_image_link_api(&None, &context).await,
|
||||
Ok(None)
|
||||
));
|
||||
assert!(matches!(
|
||||
proxy_image_link_opt_api(&Some(String::new()), &context).await,
|
||||
Ok(Some(None))
|
||||
));
|
||||
assert!(
|
||||
proxy_image_link_opt_api(&Some("invalid_url".to_string()), &context)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
let example_url = "https://lemmy-alpha/image.png";
|
||||
assert!(matches!(
|
||||
proxy_image_link_opt_api(&Some(example_url.to_string()), &context).await,
|
||||
Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,11 @@ use lemmy_api_common::{
|
|||
check_community_user_action,
|
||||
check_post_deleted_or_removed,
|
||||
generate_local_apub_endpoint,
|
||||
get_post,
|
||||
get_url_blocklist,
|
||||
is_mod_or_admin,
|
||||
local_site_to_slur_regex,
|
||||
process_markdown,
|
||||
update_read_comments,
|
||||
EndpointType,
|
||||
},
|
||||
};
|
||||
|
@ -28,9 +28,9 @@ use lemmy_db_schema::{
|
|||
},
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views::structs::{LocalUserView, PostView};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
|
||||
};
|
||||
|
||||
|
@ -41,18 +41,29 @@ pub async fn create_comment(
|
|||
data: Json<CreateComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&Some(content.clone()), false)?;
|
||||
is_valid_body_field(&content, false)?;
|
||||
|
||||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
let post = get_post(post_id, &mut context.pool()).await?;
|
||||
let community_id = post.community_id;
|
||||
|
||||
// Read the full post view in order to get the comments count.
|
||||
let post_view = PostView::read(
|
||||
&mut context.pool(),
|
||||
post_id,
|
||||
Some(local_user_view.person.id),
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
let post = post_view.post;
|
||||
let community_id = post_view.community.id;
|
||||
|
||||
check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?;
|
||||
check_post_deleted_or_removed(&post)?;
|
||||
|
@ -70,7 +81,8 @@ pub async fn create_comment(
|
|||
Comment::read(&mut context.pool(), parent_id).await.ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
.flatten();
|
||||
|
||||
// If there's a parent_id, check to make sure that comment is in that post
|
||||
// Strange issue where sometimes the post ID of the parent comment is incorrect
|
||||
|
@ -138,9 +150,8 @@ pub async fn create_comment(
|
|||
let mentions = scrape_text_for_mentions(&content);
|
||||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
&updated_comment,
|
||||
inserted_comment_id,
|
||||
&local_user_view.person,
|
||||
&post,
|
||||
true,
|
||||
&context,
|
||||
)
|
||||
|
@ -164,6 +175,15 @@ pub async fn create_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Update the read comments, so your own new comment doesn't appear as a +1 unread
|
||||
update_read_comments(
|
||||
local_user_view.person.id,
|
||||
post_id,
|
||||
post_view.counts.comments + 1,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// If we're responding to a comment where we're the recipient,
|
||||
// (ie we're the grandparent, or the recipient of the parent comment_reply),
|
||||
// then mark the parent as read.
|
||||
|
@ -173,7 +193,7 @@ pub async fn create_comment(
|
|||
let parent_id = parent.id;
|
||||
let comment_reply =
|
||||
CommentReply::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await;
|
||||
if let Ok(reply) = comment_reply {
|
||||
if let Ok(Some(reply)) = comment_reply {
|
||||
CommentReply::update(
|
||||
&mut context.pool(),
|
||||
reply.id,
|
||||
|
@ -186,7 +206,7 @@ pub async fn create_comment(
|
|||
// If the parent has PersonMentions mark them as read too
|
||||
let person_mention =
|
||||
PersonMention::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await;
|
||||
if let Ok(mention) = person_mention {
|
||||
if let Ok(Some(mention)) = person_mention {
|
||||
PersonMention::update(
|
||||
&mut context.pool(),
|
||||
mention.id,
|
||||
|
@ -208,7 +228,7 @@ pub async fn create_comment(
|
|||
))
|
||||
}
|
||||
|
||||
pub fn check_comment_depth(comment: &Comment) -> Result<(), LemmyError> {
|
||||
pub fn check_comment_depth(comment: &Comment) -> LemmyResult<()> {
|
||||
let path = &comment.path.0;
|
||||
let length = path.split('.').count();
|
||||
if length > MAX_COMMENT_DEPTH_LIMIT {
|
||||
|
|
|
@ -8,23 +8,22 @@ use lemmy_api_common::{
|
|||
utils::check_community_user_action,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
post::Post,
|
||||
},
|
||||
source::comment::{Comment, CommentUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn delete_comment(
|
||||
data: Json<DeleteComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
// Dont delete it if its already been deleted.
|
||||
if orig_comment.comment.deleted == data.deleted {
|
||||
|
@ -56,17 +55,8 @@ pub async fn delete_comment(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
|
||||
|
||||
let post_id = updated_comment.post_id;
|
||||
let post = Post::read(&mut context.pool(), post_id).await?;
|
||||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
&updated_comment,
|
||||
&local_user_view.person,
|
||||
&post,
|
||||
false,
|
||||
&context,
|
||||
)
|
||||
.await?;
|
||||
let recipient_ids =
|
||||
send_local_notifs(vec![], comment_id, &local_user_view.person, false, &context).await?;
|
||||
let updated_comment_id = updated_comment.id;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
|
|
|
@ -7,14 +7,14 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::source::local_site::LocalSite;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyError;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_comment(
|
||||
data: Query<GetComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
|
|
|
@ -12,21 +12,22 @@ use lemmy_db_schema::{
|
|||
comment::{Comment, CommentUpdateForm},
|
||||
comment_report::CommentReport,
|
||||
moderator::{ModRemoveComment, ModRemoveCommentForm},
|
||||
post::Post,
|
||||
},
|
||||
traits::{Crud, Reportable},
|
||||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn remove_comment(
|
||||
data: Json<RemoveComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_mod_action(
|
||||
&local_user_view.person,
|
||||
|
@ -36,6 +37,12 @@ pub async fn remove_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Don't allow removing or restoring comment which was deleted by user, as it would reveal
|
||||
// the comment text in mod log.
|
||||
if orig_comment.comment.deleted {
|
||||
return Err(LemmyErrorType::CouldntUpdateComment.into());
|
||||
}
|
||||
|
||||
// Do the remove
|
||||
let removed = data.removed;
|
||||
let updated_comment = Comment::update(
|
||||
|
@ -61,13 +68,10 @@ pub async fn remove_comment(
|
|||
};
|
||||
ModRemoveComment::create(&mut context.pool(), &form).await?;
|
||||
|
||||
let post_id = updated_comment.post_id;
|
||||
let post = Post::read(&mut context.pool(), post_id).await?;
|
||||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
&updated_comment,
|
||||
comment_id,
|
||||
&local_user_view.person.clone(),
|
||||
&post,
|
||||
false,
|
||||
&context,
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
|
||||
};
|
||||
|
||||
|
@ -32,11 +32,13 @@ pub async fn update_comment(
|
|||
data: Json<EditComment>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -61,7 +63,9 @@ pub async fn update_comment(
|
|||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let content = process_markdown_opt(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&content, false)?;
|
||||
if let Some(content) = &content {
|
||||
is_valid_body_field(content, false)?;
|
||||
}
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let form = CommentUpdateForm {
|
||||
|
@ -79,9 +83,8 @@ pub async fn update_comment(
|
|||
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
||||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
&updated_comment,
|
||||
comment_id,
|
||||
&local_user_view.person,
|
||||
&orig_comment.post,
|
||||
false,
|
||||
&context,
|
||||
)
|
||||
|
|
|
@ -30,10 +30,11 @@ use lemmy_db_schema::{
|
|||
},
|
||||
},
|
||||
traits::{ApubActor, Crud, Followable, Joinable},
|
||||
utils::diesel_url_create,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::{
|
||||
slurs::check_slurs,
|
||||
validation::{is_valid_actor_name, is_valid_body_field},
|
||||
|
@ -45,8 +46,10 @@ pub async fn create_community(
|
|||
data: Json<CreateCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
) -> LemmyResult<Json<CommunityResponse>> {
|
||||
let site_view = SiteView::read_local(&mut context.pool())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||
let local_site = site_view.local_site;
|
||||
|
||||
if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
|
||||
|
@ -59,11 +62,18 @@ pub async fn create_community(
|
|||
check_slurs(&data.title, &slur_regex)?;
|
||||
let description =
|
||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
||||
let icon = proxy_image_link_api(&data.icon, &context).await?;
|
||||
let banner = proxy_image_link_api(&data.banner, &context).await?;
|
||||
|
||||
let icon = diesel_url_create(data.icon.as_deref())?;
|
||||
let icon = proxy_image_link_api(icon, &context).await?;
|
||||
|
||||
let banner = diesel_url_create(data.banner.as_deref())?;
|
||||
let banner = proxy_image_link_api(banner, &context).await?;
|
||||
|
||||
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
||||
is_valid_body_field(&data.description, false)?;
|
||||
|
||||
if let Some(desc) = &data.description {
|
||||
is_valid_body_field(desc, false)?;
|
||||
}
|
||||
|
||||
// Double check for duplicate community actor_ids
|
||||
let community_actor_id = generate_local_apub_endpoint(
|
||||
|
|
|
@ -13,14 +13,14 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn delete_community(
|
||||
data: Json<DeleteCommunity>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
||||
) -> LemmyResult<Json<CommunityResponse>> {
|
||||
// Fetch the community mods
|
||||
let community_id = data.community_id;
|
||||
let community_mods =
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue