mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-23 01:41:01 +00:00
Merge upstream/main into migration-runner (using imerge)
This commit is contained in:
commit
f636f1a574
383 changed files with 13411 additions and 10407 deletions
|
@ -2,7 +2,7 @@
|
||||||
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- &rust_image "rust:1.80"
|
- &rust_image "rust:1.81"
|
||||||
- &rust_nightly_image "rustlang/rust:nightly"
|
- &rust_nightly_image "rustlang/rust:nightly"
|
||||||
- &install_pnpm "corepack enable pnpm"
|
- &install_pnpm "corepack enable pnpm"
|
||||||
- &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin"
|
- &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin"
|
||||||
|
@ -42,14 +42,14 @@ steps:
|
||||||
- event: [pull_request, tag]
|
- event: [pull_request, tag]
|
||||||
|
|
||||||
prettier_check:
|
prettier_check:
|
||||||
image: tmknom/prettier:3.0.0
|
image: tmknom/prettier:3.2.5
|
||||||
commands:
|
commands:
|
||||||
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' '!api_tests/pnpm-lock.yaml'
|
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' '!api_tests/pnpm-lock.yaml'
|
||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
|
|
||||||
toml_fmt:
|
toml_fmt:
|
||||||
image: tamasfe/taplo:0.8.1
|
image: tamasfe/taplo:0.9.3
|
||||||
commands:
|
commands:
|
||||||
- taplo format --check
|
- taplo format --check
|
||||||
when:
|
when:
|
||||||
|
@ -73,12 +73,12 @@ steps:
|
||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
|
|
||||||
cargo_machete:
|
cargo_shear:
|
||||||
image: *rust_nightly_image
|
image: *rust_nightly_image
|
||||||
commands:
|
commands:
|
||||||
- *install_binstall
|
- *install_binstall
|
||||||
- cargo binstall -y cargo-machete
|
- cargo binstall -y cargo-shear
|
||||||
- cargo machete
|
- cargo shear
|
||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
|
|
||||||
|
@ -122,7 +122,6 @@ steps:
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
- export LEMMY_CONFIG_LOCATION=./config/config.hjson
|
|
||||||
- ./scripts/update_config_defaults.sh config/defaults_current.hjson
|
- ./scripts/update_config_defaults.sh config/defaults_current.hjson
|
||||||
- diff config/defaults.hjson config/defaults_current.hjson
|
- diff config/defaults.hjson config/defaults_current.hjson
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
@ -147,7 +146,6 @@ steps:
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
# same as scripts/db_perf.sh but without creating a new database server
|
# same as scripts/db_perf.sh but without creating a new database server
|
||||||
- export LEMMY_CONFIG_LOCATION=config/config.hjson
|
|
||||||
- cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1
|
- cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
|
@ -157,7 +155,7 @@ steps:
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
- rustup component add clippy
|
- rustup component add clippy
|
||||||
- cargo clippy --workspace --tests --all-targets --features console -- -D warnings
|
- cargo clippy --workspace --tests --all-targets -- -D warnings
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
cargo_build:
|
cargo_build:
|
||||||
|
@ -177,9 +175,10 @@ steps:
|
||||||
RUST_BACKTRACE: "1"
|
RUST_BACKTRACE: "1"
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
|
- cp crates/db_schema/src/schema.rs tmp.schema
|
||||||
- target/lemmy_server migration --all run
|
- target/lemmy_server migration --all run
|
||||||
- <<: *install_diesel_cli
|
- <<: *install_diesel_cli
|
||||||
- diesel print-schema --config-file=diesel.toml > tmp.schema
|
- diesel print-schema
|
||||||
- diff tmp.schema crates/db_schema/src/schema.rs
|
- diff tmp.schema crates/db_schema/src/schema.rs
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
|
@ -190,6 +189,7 @@ steps:
|
||||||
RUST_BACKTRACE: "1"
|
RUST_BACKTRACE: "1"
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
LEMMY_TEST_FAST_FEDERATION: "1"
|
LEMMY_TEST_FAST_FEDERATION: "1"
|
||||||
|
LEMMY_CONFIG_LOCATION: ../../config/config.hjson
|
||||||
commands:
|
commands:
|
||||||
# Install pg_dump for the schema setup test (must match server version)
|
# Install pg_dump for the schema setup test (must match server version)
|
||||||
- apt update && apt install -y lsb-release
|
- apt update && apt install -y lsb-release
|
||||||
|
@ -197,12 +197,20 @@ steps:
|
||||||
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||||
- apt update && apt install -y postgresql-client-16
|
- apt update && apt install -y postgresql-client-16
|
||||||
# Run tests
|
# Run tests
|
||||||
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
|
||||||
- cargo test --workspace --no-fail-fast
|
- cargo test --workspace --no-fail-fast
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
|
check_ts_bindings:
|
||||||
|
image: *rust_image
|
||||||
|
environment:
|
||||||
|
CARGO_HOME: .cargo_home
|
||||||
|
commands:
|
||||||
|
- ./scripts/ts_bindings_check.sh
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
|
||||||
run_federation_tests:
|
run_federation_tests:
|
||||||
image: node:20-bookworm-slim
|
image: node:22-bookworm-slim
|
||||||
environment:
|
environment:
|
||||||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
|
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
|
||||||
DO_WRITE_HOSTS_FILE: "1"
|
DO_WRITE_HOSTS_FILE: "1"
|
||||||
|
@ -227,10 +235,13 @@ steps:
|
||||||
|
|
||||||
publish_release_docker:
|
publish_release_docker:
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
secrets: [docker_username, docker_password]
|
|
||||||
settings:
|
settings:
|
||||||
repo: dessalines/lemmy
|
repo: dessalines/lemmy
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
platforms: linux/amd64, linux/arm64
|
platforms: linux/amd64, linux/arm64
|
||||||
build_args:
|
build_args:
|
||||||
- RUST_RELEASE_MODE=release
|
- RUST_RELEASE_MODE=release
|
||||||
|
@ -240,10 +251,13 @@ steps:
|
||||||
|
|
||||||
nightly_build:
|
nightly_build:
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
secrets: [docker_username, docker_password]
|
|
||||||
settings:
|
settings:
|
||||||
repo: dessalines/lemmy
|
repo: dessalines/lemmy
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
build_args:
|
build_args:
|
||||||
- RUST_RELEASE_MODE=release
|
- RUST_RELEASE_MODE=release
|
||||||
|
|
2836
Cargo.lock
generated
2836
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
77
Cargo.toml
77
Cargo.toml
|
@ -1,5 +1,5 @@
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.19.6-beta.6"
|
version = "0.19.6-beta.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -24,11 +24,12 @@ doctest = false
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
# See https://github.com/johnthagen/min-sized-rust for additional optimizations
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 0
|
debug = 0
|
||||||
lto = "thin"
|
lto = "fat"
|
||||||
strip = true # Automatically strip symbols from the binary.
|
opt-level = 3 # Optimize for speed, not size.
|
||||||
opt-level = "z" # Optimize for size.
|
codegen-units = 1 # Reduce parallel code generation.
|
||||||
|
|
||||||
# This profile significantly speeds up build time. If debug info is needed you can comment the line
|
# This profile significantly speeds up build time. If debug info is needed you can comment the line
|
||||||
# out temporarily, but make sure to leave this in the main branch.
|
# out temporarily, but make sure to leave this in the main branch.
|
||||||
|
@ -36,16 +37,6 @@ opt-level = "z" # Optimize for size.
|
||||||
debug = 0
|
debug = 0
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
embed-pictrs = ["pict-rs"]
|
|
||||||
# This feature requires building with `tokio_unstable` flag, see documentation:
|
|
||||||
# https://docs.rs/tokio/latest/tokio/#unstable-features
|
|
||||||
console = [
|
|
||||||
"console-subscriber",
|
|
||||||
"opentelemetry",
|
|
||||||
"opentelemetry-otlp",
|
|
||||||
"tracing-opentelemetry",
|
|
||||||
"reqwest-tracing/opentelemetry_0_16",
|
|
||||||
]
|
|
||||||
json-log = ["tracing-subscriber/json"]
|
json-log = ["tracing-subscriber/json"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
|
@ -87,20 +78,21 @@ uninlined_format_args = "allow"
|
||||||
unused_self = "deny"
|
unused_self = "deny"
|
||||||
unwrap_used = "deny"
|
unwrap_used = "deny"
|
||||||
unimplemented = "deny"
|
unimplemented = "deny"
|
||||||
|
unused_async = "deny"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lemmy_api = { version = "=0.19.6-beta.6", path = "./crates/api" }
|
lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" }
|
||||||
lemmy_api_crud = { version = "=0.19.6-beta.6", path = "./crates/api_crud" }
|
lemmy_api_crud = { version = "=0.19.6-beta.7", path = "./crates/api_crud" }
|
||||||
lemmy_apub = { version = "=0.19.6-beta.6", path = "./crates/apub" }
|
lemmy_apub = { version = "=0.19.6-beta.7", path = "./crates/apub" }
|
||||||
lemmy_utils = { version = "=0.19.6-beta.6", path = "./crates/utils", default-features = false }
|
lemmy_utils = { version = "=0.19.6-beta.7", path = "./crates/utils", default-features = false }
|
||||||
lemmy_db_schema = { version = "=0.19.6-beta.6", path = "./crates/db_schema" }
|
lemmy_db_schema = { version = "=0.19.6-beta.7", path = "./crates/db_schema" }
|
||||||
lemmy_api_common = { version = "=0.19.6-beta.6", path = "./crates/api_common" }
|
lemmy_api_common = { version = "=0.19.6-beta.7", path = "./crates/api_common" }
|
||||||
lemmy_routes = { version = "=0.19.6-beta.6", path = "./crates/routes" }
|
lemmy_routes = { version = "=0.19.6-beta.7", path = "./crates/routes" }
|
||||||
lemmy_db_views = { version = "=0.19.6-beta.6", path = "./crates/db_views" }
|
lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.19.6-beta.6", path = "./crates/db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
|
||||||
lemmy_db_views_moderator = { version = "=0.19.6-beta.6", path = "./crates/db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
|
||||||
lemmy_federate = { version = "=0.19.6-beta.6", path = "./crates/federate" }
|
lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
|
||||||
activitypub_federation = { version = "0.5.8", default-features = false, features = [
|
activitypub_federation = { version = "0.6.0-alpha2", default-features = false, features = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
] }
|
] }
|
||||||
diesel = "2.1.6"
|
diesel = "2.1.6"
|
||||||
|
@ -108,7 +100,7 @@ diesel_migrations = "2.1.0"
|
||||||
diesel-async = "0.4.1"
|
diesel-async = "0.4.1"
|
||||||
serde = { version = "1.0.204", features = ["derive"] }
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
serde_with = "3.9.0"
|
serde_with = "3.9.0"
|
||||||
actix-web = { version = "4.8.0", default-features = false, features = [
|
actix-web = { version = "4.9.0", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
"rustls-0_23",
|
"rustls-0_23",
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
|
@ -117,19 +109,17 @@ actix-web = { version = "4.8.0", default-features = false, features = [
|
||||||
"cookies",
|
"cookies",
|
||||||
] }
|
] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-actix-web = { version = "0.7.11", default-features = false }
|
tracing-actix-web = { version = "0.7.10", default-features = false }
|
||||||
tracing-error = "0.2.0"
|
|
||||||
tracing-log = "0.2.0"
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
url = { version = "2.5.2", features = ["serde"] }
|
url = { version = "2.5.2", features = ["serde"] }
|
||||||
reqwest = { version = "0.11.27", default-features = false, features = [
|
reqwest = { version = "0.12.7", default-features = false, features = [
|
||||||
"json",
|
"json",
|
||||||
"blocking",
|
"blocking",
|
||||||
"gzip",
|
"gzip",
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
] }
|
] }
|
||||||
reqwest-middleware = "0.2.5"
|
reqwest-middleware = "0.3.3"
|
||||||
reqwest-tracing = "0.4.8"
|
reqwest-tracing = "0.5.3"
|
||||||
clokwerk = "0.4.0"
|
clokwerk = "0.4.0"
|
||||||
doku = { version = "0.21.1", features = ["url-2"] }
|
doku = { version = "0.21.1", features = ["url-2"] }
|
||||||
bcrypt = "0.15.1"
|
bcrypt = "0.15.1"
|
||||||
|
@ -143,7 +133,6 @@ anyhow = { version = "1.0.86", features = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
] } # backtrace is on by default on nightly, but not stable rust
|
] } # backtrace is on by default on nightly, but not stable rust
|
||||||
diesel_ltree = "0.3.1"
|
diesel_ltree = "0.3.1"
|
||||||
typed-builder = "0.19.1"
|
|
||||||
serial_test = "3.1.1"
|
serial_test = "3.1.1"
|
||||||
tokio = { version = "1.39.2", features = ["full"] }
|
tokio = { version = "1.39.2", features = ["full"] }
|
||||||
regex = "1.10.5"
|
regex = "1.10.5"
|
||||||
|
@ -152,14 +141,13 @@ diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
http = "0.2.12"
|
http = "1.1"
|
||||||
rosetta-i18n = "0.1.3"
|
rosetta-i18n = "0.1.3"
|
||||||
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
|
ts-rs = { version = "10.0.0", features = [
|
||||||
tracing-opentelemetry = { version = "0.19.0" }
|
|
||||||
ts-rs = { version = "7.1.1", features = [
|
|
||||||
"serde-compat",
|
"serde-compat",
|
||||||
"chrono-impl",
|
"chrono-impl",
|
||||||
"no-serde-warnings",
|
"no-serde-warnings",
|
||||||
|
"url-impl",
|
||||||
] }
|
] }
|
||||||
rustls = { version = "0.23.12", features = ["ring"] }
|
rustls = { version = "0.23.12", features = ["ring"] }
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
|
@ -171,7 +159,9 @@ moka = { version = "0.12.8", features = ["future"] }
|
||||||
i-love-jesus = { version = "0.1.0" }
|
i-love-jesus = { version = "0.1.0" }
|
||||||
clap = { version = "4.5.13", features = ["derive", "env"] }
|
clap = { version = "4.5.13", features = ["derive", "env"] }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
derive-new = "0.6.0"
|
derive-new = "0.7.0"
|
||||||
|
diesel-bind-if-some = "0.1.0"
|
||||||
|
tuplex = "0.1.2"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { workspace = true }
|
lemmy_api = { workspace = true }
|
||||||
|
@ -188,8 +178,6 @@ diesel-async = { workspace = true }
|
||||||
actix-web = { workspace = true }
|
actix-web = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-actix-web = { workspace = true }
|
tracing-actix-web = { workspace = true }
|
||||||
tracing-error = { workspace = true }
|
|
||||||
tracing-log = { workspace = true }
|
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
|
@ -197,11 +185,6 @@ reqwest-middleware = { workspace = true }
|
||||||
reqwest-tracing = { workspace = true }
|
reqwest-tracing = { workspace = true }
|
||||||
clokwerk = { workspace = true }
|
clokwerk = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tracing-opentelemetry = { workspace = true, optional = true }
|
|
||||||
opentelemetry = { workspace = true, optional = true }
|
|
||||||
console-subscriber = { version = "0.4.0", optional = true }
|
|
||||||
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
|
||||||
pict-rs = { version = "0.5.16", optional = true }
|
|
||||||
rustls = { workspace = true }
|
rustls = { workspace = true }
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
actix-cors = "0.7.0"
|
actix-cors = "0.7.0"
|
||||||
|
@ -210,7 +193,7 @@ chrono = { workspace = true }
|
||||||
prometheus = { version = "0.13.4", features = ["process"] }
|
prometheus = { version = "0.13.4", features = ["process"] }
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
actix-web-prom = "0.8.0"
|
actix-web-prom = "0.9.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
|
|
|
@ -6,31 +6,32 @@
|
||||||
"repository": "https://github.com/LemmyNet/lemmy",
|
"repository": "https://github.com/LemmyNet/lemmy",
|
||||||
"author": "Dessalines",
|
"author": "Dessalines",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"packageManager": "pnpm@9.6.0",
|
"packageManager": "pnpm@9.12.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'",
|
"lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'",
|
||||||
"fix": "prettier --write src && eslint --fix src",
|
"fix": "prettier --write src && eslint --fix src",
|
||||||
"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": "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 private_community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
|
||||||
"api-test-follow": "jest -i follow.spec.ts",
|
"api-test-follow": "jest -i follow.spec.ts",
|
||||||
"api-test-comment": "jest -i comment.spec.ts",
|
"api-test-comment": "jest -i comment.spec.ts",
|
||||||
"api-test-post": "jest -i post.spec.ts",
|
"api-test-post": "jest -i post.spec.ts",
|
||||||
"api-test-user": "jest -i user.spec.ts",
|
"api-test-user": "jest -i user.spec.ts",
|
||||||
"api-test-community": "jest -i community.spec.ts",
|
"api-test-community": "jest -i community.spec.ts",
|
||||||
|
"api-test-private-community": "jest -i private_community.spec.ts",
|
||||||
"api-test-private-message": "jest -i private_message.spec.ts",
|
"api-test-private-message": "jest -i private_message.spec.ts",
|
||||||
"api-test-image": "jest -i image.spec.ts"
|
"api-test-image": "jest -i image.spec.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^22.0.2",
|
"@types/node": "^22.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.1.0",
|
||||||
"eslint": "^9.8.0",
|
"eslint": "^9.9.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"lemmy-js-client": "0.19.5-alpha.1",
|
"lemmy-js-client": "0.20.0-private-community.9",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.5.4",
|
||||||
"typescript-eslint": "^8.0.0"
|
"typescript-eslint": "^8.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -92,7 +92,7 @@ test("Create a comment", async () => {
|
||||||
|
|
||||||
test("Create a comment in a non-existent post", async () => {
|
test("Create a comment in a non-existent post", async () => {
|
||||||
await expect(createComment(alpha, -1)).rejects.toStrictEqual(
|
await expect(createComment(alpha, -1)).rejects.toStrictEqual(
|
||||||
Error("couldnt_find_post"),
|
Error("not_found"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ test("Delete a comment", async () => {
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() =>
|
() =>
|
||||||
resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
||||||
r => r.message !== "couldnt_find_object",
|
r => r.message !== "not_found",
|
||||||
)
|
)
|
||||||
).comment;
|
).comment;
|
||||||
if (!gammaComment) {
|
if (!gammaComment) {
|
||||||
|
@ -158,16 +158,16 @@ test("Delete a comment", async () => {
|
||||||
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
||||||
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
||||||
|
|
||||||
// Make sure that comment is undefined on beta
|
// Make sure that comment is deleted on beta
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(beta, commentRes.comment_view.comment),
|
||||||
e => e.message == "couldnt_find_object",
|
c => c.comment?.comment.deleted === true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure that comment is undefined on gamma after delete
|
// Make sure that comment is deleted on gamma after delete
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(gamma, commentRes.comment_view.comment),
|
||||||
e => e.message === "couldnt_find_object",
|
c => c.comment?.comment.deleted === true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test undeleting the comment
|
// Test undeleting the comment
|
||||||
|
@ -181,11 +181,10 @@ test("Delete a comment", async () => {
|
||||||
// Make sure that comment is undeleted on beta
|
// Make sure that comment is undeleted on beta
|
||||||
let betaComment2 = (
|
let betaComment2 = (
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(beta, commentRes.comment_view.comment),
|
||||||
e => e.message !== "couldnt_find_object",
|
c => c.comment?.comment.deleted === false,
|
||||||
)
|
)
|
||||||
).comment;
|
).comment;
|
||||||
expect(betaComment2?.comment.deleted).toBe(false);
|
|
||||||
assertCommentFederation(betaComment2, undeleteCommentRes.comment_view);
|
assertCommentFederation(betaComment2, undeleteCommentRes.comment_view);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -858,3 +857,26 @@ test("Dont send a comment reply to a blocked community", async () => {
|
||||||
blockRes = await blockCommunity(beta, newCommunityId, false);
|
blockRes = await blockCommunity(beta, newCommunityId, false);
|
||||||
expect(blockRes.blocked).toBe(false);
|
expect(blockRes.blocked).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Fetching a deeply nested comment can lead to stack overflow as all parent comments are also
|
||||||
|
/// fetched recursively. Ensure that it works properly.
|
||||||
|
test.skip("Fetch a deeply nested comment", async () => {
|
||||||
|
let lastComment;
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
let commentRes = await createComment(
|
||||||
|
alpha,
|
||||||
|
postOnAlphaRes.post_view.post.id,
|
||||||
|
lastComment?.comment_view.comment.id,
|
||||||
|
);
|
||||||
|
expect(commentRes.comment_view.comment).toBeDefined();
|
||||||
|
lastComment = commentRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
let betaComment = await resolveComment(
|
||||||
|
beta,
|
||||||
|
lastComment!.comment_view.comment,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(betaComment!.comment!.comment).toBeDefined();
|
||||||
|
expect(betaComment?.comment?.post).toBeDefined();
|
||||||
|
});
|
||||||
|
|
|
@ -527,12 +527,12 @@ test("Content in local-only community doesn't federate", async () => {
|
||||||
// cant resolve the community from another instance
|
// cant resolve the community from another instance
|
||||||
await expect(
|
await expect(
|
||||||
resolveCommunity(beta, communityRes.actor_id),
|
resolveCommunity(beta, communityRes.actor_id),
|
||||||
).rejects.toStrictEqual(Error("couldnt_find_object"));
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
|
||||||
// create a post, also cant resolve it
|
// create a post, also cant resolve it
|
||||||
let postRes = await createPost(alpha, communityRes.id);
|
let postRes = await createPost(alpha, communityRes.id);
|
||||||
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual(
|
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual(
|
||||||
Error("couldnt_find_object"),
|
Error("not_found"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -125,12 +125,12 @@ test("Create a post", async () => {
|
||||||
// Delta only follows beta, so it should not see an alpha ap_id
|
// Delta only follows beta, so it should not see an alpha ap_id
|
||||||
await expect(
|
await expect(
|
||||||
resolvePost(delta, postRes.post_view.post),
|
resolvePost(delta, postRes.post_view.post),
|
||||||
).rejects.toStrictEqual(Error("couldnt_find_object"));
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
|
||||||
// Epsilon has alpha blocked, it should not see the alpha post
|
// Epsilon has alpha blocked, it should not see the alpha post
|
||||||
await expect(
|
await expect(
|
||||||
resolvePost(epsilon, postRes.post_view.post),
|
resolvePost(epsilon, postRes.post_view.post),
|
||||||
).rejects.toStrictEqual(Error("couldnt_find_object"));
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
|
||||||
// remove added allow/blocklists
|
// remove added allow/blocklists
|
||||||
editSiteForm.allowed_instances = [];
|
editSiteForm.allowed_instances = [];
|
||||||
|
@ -140,9 +140,7 @@ test("Create a post", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create a post in a non-existent community", async () => {
|
test("Create a post in a non-existent community", async () => {
|
||||||
await expect(createPost(alpha, -2)).rejects.toStrictEqual(
|
await expect(createPost(alpha, -2)).rejects.toStrictEqual(Error("not_found"));
|
||||||
Error("couldnt_find_community"),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Unlike a post", async () => {
|
test("Unlike a post", async () => {
|
||||||
|
@ -502,10 +500,17 @@ test("Enforce site ban federation for local user", async () => {
|
||||||
alpha,
|
alpha,
|
||||||
alphaPerson.person.id,
|
alphaPerson.person.id,
|
||||||
false,
|
false,
|
||||||
false,
|
true,
|
||||||
);
|
);
|
||||||
expect(unBanAlpha.banned).toBe(false);
|
expect(unBanAlpha.banned).toBe(false);
|
||||||
|
|
||||||
|
// existing alpha post should be restored on beta
|
||||||
|
betaBanRes = await waitUntil(
|
||||||
|
() => getPost(beta, searchBeta1.post.id),
|
||||||
|
s => !s.post_view.post.removed,
|
||||||
|
);
|
||||||
|
expect(betaBanRes.post_view.post.removed).toBe(false);
|
||||||
|
|
||||||
// Login gets invalidated by ban, need to login again
|
// Login gets invalidated by ban, need to login again
|
||||||
if (!alphaUserPerson) {
|
if (!alphaUserPerson) {
|
||||||
throw "Missing alpha person";
|
throw "Missing alpha person";
|
||||||
|
@ -623,7 +628,7 @@ test("Enforce community ban for federated user", async () => {
|
||||||
// Alpha tries to make post on beta, but it fails because of ban
|
// Alpha tries to make post on beta, but it fails because of ban
|
||||||
await expect(
|
await expect(
|
||||||
createPost(alpha, betaCommunity.community.id),
|
createPost(alpha, betaCommunity.community.id),
|
||||||
).rejects.toStrictEqual(Error("banned_from_community"));
|
).rejects.toStrictEqual(Error("person_is_banned_from_community"));
|
||||||
|
|
||||||
// Unban alpha
|
// Unban alpha
|
||||||
let unBanAlpha = await banPersonFromCommunity(
|
let unBanAlpha = await banPersonFromCommunity(
|
||||||
|
@ -789,3 +794,29 @@ test("Fetch post with redirect", async () => {
|
||||||
let gammaPost2 = await gamma.resolveObject(form);
|
let gammaPost2 = await gamma.resolveObject(form);
|
||||||
expect(gammaPost2.post).toBeDefined();
|
expect(gammaPost2.post).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Rewrite markdown links", async () => {
|
||||||
|
const community = (await resolveBetaCommunity(beta)).community!;
|
||||||
|
|
||||||
|
// create a post
|
||||||
|
let postRes1 = await createPost(beta, community.community.id);
|
||||||
|
|
||||||
|
// link to this post in markdown
|
||||||
|
let postRes2 = await createPost(
|
||||||
|
beta,
|
||||||
|
community.community.id,
|
||||||
|
"https://example.com/",
|
||||||
|
`[link](${postRes1.post_view.post.ap_id})`,
|
||||||
|
);
|
||||||
|
console.log(postRes2.post_view.post.body);
|
||||||
|
expect(postRes2.post_view.post).toBeDefined();
|
||||||
|
|
||||||
|
// fetch both posts from another instance
|
||||||
|
const alphaPost1 = await resolvePost(alpha, postRes1.post_view.post);
|
||||||
|
const alphaPost2 = await resolvePost(alpha, postRes2.post_view.post);
|
||||||
|
|
||||||
|
// remote markdown link is replaced with local link
|
||||||
|
expect(alphaPost2.post?.post.body).toBe(
|
||||||
|
`[link](http://lemmy-alpha:8541/post/${alphaPost1.post?.post.id})`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
214
api_tests/src/private_community.spec.ts
Normal file
214
api_tests/src/private_community.spec.ts
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
|
import { FollowCommunity } from "lemmy-js-client";
|
||||||
|
import {
|
||||||
|
alpha,
|
||||||
|
setupLogins,
|
||||||
|
createCommunity,
|
||||||
|
unfollows,
|
||||||
|
registerUser,
|
||||||
|
listCommunityPendingFollows,
|
||||||
|
getCommunity,
|
||||||
|
getCommunityPendingFollowsCount,
|
||||||
|
approveCommunityPendingFollow,
|
||||||
|
randomString,
|
||||||
|
createPost,
|
||||||
|
createComment,
|
||||||
|
beta,
|
||||||
|
resolveCommunity,
|
||||||
|
betaUrl,
|
||||||
|
resolvePost,
|
||||||
|
resolveComment,
|
||||||
|
likeComment,
|
||||||
|
waitUntil,
|
||||||
|
} from "./shared";
|
||||||
|
|
||||||
|
beforeAll(setupLogins);
|
||||||
|
afterAll(unfollows);
|
||||||
|
|
||||||
|
test("Follow a private community", async () => {
|
||||||
|
// create private community
|
||||||
|
const community = await createCommunity(alpha, randomString(10), "Private");
|
||||||
|
expect(community.community_view.community.visibility).toBe("Private");
|
||||||
|
const alphaCommunityId = community.community_view.community.id;
|
||||||
|
|
||||||
|
// No pending follows yet
|
||||||
|
const pendingFollows0 = await listCommunityPendingFollows(alpha);
|
||||||
|
expect(pendingFollows0.items.length).toBe(0);
|
||||||
|
const pendingFollowsCount0 = await getCommunityPendingFollowsCount(
|
||||||
|
alpha,
|
||||||
|
alphaCommunityId,
|
||||||
|
);
|
||||||
|
expect(pendingFollowsCount0.count).toBe(0);
|
||||||
|
|
||||||
|
// follow as new user
|
||||||
|
const user = await registerUser(beta, betaUrl);
|
||||||
|
const betaCommunity = (
|
||||||
|
await resolveCommunity(user, community.community_view.community.actor_id)
|
||||||
|
).community;
|
||||||
|
expect(betaCommunity).toBeDefined();
|
||||||
|
const betaCommunityId = betaCommunity!.community.id;
|
||||||
|
const follow_form: FollowCommunity = {
|
||||||
|
community_id: betaCommunityId,
|
||||||
|
follow: true,
|
||||||
|
};
|
||||||
|
await user.followCommunity(follow_form);
|
||||||
|
|
||||||
|
// Follow listed as pending
|
||||||
|
const follow1 = await getCommunity(user, betaCommunityId);
|
||||||
|
expect(follow1.community_view.subscribed).toBe("ApprovalRequired");
|
||||||
|
|
||||||
|
// Wait for follow to federate, shown as pending
|
||||||
|
let pendingFollows1 = await waitUntil(
|
||||||
|
() => listCommunityPendingFollows(alpha),
|
||||||
|
f => f.items.length == 1,
|
||||||
|
);
|
||||||
|
expect(pendingFollows1.items[0].is_new_instance).toBe(true);
|
||||||
|
const pendingFollowsCount1 = await getCommunityPendingFollowsCount(
|
||||||
|
alpha,
|
||||||
|
alphaCommunityId,
|
||||||
|
);
|
||||||
|
expect(pendingFollowsCount1.count).toBe(1);
|
||||||
|
|
||||||
|
// user still sees approval required at this point
|
||||||
|
const betaCommunity2 = await getCommunity(user, betaCommunityId);
|
||||||
|
expect(betaCommunity2.community_view.subscribed).toBe("ApprovalRequired");
|
||||||
|
|
||||||
|
// Approve the follow
|
||||||
|
const approve = await approveCommunityPendingFollow(
|
||||||
|
alpha,
|
||||||
|
alphaCommunityId,
|
||||||
|
pendingFollows1.items[0].person.id,
|
||||||
|
);
|
||||||
|
expect(approve.success).toBe(true);
|
||||||
|
|
||||||
|
// Follow is confirmed
|
||||||
|
await waitUntil(
|
||||||
|
() => getCommunity(user, betaCommunityId),
|
||||||
|
c => c.community_view.subscribed == "Subscribed",
|
||||||
|
);
|
||||||
|
const pendingFollows2 = await listCommunityPendingFollows(alpha);
|
||||||
|
expect(pendingFollows2.items.length).toBe(0);
|
||||||
|
const pendingFollowsCount2 = await getCommunityPendingFollowsCount(
|
||||||
|
alpha,
|
||||||
|
alphaCommunityId,
|
||||||
|
);
|
||||||
|
expect(pendingFollowsCount2.count).toBe(0);
|
||||||
|
|
||||||
|
// follow with another user from that instance, is_new_instance should be false now
|
||||||
|
const user2 = await registerUser(beta, betaUrl);
|
||||||
|
await user2.followCommunity(follow_form);
|
||||||
|
let pendingFollows3 = await waitUntil(
|
||||||
|
() => listCommunityPendingFollows(alpha),
|
||||||
|
f => f.items.length == 1,
|
||||||
|
);
|
||||||
|
expect(pendingFollows3.items[0].is_new_instance).toBe(false);
|
||||||
|
|
||||||
|
// cleanup pending follow
|
||||||
|
const approve2 = await approveCommunityPendingFollow(
|
||||||
|
alpha,
|
||||||
|
alphaCommunityId,
|
||||||
|
pendingFollows3.items[0].person.id,
|
||||||
|
);
|
||||||
|
expect(approve2.success).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Only followers can view and interact with private community content", async () => {
|
||||||
|
// create private community
|
||||||
|
const community = await createCommunity(alpha, randomString(10), "Private");
|
||||||
|
expect(community.community_view.community.visibility).toBe("Private");
|
||||||
|
const alphaCommunityId = community.community_view.community.id;
|
||||||
|
|
||||||
|
// create post and comment
|
||||||
|
const post0 = await createPost(alpha, alphaCommunityId);
|
||||||
|
const post_id = post0.post_view.post.id;
|
||||||
|
expect(post_id).toBeDefined();
|
||||||
|
const comment = await createComment(alpha, post_id);
|
||||||
|
const comment_id = comment.comment_view.comment.id;
|
||||||
|
expect(comment_id).toBeDefined();
|
||||||
|
|
||||||
|
// user is not following the community and cannot view nor create posts
|
||||||
|
const user = await registerUser(beta, betaUrl);
|
||||||
|
const betaCommunity = (
|
||||||
|
await resolveCommunity(user, community.community_view.community.actor_id)
|
||||||
|
).community!.community;
|
||||||
|
await expect(resolvePost(user, post0.post_view.post)).rejects.toStrictEqual(
|
||||||
|
Error("not_found"),
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
resolveComment(user, comment.comment_view.comment),
|
||||||
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
await expect(createPost(user, betaCommunity.id)).rejects.toStrictEqual(
|
||||||
|
Error("not_found"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// follow the community and approve
|
||||||
|
const follow_form: FollowCommunity = {
|
||||||
|
community_id: betaCommunity.id,
|
||||||
|
follow: true,
|
||||||
|
};
|
||||||
|
await user.followCommunity(follow_form);
|
||||||
|
const pendingFollows1 = await waitUntil(
|
||||||
|
() => listCommunityPendingFollows(alpha),
|
||||||
|
f => f.items.length == 1,
|
||||||
|
);
|
||||||
|
const approve = await approveCommunityPendingFollow(
|
||||||
|
alpha,
|
||||||
|
alphaCommunityId,
|
||||||
|
pendingFollows1.items[0].person.id,
|
||||||
|
);
|
||||||
|
expect(approve.success).toBe(true);
|
||||||
|
|
||||||
|
// now user can fetch posts and comments in community (using signed fetch), and create posts
|
||||||
|
await waitUntil(
|
||||||
|
() => resolvePost(user, post0.post_view.post),
|
||||||
|
p => p?.post?.post.id != undefined,
|
||||||
|
);
|
||||||
|
const resolvedComment = (
|
||||||
|
await resolveComment(user, comment.comment_view.comment)
|
||||||
|
).comment;
|
||||||
|
expect(resolvedComment?.comment.id).toBeDefined();
|
||||||
|
|
||||||
|
const post1 = await createPost(user, betaCommunity.id);
|
||||||
|
expect(post1.post_view).toBeDefined();
|
||||||
|
const like = await likeComment(user, 1, resolvedComment!.comment);
|
||||||
|
expect(like.comment_view.my_vote).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Reject follower", async () => {
|
||||||
|
// create private community
|
||||||
|
const community = await createCommunity(alpha, randomString(10), "Private");
|
||||||
|
expect(community.community_view.community.visibility).toBe("Private");
|
||||||
|
const alphaCommunityId = community.community_view.community.id;
|
||||||
|
|
||||||
|
// user is not following the community and cannot view nor create posts
|
||||||
|
const user = await registerUser(beta, betaUrl);
|
||||||
|
const betaCommunity1 = (
|
||||||
|
await resolveCommunity(user, community.community_view.community.actor_id)
|
||||||
|
).community!.community;
|
||||||
|
|
||||||
|
// follow the community and reject
|
||||||
|
const follow_form: FollowCommunity = {
|
||||||
|
community_id: betaCommunity1.id,
|
||||||
|
follow: true,
|
||||||
|
};
|
||||||
|
const follow = await user.followCommunity(follow_form);
|
||||||
|
expect(follow.community_view.subscribed).toBe("ApprovalRequired");
|
||||||
|
|
||||||
|
const pendingFollows1 = await waitUntil(
|
||||||
|
() => listCommunityPendingFollows(alpha),
|
||||||
|
f => f.items.length == 1,
|
||||||
|
);
|
||||||
|
const approve = await approveCommunityPendingFollow(
|
||||||
|
alpha,
|
||||||
|
alphaCommunityId,
|
||||||
|
pendingFollows1.items[0].person.id,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(approve.success).toBe(true);
|
||||||
|
|
||||||
|
await waitUntil(
|
||||||
|
() => getCommunity(user, betaCommunity1.id),
|
||||||
|
c => c.community_view.subscribed == "NotSubscribed",
|
||||||
|
);
|
||||||
|
});
|
|
@ -1,17 +1,24 @@
|
||||||
import {
|
import {
|
||||||
|
ApproveCommunityPendingFollower,
|
||||||
BlockCommunity,
|
BlockCommunity,
|
||||||
BlockCommunityResponse,
|
BlockCommunityResponse,
|
||||||
BlockInstance,
|
BlockInstance,
|
||||||
BlockInstanceResponse,
|
BlockInstanceResponse,
|
||||||
CommunityId,
|
CommunityId,
|
||||||
|
CommunityVisibility,
|
||||||
CreatePrivateMessageReport,
|
CreatePrivateMessageReport,
|
||||||
DeleteImage,
|
DeleteImage,
|
||||||
EditCommunity,
|
EditCommunity,
|
||||||
|
GetCommunityPendingFollowsCount,
|
||||||
|
GetCommunityPendingFollowsCountResponse,
|
||||||
GetReplies,
|
GetReplies,
|
||||||
GetRepliesResponse,
|
GetRepliesResponse,
|
||||||
GetUnreadCountResponse,
|
GetUnreadCountResponse,
|
||||||
InstanceId,
|
InstanceId,
|
||||||
LemmyHttp,
|
LemmyHttp,
|
||||||
|
ListCommunityPendingFollows,
|
||||||
|
ListCommunityPendingFollowsResponse,
|
||||||
|
PersonId,
|
||||||
PostView,
|
PostView,
|
||||||
PrivateMessageReportResponse,
|
PrivateMessageReportResponse,
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
|
@ -83,7 +90,7 @@ export const fetchFunction = fetch;
|
||||||
export const imageFetchLimit = 50;
|
export const imageFetchLimit = 50;
|
||||||
export const sampleImage =
|
export const sampleImage =
|
||||||
"https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg";
|
"https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg";
|
||||||
export const sampleSite = "https://yahoo.com";
|
export const sampleSite = "https://w3.org";
|
||||||
|
|
||||||
export const alphaUrl = "http://127.0.0.1:8541";
|
export const alphaUrl = "http://127.0.0.1:8541";
|
||||||
export const betaUrl = "http://127.0.0.1:8551";
|
export const betaUrl = "http://127.0.0.1:8551";
|
||||||
|
@ -198,7 +205,7 @@ export async function setupLogins() {
|
||||||
// only needed the first time so do in this try
|
// only needed the first time so do in this try
|
||||||
await delay(10_000);
|
await delay(10_000);
|
||||||
} catch {
|
} catch {
|
||||||
console.log("Communities already exist");
|
//console.log("Communities already exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,13 +426,13 @@ export async function banPersonFromSite(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
person_id: number,
|
person_id: number,
|
||||||
ban: boolean,
|
ban: boolean,
|
||||||
remove_data: boolean,
|
remove_or_restore_data: boolean,
|
||||||
): Promise<BanPersonResponse> {
|
): Promise<BanPersonResponse> {
|
||||||
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
||||||
let form: BanPerson = {
|
let form: BanPerson = {
|
||||||
person_id,
|
person_id,
|
||||||
ban,
|
ban,
|
||||||
remove_data,
|
remove_or_restore_data,
|
||||||
};
|
};
|
||||||
return api.banPerson(form);
|
return api.banPerson(form);
|
||||||
}
|
}
|
||||||
|
@ -434,13 +441,13 @@ export async function banPersonFromCommunity(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
person_id: number,
|
person_id: number,
|
||||||
community_id: number,
|
community_id: number,
|
||||||
remove_data: boolean,
|
remove_or_restore_data: boolean,
|
||||||
ban: boolean,
|
ban: boolean,
|
||||||
): Promise<BanFromCommunityResponse> {
|
): Promise<BanFromCommunityResponse> {
|
||||||
let form: BanFromCommunity = {
|
let form: BanFromCommunity = {
|
||||||
person_id,
|
person_id,
|
||||||
community_id,
|
community_id,
|
||||||
remove_data: remove_data,
|
remove_or_restore_data,
|
||||||
ban,
|
ban,
|
||||||
};
|
};
|
||||||
return api.banFromCommunity(form);
|
return api.banFromCommunity(form);
|
||||||
|
@ -554,12 +561,14 @@ export async function likeComment(
|
||||||
export async function createCommunity(
|
export async function createCommunity(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
name_: string = randomString(10),
|
name_: string = randomString(10),
|
||||||
|
visibility: CommunityVisibility = "Public",
|
||||||
): Promise<CommunityResponse> {
|
): Promise<CommunityResponse> {
|
||||||
let description = "a sample description";
|
let description = "a sample description";
|
||||||
let form: CreateCommunity = {
|
let form: CreateCommunity = {
|
||||||
name: name_,
|
name: name_,
|
||||||
title: name_,
|
title: name_,
|
||||||
description,
|
description,
|
||||||
|
visibility,
|
||||||
};
|
};
|
||||||
return api.createCommunity(form);
|
return api.createCommunity(form);
|
||||||
}
|
}
|
||||||
|
@ -688,9 +697,8 @@ export async function saveUserSettingsBio(
|
||||||
let form: SaveUserSettings = {
|
let form: SaveUserSettings = {
|
||||||
show_nsfw: true,
|
show_nsfw: true,
|
||||||
blur_nsfw: false,
|
blur_nsfw: false,
|
||||||
auto_expand: true,
|
|
||||||
theme: "darkly",
|
theme: "darkly",
|
||||||
default_sort_type: "Active",
|
default_post_sort_type: "Active",
|
||||||
default_listing_type: "All",
|
default_listing_type: "All",
|
||||||
interface_language: "en",
|
interface_language: "en",
|
||||||
show_avatars: true,
|
show_avatars: true,
|
||||||
|
@ -709,8 +717,7 @@ export async function saveUserSettingsFederated(
|
||||||
let form: SaveUserSettings = {
|
let form: SaveUserSettings = {
|
||||||
show_nsfw: false,
|
show_nsfw: false,
|
||||||
blur_nsfw: true,
|
blur_nsfw: true,
|
||||||
auto_expand: false,
|
default_post_sort_type: "Hot",
|
||||||
default_sort_type: "Hot",
|
|
||||||
default_listing_type: "All",
|
default_listing_type: "All",
|
||||||
interface_language: "",
|
interface_language: "",
|
||||||
avatar,
|
avatar,
|
||||||
|
@ -872,6 +879,39 @@ export function blockCommunity(
|
||||||
return api.blockCommunity(form);
|
return api.blockCommunity(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listCommunityPendingFollows(
|
||||||
|
api: LemmyHttp,
|
||||||
|
): Promise<ListCommunityPendingFollowsResponse> {
|
||||||
|
let form: ListCommunityPendingFollows = {
|
||||||
|
pending_only: true,
|
||||||
|
all_communities: false,
|
||||||
|
page: 1,
|
||||||
|
limit: 50,
|
||||||
|
};
|
||||||
|
return api.listCommunityPendingFollows(form);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCommunityPendingFollowsCount(
|
||||||
|
api: LemmyHttp,
|
||||||
|
community_id: CommunityId,
|
||||||
|
): Promise<GetCommunityPendingFollowsCountResponse> {
|
||||||
|
return api.getCommunityPendingFollowsCount(community_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function approveCommunityPendingFollow(
|
||||||
|
api: LemmyHttp,
|
||||||
|
community_id: CommunityId,
|
||||||
|
follower_id: PersonId,
|
||||||
|
approve: boolean = true,
|
||||||
|
): Promise<SuccessResponse> {
|
||||||
|
let form: ApproveCommunityPendingFollower = {
|
||||||
|
community_id,
|
||||||
|
follower_id,
|
||||||
|
approve,
|
||||||
|
};
|
||||||
|
return api.approveCommunityPendingFollow(form);
|
||||||
|
}
|
||||||
|
|
||||||
export function delay(millis = 500) {
|
export function delay(millis = 500) {
|
||||||
return new Promise(resolve => setTimeout(resolve, millis));
|
return new Promise(resolve => setTimeout(resolve, millis));
|
||||||
}
|
}
|
||||||
|
@ -962,8 +1002,12 @@ export async function waitUntil<T>(
|
||||||
let retry = 0;
|
let retry = 0;
|
||||||
let result;
|
let result;
|
||||||
while (retry++ < retries) {
|
while (retry++ < retries) {
|
||||||
result = await fetcher();
|
try {
|
||||||
if (checker(result)) return result;
|
result = await fetcher();
|
||||||
|
if (checker(result)) return result;
|
||||||
|
} catch (error) {
|
||||||
|
//console.error(error);
|
||||||
|
}
|
||||||
await delay(
|
await delay(
|
||||||
delaySeconds[Math.min(retry - 1, delaySeconds.length - 1)] * 1000,
|
delaySeconds[Math.min(retry - 1, delaySeconds.length - 1)] * 1000,
|
||||||
);
|
);
|
||||||
|
|
|
@ -75,6 +75,8 @@
|
||||||
"ProxyAllImages"
|
"ProxyAllImages"
|
||||||
# Timeout for uploading images to pictrs (in seconds)
|
# Timeout for uploading images to pictrs (in seconds)
|
||||||
upload_timeout: 30
|
upload_timeout: 30
|
||||||
|
# Resize post thumbnails to this maximum width/height.
|
||||||
|
max_thumbnail_size: 512
|
||||||
}
|
}
|
||||||
# Email sending configuration. All options except login/password are mandatory
|
# Email sending configuration. All options except login/password are mandatory
|
||||||
email: {
|
email: {
|
||||||
|
|
|
@ -22,12 +22,11 @@ pub async fn distinguish_comment(
|
||||||
data.comment_id,
|
data.comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
&orig_comment.community,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -40,7 +39,7 @@ pub async fn distinguish_comment(
|
||||||
// Verify that only a mod or admin can distinguish a comment
|
// Verify that only a mod or admin can distinguish a comment
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
&orig_comment.community,
|
||||||
false,
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -60,8 +59,7 @@ pub async fn distinguish_comment(
|
||||||
data.comment_id,
|
data.comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
Ok(Json(CommentResponse {
|
Ok(Json(CommentResponse {
|
||||||
comment_view,
|
comment_view,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, CreateCommentLike},
|
comment::{CommentResponse, CreateCommentLike},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_bot_account, check_community_user_action, check_downvotes_enabled},
|
utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
|
@ -27,25 +27,30 @@ pub async fn like_comment(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CommentResponse>> {
|
) -> LemmyResult<Json<CommentResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
let comment_id = data.comment_id;
|
||||||
|
|
||||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
let mut recipient_ids = Vec::<LocalUserId>::new();
|
||||||
|
|
||||||
// Don't do a downvote if site has downvotes disabled
|
check_local_vote_mode(
|
||||||
check_downvotes_enabled(data.score, &local_site)?;
|
data.score,
|
||||||
|
VoteItem::Comment(comment_id),
|
||||||
|
&local_site,
|
||||||
|
local_user_view.person.id,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
check_bot_account(&local_user_view.person)?;
|
check_bot_account(&local_user_view.person)?;
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
|
||||||
let orig_comment = CommentView::read(
|
let orig_comment = CommentView::read(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
&orig_comment.community,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -54,8 +59,7 @@ pub async fn like_comment(
|
||||||
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
|
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
|
||||||
if let Ok(Some(reply)) = comment_reply {
|
if let Ok(Some(reply)) = comment_reply {
|
||||||
let recipient_id = reply.recipient_id;
|
let recipient_id = reply.recipient_id;
|
||||||
if let Ok(Some(local_recipient)) =
|
if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
|
||||||
LocalUserView::read_person(&mut context.pool(), recipient_id).await
|
|
||||||
{
|
{
|
||||||
recipient_ids.push(local_recipient.local_user.id);
|
recipient_ids.push(local_recipient.local_user.id);
|
||||||
}
|
}
|
||||||
|
@ -63,7 +67,6 @@ pub async fn like_comment(
|
||||||
|
|
||||||
let like_form = CommentLikeForm {
|
let like_form = CommentLikeForm {
|
||||||
comment_id: data.comment_id,
|
comment_id: data.comment_id,
|
||||||
post_id: orig_comment.post.id,
|
|
||||||
person_id: local_user_view.person.id,
|
person_id: local_user_view.person.id,
|
||||||
score: data.score,
|
score: data.score,
|
||||||
};
|
};
|
||||||
|
@ -89,8 +92,7 @@ pub async fn like_comment(
|
||||||
score: data.score,
|
score: data.score,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
build_comment_response(
|
build_comment_response(
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
utils::is_mod_or_admin,
|
utils::is_mod_or_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists likes for a comment
|
/// Lists likes for a comment
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -19,8 +19,7 @@ pub async fn list_comment_likes(
|
||||||
data.comment_id,
|
data.comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
is_mod_or_admin(
|
is_mod_or_admin(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
|
|
|
@ -37,8 +37,7 @@ pub async fn save_comment(
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
Ok(Json(CommentResponse {
|
Ok(Json(CommentResponse {
|
||||||
comment_view,
|
comment_view,
|
||||||
|
|
|
@ -40,12 +40,11 @@ pub async fn create_comment_report(
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
comment_view.community.id,
|
&comment_view.community,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -64,9 +63,8 @@ pub async fn create_comment_report(
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
||||||
|
|
||||||
let comment_report_view = CommentReportView::read(&mut context.pool(), report.id, person_id)
|
let comment_report_view =
|
||||||
.await?
|
CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
|
|
||||||
|
|
||||||
// Email the admins
|
// Email the admins
|
||||||
if local_site.reports_email_admins {
|
if local_site.reports_email_admins {
|
||||||
|
@ -87,8 +85,7 @@ pub async fn create_comment_report(
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(CommentReportResponse {
|
Ok(Json(CommentReportResponse {
|
||||||
comment_report_view,
|
comment_report_view,
|
||||||
|
|
|
@ -17,14 +17,12 @@ pub async fn resolve_comment_report(
|
||||||
) -> LemmyResult<Json<CommentReportResponse>> {
|
) -> LemmyResult<Json<CommentReportResponse>> {
|
||||||
let report_id = data.report_id;
|
let report_id = data.report_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let report = CommentReportView::read(&mut context.pool(), report_id, person_id)
|
let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
report.community.id,
|
&report.community,
|
||||||
true,
|
true,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -41,9 +39,8 @@ pub async fn resolve_comment_report(
|
||||||
}
|
}
|
||||||
|
|
||||||
let report_id = data.report_id;
|
let report_id = data.report_id;
|
||||||
let comment_report_view = CommentReportView::read(&mut context.pool(), report_id, person_id)
|
let comment_report_view =
|
||||||
.await?
|
CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
|
|
||||||
|
|
||||||
Ok(Json(CommentReportResponse {
|
Ok(Json(CommentReportResponse {
|
||||||
comment_report_view,
|
comment_report_view,
|
||||||
|
|
|
@ -24,12 +24,11 @@ pub async fn add_mod_to_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<AddModToCommunityResponse>> {
|
) -> LemmyResult<Json<AddModToCommunityResponse>> {
|
||||||
let community_id = data.community_id;
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
|
|
||||||
// Verify that only mods or admins can add mod
|
// Verify that only mods or admins can add mod
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
community_id,
|
&community,
|
||||||
false,
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -39,30 +38,23 @@ pub async fn add_mod_to_community(
|
||||||
if !data.added {
|
if !data.added {
|
||||||
LocalUser::is_higher_mod_or_admin_check(
|
LocalUser::is_higher_mod_or_admin_check(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
community_id,
|
community.id,
|
||||||
local_user_view.person.id,
|
local_user_view.person.id,
|
||||||
vec![data.person_id],
|
vec![data.person_id],
|
||||||
)
|
)
|
||||||
.await?;
|
.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
|
// 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
|
// moderator. This is necessary because otherwise the action would be rejected
|
||||||
// by the community's home instance.
|
// by the community's home instance.
|
||||||
if local_user_view.local_user.admin && !community.local {
|
if local_user_view.local_user.admin && !community.local {
|
||||||
let is_mod = CommunityModeratorView::is_community_moderator(
|
CommunityModeratorView::check_is_community_moderator(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
community.id,
|
community.id,
|
||||||
local_user_view.person.id,
|
local_user_view.person.id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if !is_mod {
|
|
||||||
Err(LemmyErrorType::NotAModerator)?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update in local database
|
// Update in local database
|
||||||
|
@ -103,8 +95,7 @@ pub async fn add_mod_to_community(
|
||||||
added: data.added,
|
added: data.added,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(AddModToCommunityResponse { moderators }))
|
Ok(Json(AddModToCommunityResponse { moderators }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,16 @@ use lemmy_api_common::{
|
||||||
community::{BanFromCommunity, BanFromCommunityResponse},
|
community::{BanFromCommunity, BanFromCommunityResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community},
|
utils::{
|
||||||
|
check_community_mod_action,
|
||||||
|
check_expire_time,
|
||||||
|
remove_or_restore_user_data_in_community,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{
|
community::{
|
||||||
|
Community,
|
||||||
CommunityFollower,
|
CommunityFollower,
|
||||||
CommunityFollowerForm,
|
CommunityFollowerForm,
|
||||||
CommunityPersonBan,
|
CommunityPersonBan,
|
||||||
|
@ -33,13 +38,13 @@ pub async fn ban_from_community(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<BanFromCommunityResponse>> {
|
) -> LemmyResult<Json<BanFromCommunityResponse>> {
|
||||||
let banned_person_id = data.person_id;
|
let banned_person_id = data.person_id;
|
||||||
let remove_data = data.remove_data.unwrap_or(false);
|
|
||||||
let expires = check_expire_time(data.expires)?;
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
|
|
||||||
// Verify that only mods or admins can ban
|
// Verify that only mods or admins can ban
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
data.community_id,
|
&community,
|
||||||
false,
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -69,12 +74,7 @@ pub async fn ban_from_community(
|
||||||
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
|
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
|
||||||
|
|
||||||
// Also unsubscribe them from the community, if they are subscribed
|
// Also unsubscribe them from the community, if they are subscribed
|
||||||
let community_follower_form = CommunityFollowerForm {
|
let community_follower_form = CommunityFollowerForm::new(data.community_id, banned_person_id);
|
||||||
community_id: data.community_id,
|
|
||||||
person_id: banned_person_id,
|
|
||||||
pending: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
@ -85,9 +85,18 @@ pub async fn ban_from_community(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove/Restore their data if that's desired
|
// Remove/Restore their data if that's desired
|
||||||
if remove_data {
|
if data.remove_or_restore_data.unwrap_or(false) {
|
||||||
remove_user_data_in_community(data.community_id, banned_person_id, &mut context.pool()).await?;
|
let remove_data = data.ban;
|
||||||
}
|
remove_or_restore_user_data_in_community(
|
||||||
|
data.community_id,
|
||||||
|
local_user_view.person.id,
|
||||||
|
banned_person_id,
|
||||||
|
remove_data,
|
||||||
|
&data.reason,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let form = ModBanFromCommunityForm {
|
let form = ModBanFromCommunityForm {
|
||||||
|
@ -101,9 +110,7 @@ pub async fn ban_from_community(
|
||||||
|
|
||||||
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
|
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let person_view = PersonView::read(&mut context.pool(), data.person_id)
|
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::BanFromCommunity {
|
SendActivityData::BanFromCommunity {
|
||||||
|
@ -113,8 +120,7 @@ pub async fn ban_from_community(
|
||||||
data: data.0.clone(),
|
data: data.0.clone(),
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(BanFromCommunityResponse {
|
Ok(Json(BanFromCommunityResponse {
|
||||||
person_view,
|
person_view,
|
||||||
|
|
|
@ -35,12 +35,7 @@ pub async fn block_community(
|
||||||
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
|
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
|
||||||
|
|
||||||
// Also, unfollow the community, and send a federated unfollow
|
// Also, unfollow the community, and send a federated unfollow
|
||||||
let community_follower_form = CommunityFollowerForm {
|
let community_follower_form = CommunityFollowerForm::new(data.community_id, person_id);
|
||||||
community_id: data.community_id,
|
|
||||||
person_id,
|
|
||||||
pending: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
@ -56,8 +51,7 @@ pub async fn block_community(
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::FollowCommunity(
|
SendActivityData::FollowCommunity(
|
||||||
|
@ -66,8 +60,7 @@ pub async fn block_community(
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(BlockCommunityResponse {
|
Ok(Json(BlockCommunityResponse {
|
||||||
blocked: data.block,
|
blocked: data.block,
|
||||||
|
|
|
@ -4,17 +4,18 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, FollowCommunity},
|
community::{CommunityResponse, FollowCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::check_community_user_action,
|
utils::{check_community_deleted_removed, check_user_valid},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
community::{Community, CommunityFollower, CommunityFollowerForm},
|
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityFollowerState},
|
||||||
},
|
},
|
||||||
traits::{Crud, Followable},
|
traits::{Crud, Followable},
|
||||||
|
CommunityVisibility,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommunityView;
|
use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -23,42 +24,52 @@ pub async fn follow_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CommunityResponse>> {
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
let community = Community::read(&mut context.pool(), data.community_id)
|
check_user_valid(&local_user_view.person)?;
|
||||||
.await?
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
let form = CommunityFollowerForm::new(community.id, local_user_view.person.id);
|
||||||
let mut community_follower_form = CommunityFollowerForm {
|
|
||||||
community_id: community.id,
|
|
||||||
person_id: local_user_view.person.id,
|
|
||||||
pending: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if data.follow {
|
if data.follow {
|
||||||
|
// Only run these checks for local community, in case of remote community the local
|
||||||
|
// state may be outdated. Can't use check_community_user_action() here as it only allows
|
||||||
|
// actions from existing followers for private community (so following would be impossible).
|
||||||
if community.local {
|
if community.local {
|
||||||
check_community_user_action(&local_user_view.person, community.id, &mut context.pool())
|
check_community_deleted_removed(&community)?;
|
||||||
|
CommunityPersonBanView::check(&mut context.pool(), local_user_view.person.id, community.id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
|
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
|
|
||||||
} else {
|
|
||||||
// Mark as pending, the actual federation activity is sent via `SendActivity` handler
|
|
||||||
community_follower_form.pending = true;
|
|
||||||
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
|
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let state = if community.local {
|
||||||
|
// Local follow is accepted immediately
|
||||||
|
Some(CommunityFollowerState::Accepted)
|
||||||
|
} else if community.visibility == CommunityVisibility::Private {
|
||||||
|
// Private communities require manual approval
|
||||||
|
Some(CommunityFollowerState::ApprovalRequired)
|
||||||
|
} else {
|
||||||
|
// remote follow needs to be federated first
|
||||||
|
Some(CommunityFollowerState::Pending)
|
||||||
|
};
|
||||||
|
|
||||||
|
let form = CommunityFollowerForm {
|
||||||
|
state,
|
||||||
|
..CommunityFollowerForm::new(community.id, local_user_view.person.id)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write to db
|
||||||
|
CommunityFollower::follow(&mut context.pool(), &form)
|
||||||
|
.await
|
||||||
|
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
|
||||||
} else {
|
} else {
|
||||||
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
CommunityFollower::unfollow(&mut context.pool(), &form)
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
|
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send the federated follow
|
||||||
if !community.local {
|
if !community.local {
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::FollowCommunity(community, local_user_view.person.clone(), data.follow),
|
SendActivityData::FollowCommunity(community, local_user_view.person.clone(), data.follow),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
@ -68,8 +79,7 @@ pub async fn follow_community(
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
|
|
||||||
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,7 @@ pub async fn hide_community(
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
|
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,6 @@ pub mod ban;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod follow;
|
pub mod follow;
|
||||||
pub mod hide;
|
pub mod hide;
|
||||||
|
pub mod pending_follows;
|
||||||
|
pub mod random;
|
||||||
pub mod transfer;
|
pub mod transfer;
|
||||||
|
|
46
crates/api/src/community/pending_follows/approve.rs
Normal file
46
crates/api/src/community/pending_follows/approve.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
community::ApproveCommunityPendingFollower,
|
||||||
|
context::LemmyContext,
|
||||||
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
|
utils::is_mod_or_admin,
|
||||||
|
SuccessResponse,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::community::{CommunityFollower, CommunityFollowerForm},
|
||||||
|
traits::Followable,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
pub async fn post_pending_follows_approve(
|
||||||
|
data: Json<ApproveCommunityPendingFollower>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
|
is_mod_or_admin(
|
||||||
|
&mut context.pool(),
|
||||||
|
&local_user_view.person,
|
||||||
|
data.community_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let activity_data = if data.approve {
|
||||||
|
CommunityFollower::approve(
|
||||||
|
&mut context.pool(),
|
||||||
|
data.community_id,
|
||||||
|
data.follower_id,
|
||||||
|
local_user_view.person.id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
SendActivityData::AcceptFollower(data.community_id, data.follower_id)
|
||||||
|
} else {
|
||||||
|
let form = CommunityFollowerForm::new(data.community_id, data.follower_id);
|
||||||
|
CommunityFollower::unfollow(&mut context.pool(), &form).await?;
|
||||||
|
SendActivityData::RejectFollower(data.community_id, data.follower_id)
|
||||||
|
};
|
||||||
|
ActivityChannel::submit_activity(activity_data, &context)?;
|
||||||
|
|
||||||
|
Ok(Json(SuccessResponse::default()))
|
||||||
|
}
|
25
crates/api/src/community/pending_follows/count.rs
Normal file
25
crates/api/src/community/pending_follows/count.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use actix_web::web::{Data, Json, Query};
|
||||||
|
use lemmy_api_common::{
|
||||||
|
community::{GetCommunityPendingFollowsCount, GetCommunityPendingFollowsCountResponse},
|
||||||
|
context::LemmyContext,
|
||||||
|
utils::is_mod_or_admin,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
pub async fn get_pending_follows_count(
|
||||||
|
data: Query<GetCommunityPendingFollowsCount>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> LemmyResult<Json<GetCommunityPendingFollowsCountResponse>> {
|
||||||
|
is_mod_or_admin(
|
||||||
|
&mut context.pool(),
|
||||||
|
&local_user_view.person,
|
||||||
|
data.community_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let count =
|
||||||
|
CommunityFollowerView::count_approval_required(&mut context.pool(), data.community_id).await?;
|
||||||
|
Ok(Json(GetCommunityPendingFollowsCountResponse { count }))
|
||||||
|
}
|
29
crates/api/src/community/pending_follows/list.rs
Normal file
29
crates/api/src/community/pending_follows/list.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use actix_web::web::{Data, Json, Query};
|
||||||
|
use lemmy_api_common::{
|
||||||
|
community::{ListCommunityPendingFollows, ListCommunityPendingFollowsResponse},
|
||||||
|
context::LemmyContext,
|
||||||
|
utils::check_community_mod_of_any_or_admin_action,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
pub async fn get_pending_follows_list(
|
||||||
|
data: Query<ListCommunityPendingFollows>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> LemmyResult<Json<ListCommunityPendingFollowsResponse>> {
|
||||||
|
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
|
||||||
|
let all_communities =
|
||||||
|
data.all_communities.unwrap_or_default() && local_user_view.local_user.admin;
|
||||||
|
let items = CommunityFollowerView::list_approval_required(
|
||||||
|
&mut context.pool(),
|
||||||
|
local_user_view.person.id,
|
||||||
|
all_communities,
|
||||||
|
data.pending_only.unwrap_or_default(),
|
||||||
|
data.page,
|
||||||
|
data.limit,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(ListCommunityPendingFollowsResponse { items }))
|
||||||
|
}
|
3
crates/api/src/community/pending_follows/mod.rs
Normal file
3
crates/api/src/community/pending_follows/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod approve;
|
||||||
|
pub mod count;
|
||||||
|
pub mod list;
|
55
crates/api/src/community/random.rs
Normal file
55
crates/api/src/community/random.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::{Json, Query};
|
||||||
|
use lemmy_api_common::{
|
||||||
|
community::{CommunityResponse, GetRandomCommunity},
|
||||||
|
context::LemmyContext,
|
||||||
|
utils::{check_private_instance, is_mod_or_admin_opt},
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::source::{
|
||||||
|
actor_language::CommunityLanguage,
|
||||||
|
community::Community,
|
||||||
|
local_site::LocalSite,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_db_views_actor::structs::CommunityView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn get_random_community(
|
||||||
|
data: Query<GetRandomCommunity>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: Option<LocalUserView>,
|
||||||
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
|
|
||||||
|
let local_user = local_user_view.as_ref().map(|u| &u.local_user);
|
||||||
|
|
||||||
|
let random_community_id =
|
||||||
|
Community::get_random_community_id(&mut context.pool(), &data.type_).await?;
|
||||||
|
|
||||||
|
let is_mod_or_admin = is_mod_or_admin_opt(
|
||||||
|
&mut context.pool(),
|
||||||
|
local_user_view.as_ref(),
|
||||||
|
Some(random_community_id),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
let community_view = CommunityView::read(
|
||||||
|
&mut context.pool(),
|
||||||
|
random_community_id,
|
||||||
|
local_user,
|
||||||
|
is_mod_or_admin,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let discussion_languages =
|
||||||
|
CommunityLanguage::read(&mut context.pool(), random_community_id).await?;
|
||||||
|
|
||||||
|
Ok(Json(CommunityResponse {
|
||||||
|
community_view,
|
||||||
|
discussion_languages,
|
||||||
|
}))
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
moderator::{ModTransferCommunity, ModTransferCommunityForm},
|
moderator::{ModTransferCommunity, ModTransferCommunityForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Joinable},
|
traits::{Crud, Joinable},
|
||||||
|
@ -27,11 +27,11 @@ pub async fn transfer_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<GetCommunityResponse>> {
|
) -> LemmyResult<Json<GetCommunityResponse>> {
|
||||||
let community_id = data.community_id;
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
let mut community_mods =
|
let mut community_mods =
|
||||||
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
CommunityModeratorView::for_community(&mut context.pool(), community.id).await?;
|
||||||
|
|
||||||
check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?;
|
check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?;
|
||||||
|
|
||||||
// Make sure transferrer is either the top community mod, or an admin
|
// Make sure transferrer is either the top community mod, or an admin
|
||||||
if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok())
|
if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok())
|
||||||
|
@ -82,13 +82,10 @@ pub async fn transfer_community(
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id)
|
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(Json(GetCommunityResponse {
|
Ok(Json(GetCommunityResponse {
|
||||||
|
|
|
@ -172,7 +172,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
||||||
target: &Person,
|
target: &Person,
|
||||||
ban: bool,
|
ban: bool,
|
||||||
reason: &Option<String>,
|
reason: &Option<String>,
|
||||||
remove_data: &Option<bool>,
|
remove_or_restore_data: &Option<bool>,
|
||||||
expires: &Option<i64>,
|
expires: &Option<i64>,
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
|
@ -197,11 +197,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Also unsubscribe them from the community, if they are subscribed
|
// Also unsubscribe them from the community, if they are subscribed
|
||||||
let community_follower_form = CommunityFollowerForm {
|
let community_follower_form = CommunityFollowerForm::new(community_id, target.id);
|
||||||
community_id,
|
|
||||||
person_id: target.id,
|
|
||||||
pending: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
||||||
.await
|
.await
|
||||||
|
@ -230,7 +226,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
||||||
person_id: target.id,
|
person_id: target.id,
|
||||||
ban,
|
ban,
|
||||||
reason: reason.clone(),
|
reason: reason.clone(),
|
||||||
remove_data: *remove_data,
|
remove_or_restore_data: *remove_or_restore_data,
|
||||||
expires: *expires,
|
expires: *expires,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -242,8 +238,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
||||||
data: ban_from_community,
|
data: ban_from_community,
|
||||||
},
|
},
|
||||||
context,
|
context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,17 +253,13 @@ pub async fn local_user_view_from_jwt(
|
||||||
let local_user_id = Claims::validate(jwt, context)
|
let local_user_id = Claims::validate(jwt, context)
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
||||||
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id)
|
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
|
|
||||||
check_user_valid(&local_user_view.person)?;
|
check_user_valid(&local_user_view.person)?;
|
||||||
|
|
||||||
Ok(local_user_view)
|
Ok(local_user_view)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -36,8 +36,8 @@ pub async fn add_admin(
|
||||||
|
|
||||||
// Make sure that the person_id added is local
|
// Make sure that the person_id added is local
|
||||||
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
|
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
|
||||||
.await?
|
.await
|
||||||
.ok_or(LemmyErrorType::ObjectNotLocal)?;
|
.map_err(|_| LemmyErrorType::ObjectNotLocal)?;
|
||||||
|
|
||||||
LocalUser::update(
|
LocalUser::update(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{BanPerson, BanPersonResponse},
|
person::{BanPerson, BanPersonResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_expire_time, is_admin, remove_user_data},
|
utils::{check_expire_time, is_admin, remove_or_restore_user_data},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -60,15 +60,22 @@ pub async fn ban_from_site(
|
||||||
|
|
||||||
// if its a local user, invalidate logins
|
// if its a local user, invalidate logins
|
||||||
let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await;
|
let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await;
|
||||||
if let Ok(Some(local_user)) = local_user {
|
if let Ok(local_user) = local_user {
|
||||||
LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?;
|
LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove their data if that's desired
|
// Remove their data if that's desired
|
||||||
let remove_data = data.remove_data.unwrap_or(false);
|
if data.remove_or_restore_data.unwrap_or(false) {
|
||||||
if remove_data {
|
let removed = data.ban;
|
||||||
remove_user_data(person.id, &context).await?;
|
remove_or_restore_user_data(
|
||||||
}
|
local_user_view.person.id,
|
||||||
|
person.id,
|
||||||
|
removed,
|
||||||
|
&data.reason,
|
||||||
|
&context,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let form = ModBanForm {
|
let form = ModBanForm {
|
||||||
|
@ -81,16 +88,14 @@ pub async fn ban_from_site(
|
||||||
|
|
||||||
ModBan::create(&mut context.pool(), &form).await?;
|
ModBan::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let person_view = PersonView::read(&mut context.pool(), person.id)
|
let person_view = PersonView::read(&mut context.pool(), person.id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
|
||||||
|
|
||||||
ban_nonlocal_user_from_local_communities(
|
ban_nonlocal_user_from_local_communities(
|
||||||
&local_user_view,
|
&local_user_view,
|
||||||
&person,
|
&person,
|
||||||
data.ban,
|
data.ban,
|
||||||
&data.reason,
|
&data.reason,
|
||||||
&data.remove_data,
|
&data.remove_or_restore_data,
|
||||||
&data.expires,
|
&data.expires,
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
|
@ -101,13 +106,12 @@ pub async fn ban_from_site(
|
||||||
moderator: local_user_view.person,
|
moderator: local_user_view.person,
|
||||||
banned_user: person_view.person.clone(),
|
banned_user: person_view.person.clone(),
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
remove_data: data.remove_data,
|
remove_or_restore_data: data.remove_or_restore_data,
|
||||||
ban: data.ban,
|
ban: data.ban,
|
||||||
expires: data.expires,
|
expires: data.expires,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(BanPersonResponse {
|
Ok(Json(BanPersonResponse {
|
||||||
person_view,
|
person_view,
|
||||||
|
|
|
@ -32,8 +32,7 @@ pub async fn block_person(
|
||||||
|
|
||||||
let target_user = LocalUserView::read_person(&mut context.pool(), target_id)
|
let target_user = LocalUserView::read_person(&mut context.pool(), target_id)
|
||||||
.await
|
.await
|
||||||
.ok()
|
.ok();
|
||||||
.flatten();
|
|
||||||
|
|
||||||
if target_user.is_some_and(|t| t.local_user.admin) {
|
if target_user.is_some_and(|t| t.local_user.admin) {
|
||||||
Err(LemmyErrorType::CantBlockAdmin)?
|
Err(LemmyErrorType::CantBlockAdmin)?
|
||||||
|
@ -49,9 +48,7 @@ pub async fn block_person(
|
||||||
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
|
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let person_view = PersonView::read(&mut context.pool(), target_id)
|
let person_view = PersonView::read(&mut context.pool(), target_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
|
||||||
Ok(Json(BlockPersonResponse {
|
Ok(Json(BlockPersonResponse {
|
||||||
person_view,
|
person_view,
|
||||||
blocked: data.block,
|
blocked: data.block,
|
||||||
|
|
|
@ -28,11 +28,13 @@ pub async fn change_password(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the old password
|
// Check the old password
|
||||||
let valid: bool = verify(
|
let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted
|
||||||
&data.old_password,
|
{
|
||||||
&local_user_view.local_user.password_encrypted,
|
verify(&data.old_password, password_encrypted).unwrap_or(false)
|
||||||
)
|
} else {
|
||||||
.unwrap_or(false);
|
data.old_password.is_empty()
|
||||||
|
};
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ pub async fn change_password_after_reset(
|
||||||
let token = data.token.clone();
|
let token = data.token.clone();
|
||||||
let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token)
|
let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(LemmyErrorType::TokenNotFound)?
|
|
||||||
.local_user_id;
|
.local_user_id;
|
||||||
|
|
||||||
password_length_check(&data.password)?;
|
password_length_check(&data.password)?;
|
||||||
|
|
|
@ -2,8 +2,11 @@ use crate::{build_totp_2fa, generate_totp_2fa_secret};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse};
|
use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse};
|
||||||
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
|
use lemmy_db_schema::source::{
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
|
site::Site,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
|
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
|
||||||
|
@ -13,17 +16,14 @@ pub async fn generate_totp_secret(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<Json<GenerateTotpSecretResponse>> {
|
) -> LemmyResult<Json<GenerateTotpSecretResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site = Site::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
|
|
||||||
if local_user_view.local_user.totp_2fa_enabled {
|
if local_user_view.local_user.totp_2fa_enabled {
|
||||||
return Err(LemmyErrorType::TotpAlreadyEnabled)?;
|
return Err(LemmyErrorType::TotpAlreadyEnabled)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let secret = generate_totp_2fa_secret();
|
let secret = generate_totp_2fa_secret();
|
||||||
let secret_url =
|
let secret_url = build_totp_2fa(&site.name, &local_user_view.person.name, &secret)?.get_url();
|
||||||
build_totp_2fa(&site_view.site.name, &local_user_view.person.name, &secret)?.get_url();
|
|
||||||
|
|
||||||
let local_user_form = LocalUserUpdateForm {
|
let local_user_form = LocalUserUpdateForm {
|
||||||
totp_2fa_secret: Some(Some(secret)),
|
totp_2fa_secret: Some(Some(secret)),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::{context::LemmyContext, person::ListLoginsResponse};
|
||||||
use lemmy_db_schema::source::login_token::LoginToken;
|
use lemmy_db_schema::source::login_token::LoginToken;
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
@ -7,8 +7,8 @@ use lemmy_utils::error::LemmyResult;
|
||||||
pub async fn list_logins(
|
pub async fn list_logins(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<Vec<LoginToken>>> {
|
) -> LemmyResult<Json<ListLoginsResponse>> {
|
||||||
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
|
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
|
||||||
|
|
||||||
Ok(Json(logins))
|
Ok(Json(ListLoginsResponse { logins }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{check_totp_2fa_valid, local_user::check_email_verified};
|
use crate::check_totp_2fa_valid;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
web::{Data, Json},
|
web::{Data, Json},
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
|
@ -8,12 +8,7 @@ use lemmy_api_common::{
|
||||||
claims::Claims,
|
claims::Claims,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{Login, LoginResponse},
|
person::{Login, LoginResponse},
|
||||||
utils::check_user_valid,
|
utils::{check_email_verified, check_registration_application, check_user_valid},
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
source::{local_site::LocalSite, registration_application::RegistrationApplication},
|
|
||||||
utils::DbPool,
|
|
||||||
RegistrationMode,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
@ -24,23 +19,20 @@ pub async fn login(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<Json<LoginResponse>> {
|
) -> LemmyResult<Json<LoginResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
|
|
||||||
// Fetch that username / email
|
// Fetch that username / email
|
||||||
let username_or_email = data.username_or_email.clone();
|
let username_or_email = data.username_or_email.clone();
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email)
|
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
|
||||||
|
|
||||||
// Verify the password
|
// Verify the password
|
||||||
let valid: bool = verify(
|
let valid: bool = local_user_view
|
||||||
&data.password,
|
.local_user
|
||||||
&local_user_view.local_user.password_encrypted,
|
.password_encrypted
|
||||||
)
|
.as_ref()
|
||||||
.unwrap_or(false);
|
.and_then(|password_encrypted| verify(&data.password, password_encrypted).ok())
|
||||||
|
.unwrap_or(false);
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
}
|
}
|
||||||
|
@ -67,28 +59,3 @@ pub async fn login(
|
||||||
registration_created: false,
|
registration_created: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_registration_application(
|
|
||||||
local_user_view: &LocalUserView,
|
|
||||||
local_site: &LocalSite,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
if (local_site.registration_mode == RegistrationMode::RequireApplication
|
|
||||||
|| local_site.registration_mode == RegistrationMode::Closed)
|
|
||||||
&& !local_user_view.local_user.accepted_application
|
|
||||||
&& !local_user_view.local_user.admin
|
|
||||||
{
|
|
||||||
// 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?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
|
||||||
if registration.admin_id.is_some() {
|
|
||||||
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
|
|
||||||
} else {
|
|
||||||
Err(LemmyErrorType::RegistrationApplicationIsPending)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
|
||||||
|
|
||||||
pub mod add_admin;
|
pub mod add_admin;
|
||||||
pub mod ban_person;
|
pub mod ban_person;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
|
@ -20,15 +17,3 @@ pub mod save_settings;
|
||||||
pub mod update_totp;
|
pub mod update_totp;
|
||||||
pub mod validate_auth;
|
pub mod validate_auth;
|
||||||
pub mod verify_email;
|
pub mod verify_email;
|
||||||
|
|
||||||
/// Check if the user's email is verified if email verification is turned on
|
|
||||||
/// However, skip checking verification if the user is an admin
|
|
||||||
fn check_email_verified(local_user_view: &LocalUserView, site_view: &SiteView) -> LemmyResult<()> {
|
|
||||||
if !local_user_view.local_user.admin
|
|
||||||
&& site_view.local_site.require_email_verification
|
|
||||||
&& !local_user_view.local_user.email_verified
|
|
||||||
{
|
|
||||||
Err(LemmyErrorType::EmailNotVerified)?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,9 +18,7 @@ pub async fn mark_person_mention_as_read(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<PersonMentionResponse>> {
|
) -> LemmyResult<Json<PersonMentionResponse>> {
|
||||||
let person_mention_id = data.person_mention_id;
|
let person_mention_id = data.person_mention_id;
|
||||||
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id)
|
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
|
|
||||||
|
|
||||||
if local_user_view.person.id != read_person_mention.recipient_id {
|
if local_user_view.person.id != read_person_mention.recipient_id {
|
||||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||||
|
@ -39,9 +37,7 @@ pub async fn mark_person_mention_as_read(
|
||||||
let person_mention_id = read_person_mention.id;
|
let person_mention_id = read_person_mention.id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let person_mention_view =
|
let person_mention_view =
|
||||||
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id))
|
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
|
|
||||||
|
|
||||||
Ok(Json(PersonMentionResponse {
|
Ok(Json(PersonMentionResponse {
|
||||||
person_mention_view,
|
person_mention_view,
|
||||||
|
|
|
@ -18,9 +18,7 @@ pub async fn mark_reply_as_read(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CommentReplyResponse>> {
|
) -> LemmyResult<Json<CommentReplyResponse>> {
|
||||||
let comment_reply_id = data.comment_reply_id;
|
let comment_reply_id = data.comment_reply_id;
|
||||||
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id)
|
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
|
|
||||||
|
|
||||||
if local_user_view.person.id != read_comment_reply.recipient_id {
|
if local_user_view.person.id != read_comment_reply.recipient_id {
|
||||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||||
|
@ -40,9 +38,7 @@ pub async fn mark_reply_as_read(
|
||||||
let comment_reply_id = read_comment_reply.id;
|
let comment_reply_id = read_comment_reply.id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let comment_reply_view =
|
let comment_reply_view =
|
||||||
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id))
|
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
|
|
||||||
|
|
||||||
Ok(Json(CommentReplyResponse { comment_reply_view }))
|
Ok(Json(CommentReplyResponse { comment_reply_view }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use crate::local_user::check_email_verified;
|
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::PasswordReset,
|
person::PasswordReset,
|
||||||
utils::send_password_reset_email,
|
utils::{check_email_verified, send_password_reset_email},
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
|
@ -17,12 +16,10 @@ pub async fn reset_password(
|
||||||
// Fetch that email
|
// Fetch that email
|
||||||
let email = data.email.to_lowercase();
|
let email = data.email.to_lowercase();
|
||||||
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
|
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
|
||||||
.await?
|
.await
|
||||||
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
.map_err(|_| LemmyErrorType::IncorrectLogin)?;
|
||||||
|
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
check_email_verified(&local_user_view, &site_view)?;
|
check_email_verified(&local_user_view, &site_view)?;
|
||||||
|
|
||||||
// Email the pure token to the user.
|
// Email the pure token to the user.
|
||||||
|
|
|
@ -36,9 +36,7 @@ pub async fn save_user_settings(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<SuccessResponse>> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
|
|
||||||
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
|
@ -65,9 +63,7 @@ pub async fn save_user_settings(
|
||||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
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 email was changed, check that it is not taken and send verification mail
|
||||||
if previous_email.deref() != email {
|
if previous_email.deref() != email {
|
||||||
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
|
LocalUser::check_is_email_taken(&mut context.pool(), email).await?;
|
||||||
return Err(LemmyErrorType::EmailAlreadyExists)?;
|
|
||||||
}
|
|
||||||
send_verification_email(
|
send_verification_email(
|
||||||
&local_user_view,
|
&local_user_view,
|
||||||
email,
|
email,
|
||||||
|
@ -104,7 +100,8 @@ pub async fn save_user_settings(
|
||||||
let local_user_id = local_user_view.local_user.id;
|
let local_user_id = local_user_view.local_user.id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let default_listing_type = data.default_listing_type;
|
let default_listing_type = data.default_listing_type;
|
||||||
let default_sort_type = data.default_sort_type;
|
let default_post_sort_type = data.default_post_sort_type;
|
||||||
|
let default_comment_sort_type = data.default_comment_sort_type;
|
||||||
|
|
||||||
let person_form = PersonUpdateForm {
|
let person_form = PersonUpdateForm {
|
||||||
display_name,
|
display_name,
|
||||||
|
@ -133,10 +130,9 @@ pub async fn save_user_settings(
|
||||||
send_notifications_to_email: data.send_notifications_to_email,
|
send_notifications_to_email: data.send_notifications_to_email,
|
||||||
show_nsfw: data.show_nsfw,
|
show_nsfw: data.show_nsfw,
|
||||||
blur_nsfw: data.blur_nsfw,
|
blur_nsfw: data.blur_nsfw,
|
||||||
auto_expand: data.auto_expand,
|
|
||||||
show_bot_accounts: data.show_bot_accounts,
|
show_bot_accounts: data.show_bot_accounts,
|
||||||
show_scores: data.show_scores,
|
default_post_sort_type,
|
||||||
default_sort_type,
|
default_comment_sort_type,
|
||||||
default_listing_type,
|
default_listing_type,
|
||||||
theme: data.theme.clone(),
|
theme: data.theme.clone(),
|
||||||
interface_language: data.interface_language.clone(),
|
interface_language: data.interface_language.clone(),
|
||||||
|
@ -145,6 +141,7 @@ pub async fn save_user_settings(
|
||||||
post_listing_mode: data.post_listing_mode,
|
post_listing_mode: data.post_listing_mode,
|
||||||
enable_keyboard_navigation: data.enable_keyboard_navigation,
|
enable_keyboard_navigation: data.enable_keyboard_navigation,
|
||||||
enable_animated_images: data.enable_animated_images,
|
enable_animated_images: data.enable_animated_images,
|
||||||
|
enable_private_messages: data.enable_private_messages,
|
||||||
collapse_bot_comments: data.collapse_bot_comments,
|
collapse_bot_comments: data.collapse_bot_comments,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,19 +10,15 @@ use lemmy_db_schema::source::{
|
||||||
local_user::{LocalUser, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
pub async fn verify_email(
|
pub async fn verify_email(
|
||||||
data: Json<VerifyEmail>,
|
data: Json<VerifyEmail>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<Json<SuccessResponse>> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
let token = data.token.clone();
|
let token = data.token.clone();
|
||||||
let verification = EmailVerification::read_for_token(&mut context.pool(), &token)
|
let verification = EmailVerification::read_for_token(&mut context.pool(), &token).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::TokenNotFound)?;
|
|
||||||
|
|
||||||
let form = LocalUserUpdateForm {
|
let form = LocalUserUpdateForm {
|
||||||
// necessary in case this is a new signup
|
// necessary in case this is a new signup
|
||||||
|
@ -39,9 +35,7 @@ pub async fn verify_email(
|
||||||
|
|
||||||
// send out notification about registration application to admins if enabled
|
// send out notification about registration application to admins if enabled
|
||||||
if site_view.local_site.application_email_admins {
|
if site_view.local_site.application_email_admins {
|
||||||
let local_user = LocalUserView::read(&mut context.pool(), local_user_id)
|
let local_user = LocalUserView::read(&mut context.pool(), local_user_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
|
||||||
|
|
||||||
send_new_applicant_email_to_admins(
|
send_new_applicant_email_to_admins(
|
||||||
&local_user.person.name,
|
&local_user.person.name,
|
||||||
|
|
|
@ -9,6 +9,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
community::Community,
|
||||||
moderator::{ModFeaturePost, ModFeaturePostForm},
|
moderator::{ModFeaturePost, ModFeaturePostForm},
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
|
@ -16,7 +17,7 @@ use lemmy_db_schema::{
|
||||||
PostFeatureType,
|
PostFeatureType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn feature_post(
|
pub async fn feature_post(
|
||||||
|
@ -25,13 +26,12 @@ pub async fn feature_post(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<PostResponse>> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id)
|
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
|
let community = Community::read(&mut context.pool(), orig_post.community_id).await?;
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_post.community_id,
|
&community,
|
||||||
false,
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -69,8 +69,7 @@ pub async fn feature_post(
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::FeaturePost(post, local_user_view.person.clone(), data.featured),
|
SendActivityData::FeaturePost(post, local_user_view.person.clone(), data.featured),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,7 @@ use lemmy_api_common::{
|
||||||
request::fetch_link_metadata,
|
request::fetch_link_metadata,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
error::{LemmyErrorExt, LemmyResult},
|
|
||||||
LemmyErrorType,
|
|
||||||
};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
|
|
@ -8,19 +8,19 @@ use lemmy_api_common::{
|
||||||
utils::{
|
utils::{
|
||||||
check_bot_account,
|
check_bot_account,
|
||||||
check_community_user_action,
|
check_community_user_action,
|
||||||
check_downvotes_enabled,
|
check_local_vote_mode,
|
||||||
mark_post_as_read,
|
mark_post_as_read,
|
||||||
|
VoteItem,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
post::{Post, PostLike, PostLikeForm},
|
post::{PostLike, PostLikeForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Likeable},
|
traits::Likeable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::{LocalUserView, PostView};
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
@ -31,20 +31,24 @@ pub async fn like_post(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<PostResponse>> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
let post_id = data.post_id;
|
||||||
|
|
||||||
// Don't do a downvote if site has downvotes disabled
|
check_local_vote_mode(
|
||||||
check_downvotes_enabled(data.score, &local_site)?;
|
data.score,
|
||||||
|
VoteItem::Post(post_id),
|
||||||
|
&local_site,
|
||||||
|
local_user_view.person.id,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
check_bot_account(&local_user_view.person)?;
|
check_bot_account(&local_user_view.person)?;
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post_id = data.post_id;
|
let post = PostView::read(&mut context.pool(), post_id, None, false).await?;
|
||||||
let post = Post::read(&mut context.pool(), post_id)
|
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
post.community_id,
|
&post.community,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -70,20 +74,15 @@ pub async fn like_post(
|
||||||
|
|
||||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
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(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::LikePostOrComment {
|
SendActivityData::LikePostOrComment {
|
||||||
object_id: post.ap_id,
|
object_id: post.post.ap_id,
|
||||||
actor: local_user_view.person.clone(),
|
actor: local_user_view.person.clone(),
|
||||||
community,
|
community: post.community.clone(),
|
||||||
score: data.score,
|
score: data.score,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
build_post_response(context.deref(), post.community_id, local_user_view, post_id).await
|
build_post_response(context.deref(), post.community.id, local_user_view, post_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::post::Post, traits::Crud};
|
use lemmy_db_schema::{source::post::Post, traits::Crud};
|
||||||
use lemmy_db_views::structs::{LocalUserView, VoteView};
|
use lemmy_db_views::structs::{LocalUserView, VoteView};
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists likes for a post
|
/// Lists likes for a post
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -15,9 +15,7 @@ pub async fn list_post_likes(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<ListPostLikesResponse>> {
|
) -> LemmyResult<Json<ListPostLikesResponse>> {
|
||||||
let post = Post::read(&mut context.pool(), data.post_id)
|
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
is_mod_or_admin(
|
is_mod_or_admin(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
|
|
@ -14,8 +14,8 @@ use lemmy_db_schema::{
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::{LocalUserView, PostView};
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn lock_post(
|
pub async fn lock_post(
|
||||||
|
@ -24,13 +24,11 @@ pub async fn lock_post(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<PostResponse>> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id)
|
let orig_post = PostView::read(&mut context.pool(), post_id, None, false).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_post.community_id,
|
&orig_post.community,
|
||||||
false,
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -60,8 +58,7 @@ pub async fn lock_post(
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::LockPost(post, local_user_view.person.clone(), data.locked),
|
SendActivityData::LockPost(post, local_user_view.person.clone(), data.locked),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
build_post_response(&context, orig_post.community.id, local_user_view, post_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,8 +40,7 @@ pub async fn save_post(
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||||
|
|
||||||
|
|
|
@ -35,13 +35,11 @@ pub async fn create_post_report(
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let post_view = PostView::read(&mut context.pool(), post_id, None, false)
|
let post_view = PostView::read(&mut context.pool(), post_id, None, false).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
post_view.community.id,
|
&post_view.community,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -61,9 +59,7 @@ pub async fn create_post_report(
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
||||||
|
|
||||||
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id)
|
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
|
|
||||||
|
|
||||||
// Email the admins
|
// Email the admins
|
||||||
if local_site.reports_email_admins {
|
if local_site.reports_email_admins {
|
||||||
|
@ -84,8 +80,7 @@ pub async fn create_post_report(
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(PostReportResponse { post_report_view }))
|
Ok(Json(PostReportResponse { post_report_view }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,12 @@ pub async fn resolve_post_report(
|
||||||
) -> LemmyResult<Json<PostReportResponse>> {
|
) -> LemmyResult<Json<PostReportResponse>> {
|
||||||
let report_id = data.report_id;
|
let report_id = data.report_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let report = PostReportView::read(&mut context.pool(), report_id, person_id)
|
let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
report.community.id,
|
&report.community,
|
||||||
true,
|
true,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -40,9 +38,7 @@ pub async fn resolve_post_report(
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
|
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id)
|
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
|
|
||||||
|
|
||||||
Ok(Json(PostReportResponse { post_report_view }))
|
Ok(Json(PostReportResponse { post_report_view }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,7 @@ pub async fn mark_pm_as_read(
|
||||||
) -> LemmyResult<Json<PrivateMessageResponse>> {
|
) -> LemmyResult<Json<PrivateMessageResponse>> {
|
||||||
// Checking permissions
|
// Checking permissions
|
||||||
let private_message_id = data.private_message_id;
|
let private_message_id = data.private_message_id;
|
||||||
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id)
|
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
|
|
||||||
if local_user_view.person.id != orig_private_message.recipient_id {
|
if local_user_view.person.id != orig_private_message.recipient_id {
|
||||||
Err(LemmyErrorType::CouldntUpdatePrivateMessage)?
|
Err(LemmyErrorType::CouldntUpdatePrivateMessage)?
|
||||||
}
|
}
|
||||||
|
@ -39,9 +37,7 @@ pub async fn mark_pm_as_read(
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
|
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
|
||||||
|
|
||||||
let view = PrivateMessageView::read(&mut context.pool(), private_message_id)
|
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
|
|
||||||
Ok(Json(PrivateMessageResponse {
|
Ok(Json(PrivateMessageResponse {
|
||||||
private_message_view: view,
|
private_message_view: view,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -29,9 +29,7 @@ pub async fn create_pm_report(
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let private_message_id = data.private_message_id;
|
let private_message_id = data.private_message_id;
|
||||||
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id)
|
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
|
|
||||||
|
|
||||||
// Make sure that only the recipient of the private message can create a report
|
// Make sure that only the recipient of the private message can create a report
|
||||||
if person_id != private_message.recipient_id {
|
if person_id != private_message.recipient_id {
|
||||||
|
@ -49,9 +47,8 @@ pub async fn create_pm_report(
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
|
||||||
|
|
||||||
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report.id)
|
let private_message_report_view =
|
||||||
.await?
|
PrivateMessageReportView::read(&mut context.pool(), report.id).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
|
|
||||||
|
|
||||||
// Email the admins
|
// Email the admins
|
||||||
if local_site.reports_email_admins {
|
if local_site.reports_email_admins {
|
||||||
|
|
|
@ -28,9 +28,8 @@ pub async fn resolve_pm_report(
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
|
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report_id)
|
let private_message_report_view =
|
||||||
.await?
|
PrivateMessageReportView::read(&mut context.pool(), report_id).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
|
|
||||||
|
|
||||||
Ok(Json(PrivateMessageReportResponse {
|
Ok(Json(PrivateMessageReportResponse {
|
||||||
private_message_report_view,
|
private_message_report_view,
|
||||||
|
|
|
@ -5,15 +5,13 @@ use lemmy_api_common::{
|
||||||
utils::build_federated_instances,
|
utils::build_federated_instances,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::SiteView;
|
use lemmy_db_views::structs::SiteView;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn get_federated_instances(
|
pub async fn get_federated_instances(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<Json<GetFederatedInstancesResponse>> {
|
) -> LemmyResult<Json<GetFederatedInstancesResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
let federated_instances =
|
let federated_instances =
|
||||||
build_federated_instances(&site_view.local_site, &mut context.pool()).await?;
|
build_federated_instances(&site_view.local_site, &mut context.pool()).await?;
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,12 @@ use lemmy_db_schema::{
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
local_user::{LocalUser, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
moderator::{ModAdd, ModAddForm},
|
moderator::{ModAdd, ModAddForm},
|
||||||
|
oauth_provider::OAuthProvider,
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorType, LemmyResult},
|
error::{LemmyErrorType, LemmyResult},
|
||||||
|
@ -55,17 +56,14 @@ pub async fn leave_admin(
|
||||||
ModAdd::create(&mut context.pool(), &form).await?;
|
ModAdd::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
// Reread site and admins
|
// Reread site and admins
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
let admins = PersonView::admins(&mut context.pool()).await?;
|
||||||
|
|
||||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||||
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
let oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?;
|
||||||
let custom_emojis =
|
|
||||||
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
|
||||||
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||||
|
let tagline = Tagline::get_random(&mut context.pool()).await.ok();
|
||||||
|
|
||||||
Ok(Json(GetSiteResponse {
|
Ok(Json(GetSiteResponse {
|
||||||
site_view,
|
site_view,
|
||||||
|
@ -74,8 +72,11 @@ pub async fn leave_admin(
|
||||||
my_user: None,
|
my_user: None,
|
||||||
all_languages,
|
all_languages,
|
||||||
discussion_languages,
|
discussion_languages,
|
||||||
taglines,
|
oauth_providers: Some(oauth_providers),
|
||||||
custom_emojis,
|
admin_oauth_providers: None,
|
||||||
blocked_urls,
|
blocked_urls,
|
||||||
|
tagline,
|
||||||
|
taglines: vec![],
|
||||||
|
custom_emojis: vec![],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_comment(
|
pub async fn purge_comment(
|
||||||
|
@ -35,8 +35,7 @@ pub async fn purge_comment(
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
// Also check that you're a higher admin
|
// Also check that you're a higher admin
|
||||||
LocalUser::is_higher_admin_check(
|
LocalUser::is_higher_admin_check(
|
||||||
|
@ -68,8 +67,7 @@ pub async fn purge_comment(
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_community(
|
pub async fn purge_community(
|
||||||
|
@ -31,9 +31,7 @@ pub async fn purge_community(
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
// Read the community to get its images
|
// Read the community to get its images
|
||||||
let community = Community::read(&mut context.pool(), data.community_id)
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
|
|
||||||
// Also check that you're a higher admin than all the mods
|
// Also check that you're a higher admin than all the mods
|
||||||
let community_mod_person_ids =
|
let community_mod_person_ids =
|
||||||
|
@ -77,8 +75,7 @@ pub async fn purge_community(
|
||||||
removed: true,
|
removed: true,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_person(
|
pub async fn purge_person(
|
||||||
|
@ -36,9 +36,7 @@ pub async fn purge_person(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let person = Person::read(&mut context.pool(), data.person_id)
|
let person = Person::read(&mut context.pool(), data.person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
|
||||||
|
|
||||||
ban_nonlocal_user_from_local_communities(
|
ban_nonlocal_user_from_local_communities(
|
||||||
&local_user_view,
|
&local_user_view,
|
||||||
|
@ -77,13 +75,12 @@ pub async fn purge_person(
|
||||||
moderator: local_user_view.person,
|
moderator: local_user_view.person,
|
||||||
banned_user: person,
|
banned_user: person,
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
remove_data: Some(true),
|
remove_or_restore_data: Some(true),
|
||||||
ban: true,
|
ban: true,
|
||||||
expires: None,
|
expires: None,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_post(
|
pub async fn purge_post(
|
||||||
|
@ -29,9 +29,7 @@ pub async fn purge_post(
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
// Read the post to get the community_id
|
// Read the post to get the community_id
|
||||||
let post = Post::read(&mut context.pool(), data.post_id)
|
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
// Also check that you're a higher admin
|
// Also check that you're a higher admin
|
||||||
LocalUser::is_higher_admin_check(
|
LocalUser::is_higher_admin_check(
|
||||||
|
@ -68,8 +66,7 @@ pub async fn purge_post(
|
||||||
removed: true,
|
removed: true,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,7 @@ use lemmy_db_schema::{
|
||||||
utils::{diesel_string_update, get_conn},
|
utils::{diesel_string_update, get_conn},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
error::{LemmyError, LemmyResult},
|
|
||||||
LemmyErrorType,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn approve_registration_application(
|
pub async fn approve_registration_application(
|
||||||
data: Json<ApproveRegistrationApplication>,
|
data: Json<ApproveRegistrationApplication>,
|
||||||
|
@ -61,9 +58,8 @@ pub async fn approve_registration_application(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if data.approve {
|
if data.approve {
|
||||||
let approved_local_user_view = LocalUserView::read(&mut context.pool(), approved_user_id)
|
let approved_local_user_view =
|
||||||
.await?
|
LocalUserView::read(&mut context.pool(), approved_user_id).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
|
|
||||||
if approved_local_user_view.local_user.email.is_some() {
|
if approved_local_user_view.local_user.email.is_some() {
|
||||||
// Email sending may fail, but this won't revert the application approval
|
// Email sending may fail, but this won't revert the application approval
|
||||||
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
|
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
|
||||||
|
@ -71,9 +67,8 @@ pub async fn approve_registration_application(
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read the view
|
// Read the view
|
||||||
let registration_application = RegistrationApplicationView::read(&mut context.pool(), app_id)
|
let registration_application =
|
||||||
.await?
|
RegistrationApplicationView::read(&mut context.pool(), app_id).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
|
||||||
|
|
||||||
Ok(Json(RegistrationApplicationResponse {
|
Ok(Json(RegistrationApplicationResponse {
|
||||||
registration_application,
|
registration_application,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
utils::is_admin,
|
utils::is_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists registration applications, filterable by undenied only.
|
/// Lists registration applications, filterable by undenied only.
|
||||||
pub async fn get_registration_application(
|
pub async fn get_registration_application(
|
||||||
|
@ -18,9 +18,7 @@ pub async fn get_registration_application(
|
||||||
|
|
||||||
// Read the view
|
// Read the view
|
||||||
let registration_application =
|
let registration_application =
|
||||||
RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id)
|
RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
|
||||||
|
|
||||||
Ok(Json(RegistrationApplicationResponse {
|
Ok(Json(RegistrationApplicationResponse {
|
||||||
registration_application,
|
registration_application,
|
||||||
|
|
|
@ -31,16 +31,16 @@ use lemmy_db_schema::{
|
||||||
RegistrationMode,
|
RegistrationMode,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API};
|
use lemmy_utils::{
|
||||||
|
error::{LemmyErrorType, LemmyResult},
|
||||||
|
CACHE_DURATION_API,
|
||||||
|
};
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> {
|
async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> {
|
||||||
let pool = &mut context.pool();
|
let pool = &mut context.pool();
|
||||||
|
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
.await
|
|
||||||
.expect("Create test instance");
|
|
||||||
|
|
||||||
let admin_person = Person::create(
|
let admin_person = Person::create(
|
||||||
pool,
|
pool,
|
||||||
|
@ -54,35 +54,26 @@ async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id)
|
let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id).await?;
|
||||||
.await?
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let site_form = SiteInsertForm::builder()
|
let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id);
|
||||||
.name("test site".to_string())
|
let site = Site::create(pool, &site_form).await?;
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
let site = Site::create(pool, &site_form).await.unwrap();
|
|
||||||
|
|
||||||
// Create a local site, since this is necessary for determining if email verification is
|
// Create a local site, since this is necessary for determining if email verification is
|
||||||
// required
|
// required
|
||||||
let local_site_form = LocalSiteInsertForm::builder()
|
let local_site_form = LocalSiteInsertForm {
|
||||||
.site_id(site.id)
|
require_email_verification: Some(true),
|
||||||
.require_email_verification(Some(true))
|
application_question: Some(".".to_string()),
|
||||||
.application_question(Some(".".to_string()))
|
registration_mode: Some(RegistrationMode::RequireApplication),
|
||||||
.registration_mode(Some(RegistrationMode::RequireApplication))
|
site_setup: Some(true),
|
||||||
.site_setup(Some(true))
|
..LocalSiteInsertForm::new(site.id)
|
||||||
.build();
|
};
|
||||||
let local_site = LocalSite::create(pool, &local_site_form).await.unwrap();
|
let local_site = LocalSite::create(pool, &local_site_form).await?;
|
||||||
|
|
||||||
// Required to have a working local SiteView when updating the site to change email verification
|
// Required to have a working local SiteView when updating the site to change email verification
|
||||||
// requirement or registration mode
|
// requirement or registration mode
|
||||||
let rate_limit_form = LocalSiteRateLimitInsertForm::builder()
|
let rate_limit_form = LocalSiteRateLimitInsertForm::new(local_site.id);
|
||||||
.local_site_id(local_site.id)
|
LocalSiteRateLimit::create(pool, &rate_limit_form).await?;
|
||||||
.build();
|
|
||||||
LocalSiteRateLimit::create(pool, &rate_limit_form)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok((inserted_instance, admin_local_user_view))
|
Ok((inserted_instance, admin_local_user_view))
|
||||||
}
|
}
|
||||||
|
@ -116,7 +107,6 @@ async fn signup(
|
||||||
Ok((local_user, application))
|
Ok((local_user, application))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
async fn get_application_statuses(
|
async fn get_application_statuses(
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
admin: LocalUserView,
|
admin: LocalUserView,
|
||||||
|
@ -129,14 +119,14 @@ async fn get_application_statuses(
|
||||||
get_unread_registration_application_count(context.reset_request_count(), admin.clone()).await?;
|
get_unread_registration_application_count(context.reset_request_count(), admin.clone()).await?;
|
||||||
|
|
||||||
let unread_applications = list_registration_applications(
|
let unread_applications = list_registration_applications(
|
||||||
Query::from_query("unread_only=true").unwrap(),
|
Query::from_query("unread_only=true")?,
|
||||||
context.reset_request_count(),
|
context.reset_request_count(),
|
||||||
admin.clone(),
|
admin.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let all_applications = list_registration_applications(
|
let all_applications = list_registration_applications(
|
||||||
Query::from_query("unread_only=false").unwrap(),
|
Query::from_query("unread_only=false")?,
|
||||||
context.reset_request_count(),
|
context.reset_request_count(),
|
||||||
admin,
|
admin,
|
||||||
)
|
)
|
||||||
|
@ -145,10 +135,9 @@ async fn get_application_statuses(
|
||||||
Ok((application_count, unread_applications, all_applications))
|
Ok((application_count, unread_applications, all_applications))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[tokio::test]
|
|
||||||
#[serial]
|
#[serial]
|
||||||
|
#[tokio::test]
|
||||||
|
#[expect(clippy::indexing_slicing)]
|
||||||
async fn test_application_approval() -> LemmyResult<()> {
|
async fn test_application_approval() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
let pool = &mut context.pool();
|
let pool = &mut context.pool();
|
||||||
|
|
|
@ -9,14 +9,12 @@ use lemmy_utils::error::LemmyResult;
|
||||||
use sitemap_rs::{url::Url, url_set::UrlSet};
|
use sitemap_rs::{url::Url, url_set::UrlSet};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
async fn generate_urlset(
|
fn generate_urlset(posts: Vec<(DbUrl, chrono::DateTime<chrono::Utc>)>) -> LemmyResult<UrlSet> {
|
||||||
posts: Vec<(DbUrl, chrono::DateTime<chrono::Utc>)>,
|
|
||||||
) -> LemmyResult<UrlSet> {
|
|
||||||
let urls = posts
|
let urls = posts
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map_while(|post| {
|
.map_while(|(url, date_time)| {
|
||||||
Url::builder(post.0.to_string())
|
Url::builder(url.to_string())
|
||||||
.last_modified(post.1.into())
|
.last_modified(date_time.into())
|
||||||
.build()
|
.build()
|
||||||
.ok()
|
.ok()
|
||||||
})
|
})
|
||||||
|
@ -31,7 +29,7 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
|
||||||
info!("Loaded latest {} posts", posts.len());
|
info!("Loaded latest {} posts", posts.len());
|
||||||
|
|
||||||
let mut buf = Vec::<u8>::new();
|
let mut buf = Vec::<u8>::new();
|
||||||
generate_urlset(posts).await?.write(&mut buf)?;
|
generate_urlset(posts)?.write(&mut buf)?;
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
|
@ -42,44 +40,40 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
|
|
||||||
use crate::sitemap::generate_urlset;
|
use crate::sitemap::generate_urlset;
|
||||||
use chrono::{DateTime, NaiveDate, Utc};
|
use chrono::{DateTime, NaiveDate, Utc};
|
||||||
use elementtree::Element;
|
use elementtree::Element;
|
||||||
use lemmy_db_schema::newtypes::DbUrl;
|
use lemmy_db_schema::newtypes::DbUrl;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_generate_urlset() {
|
async fn test_generate_urlset() -> LemmyResult<()> {
|
||||||
let posts: Vec<(DbUrl, DateTime<Utc>)> = vec![
|
let posts: Vec<(DbUrl, DateTime<Utc>)> = vec![
|
||||||
(
|
(
|
||||||
Url::parse("https://example.com").unwrap().into(),
|
Url::parse("https://example.com")?.into(),
|
||||||
NaiveDate::from_ymd_opt(2022, 12, 1)
|
NaiveDate::from_ymd_opt(2022, 12, 1)
|
||||||
.unwrap()
|
.unwrap_or_default()
|
||||||
.and_hms_opt(9, 10, 11)
|
.and_hms_opt(9, 10, 11)
|
||||||
.unwrap()
|
.unwrap_or_default()
|
||||||
.and_utc(),
|
.and_utc(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Url::parse("https://lemmy.ml").unwrap().into(),
|
Url::parse("https://lemmy.ml")?.into(),
|
||||||
NaiveDate::from_ymd_opt(2023, 1, 1)
|
NaiveDate::from_ymd_opt(2023, 1, 1)
|
||||||
.unwrap()
|
.unwrap_or_default()
|
||||||
.and_hms_opt(1, 2, 3)
|
.and_hms_opt(1, 2, 3)
|
||||||
.unwrap()
|
.unwrap_or_default()
|
||||||
.and_utc(),
|
.and_utc(),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut buf = Vec::<u8>::new();
|
let mut buf = Vec::<u8>::new();
|
||||||
generate_urlset(posts)
|
generate_urlset(posts)?.write(&mut buf)?;
|
||||||
.await
|
let root = Element::from_reader(buf.as_slice())?;
|
||||||
.unwrap()
|
|
||||||
.write(&mut buf)
|
|
||||||
.unwrap();
|
|
||||||
let root = Element::from_reader(buf.as_slice()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(root.tag().name(), "urlset");
|
assert_eq!(root.tag().name(), "urlset");
|
||||||
assert_eq!(root.child_count(), 2);
|
assert_eq!(root.child_count(), 2);
|
||||||
|
@ -99,45 +93,43 @@ pub(crate) mod tests {
|
||||||
root
|
root
|
||||||
.children()
|
.children()
|
||||||
.next()
|
.next()
|
||||||
.unwrap()
|
.and_then(|n| n.children().find(|element| element.tag().name() == "loc"))
|
||||||
.children()
|
.map(Element::text)
|
||||||
.find(|element| element.tag().name() == "loc")
|
.unwrap_or_default(),
|
||||||
.unwrap()
|
|
||||||
.text(),
|
|
||||||
"https://example.com/"
|
"https://example.com/"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
root
|
root
|
||||||
.children()
|
.children()
|
||||||
.next()
|
.next()
|
||||||
.unwrap()
|
.and_then(|n| n
|
||||||
.children()
|
.children()
|
||||||
.find(|element| element.tag().name() == "lastmod")
|
.find(|element| element.tag().name() == "lastmod"))
|
||||||
.unwrap()
|
.map(Element::text)
|
||||||
.text(),
|
.unwrap_or_default(),
|
||||||
"2022-12-01T09:10:11+00:00"
|
"2022-12-01T09:10:11+00:00"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
root
|
root
|
||||||
.children()
|
.children()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.unwrap()
|
.and_then(|n| n.children().find(|element| element.tag().name() == "loc"))
|
||||||
.children()
|
.map(Element::text)
|
||||||
.find(|element| element.tag().name() == "loc")
|
.unwrap_or_default(),
|
||||||
.unwrap()
|
|
||||||
.text(),
|
|
||||||
"https://lemmy.ml/"
|
"https://lemmy.ml/"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
root
|
root
|
||||||
.children()
|
.children()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.unwrap()
|
.and_then(|n| n
|
||||||
.children()
|
.children()
|
||||||
.find(|element| element.tag().name() == "lastmod")
|
.find(|element| element.tag().name() == "lastmod"))
|
||||||
.unwrap()
|
.map(Element::text)
|
||||||
.text(),
|
.unwrap_or_default(),
|
||||||
"2023-01-01T01:02:03+00:00"
|
"2023-01-01T01:02:03+00:00"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ jsonwebtoken = { version = "9.3.0", optional = true }
|
||||||
# necessary for wasmt compilation
|
# necessary for wasmt compilation
|
||||||
getrandom = { version = "0.2.15", features = ["js"] }
|
getrandom = { version = "0.2.15", features = ["js"] }
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-shear]
|
||||||
ignored = ["getrandom"]
|
ignored = ["getrandom"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -27,7 +27,6 @@ use lemmy_db_views_actor::structs::CommunityView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::LemmyResult,
|
error::LemmyResult,
|
||||||
utils::{markdown::markdown_to_html, mention::MentionData},
|
utils::{markdown::markdown_to_html, mention::MentionData},
|
||||||
LemmyErrorType,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn build_comment_response(
|
pub async fn build_comment_response(
|
||||||
|
@ -37,9 +36,8 @@ pub async fn build_comment_response(
|
||||||
recipient_ids: Vec<LocalUserId>,
|
recipient_ids: Vec<LocalUserId>,
|
||||||
) -> LemmyResult<CommentResponse> {
|
) -> LemmyResult<CommentResponse> {
|
||||||
let local_user = local_user_view.map(|l| l.local_user);
|
let local_user = local_user_view.map(|l| l.local_user);
|
||||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, local_user.as_ref())
|
let comment_view =
|
||||||
.await?
|
CommentView::read(&mut context.pool(), comment_id, local_user.as_ref()).await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
Ok(CommentResponse {
|
Ok(CommentResponse {
|
||||||
comment_view,
|
comment_view,
|
||||||
recipient_ids,
|
recipient_ids,
|
||||||
|
@ -61,8 +59,7 @@ pub async fn build_community_response(
|
||||||
Some(&local_user),
|
Some(&local_user),
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
||||||
|
|
||||||
Ok(Json(CommunityResponse {
|
Ok(Json(CommunityResponse {
|
||||||
|
@ -87,8 +84,7 @@ pub async fn build_post_response(
|
||||||
Some(&local_user),
|
Some(&local_user),
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
Ok(Json(PostResponse { post_view }))
|
Ok(Json(PostResponse { post_view }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,8 +108,7 @@ pub async fn send_local_notifs(
|
||||||
comment_id,
|
comment_id,
|
||||||
local_user_view.map(|view| &view.local_user),
|
local_user_view.map(|view| &view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
let comment = comment_view.comment;
|
let comment = comment_view.comment;
|
||||||
let post = comment_view.post;
|
let post = comment_view.post;
|
||||||
let community = comment_view.community;
|
let community = comment_view.community;
|
||||||
|
@ -125,7 +120,7 @@ pub async fn send_local_notifs(
|
||||||
{
|
{
|
||||||
let mention_name = mention.name.clone();
|
let mention_name = mention.name.clone();
|
||||||
let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await;
|
let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await;
|
||||||
if let Ok(Some(mention_user_view)) = user_view {
|
if let Ok(mention_user_view) = user_view {
|
||||||
// TODO
|
// TODO
|
||||||
// At some point, make it so you can't tag the parent creator either
|
// 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
|
// Potential duplication of notifications, one for reply and the other for mention, is handled
|
||||||
|
@ -161,9 +156,7 @@ pub async fn send_local_notifs(
|
||||||
|
|
||||||
// Send comment_reply to the parent commenter / poster
|
// Send comment_reply to the parent commenter / poster
|
||||||
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||||
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id)
|
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
// Get the parent commenter local_user
|
// Get the parent commenter local_user
|
||||||
let parent_creator_id = parent_comment.creator_id;
|
let parent_creator_id = parent_comment.creator_id;
|
||||||
|
@ -182,7 +175,7 @@ pub async fn send_local_notifs(
|
||||||
// Don't send a notif to yourself
|
// Don't send a notif to yourself
|
||||||
if parent_comment.creator_id != person.id && !check_blocks {
|
if parent_comment.creator_id != person.id && !check_blocks {
|
||||||
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
|
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
|
||||||
if let Ok(Some(parent_user_view)) = user_view {
|
if let Ok(parent_user_view) = user_view {
|
||||||
// Don't duplicate notif if already mentioned by checking recipient ids
|
// Don't duplicate notif if already mentioned by checking recipient ids
|
||||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
recipient_ids.push(parent_user_view.local_user.id);
|
||||||
|
@ -229,7 +222,7 @@ pub async fn send_local_notifs(
|
||||||
if post.creator_id != person.id && !check_blocks {
|
if post.creator_id != person.id && !check_blocks {
|
||||||
let creator_id = post.creator_id;
|
let creator_id = post.creator_id;
|
||||||
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
|
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
|
||||||
if let Ok(Some(parent_user_view)) = parent_user {
|
if let Ok(parent_user_view) = parent_user {
|
||||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||||
recipient_ids.push(parent_user_view.local_user.id);
|
recipient_ids.push(parent_user_view.local_user.id);
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,8 @@ impl Claims {
|
||||||
let claims =
|
let claims =
|
||||||
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
||||||
let user_id = LocalUserId(claims.claims.sub.parse()?);
|
let user_id = LocalUserId(claims.claims.sub.parse()?);
|
||||||
let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
|
LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
|
||||||
if !is_valid {
|
Ok(user_id)
|
||||||
Err(LemmyErrorType::NotLoggedIn)?
|
|
||||||
} else {
|
|
||||||
Ok(user_id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate(
|
pub async fn generate(
|
||||||
|
@ -73,8 +69,6 @@ impl Claims {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::{claims::Claims, context::LemmyContext};
|
use crate::{claims::Claims, context::LemmyContext};
|
||||||
|
@ -89,7 +83,7 @@ mod tests {
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::build_db_pool_for_tests,
|
utils::build_db_pool_for_tests,
|
||||||
};
|
};
|
||||||
use lemmy_utils::rate_limit::RateLimitCell;
|
use lemmy_utils::{error::LemmyResult, rate_limit::RateLimitCell};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest_middleware::ClientBuilder;
|
use reqwest_middleware::ClientBuilder;
|
||||||
|
@ -97,10 +91,10 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_should_not_validate_user_token_after_password_change() {
|
async fn test_should_not_validate_user_token_after_password_change() -> LemmyResult<()> {
|
||||||
let pool_ = build_db_pool_for_tests().await;
|
let pool_ = build_db_pool_for_tests();
|
||||||
let pool = &mut (&pool_).into();
|
let pool = &mut (&pool_).into();
|
||||||
let secret = Secret::init(pool).await.unwrap().unwrap();
|
let secret = Secret::init(pool).await?;
|
||||||
let context = LemmyContext::create(
|
let context = LemmyContext::create(
|
||||||
pool_.clone(),
|
pool_.clone(),
|
||||||
ClientBuilder::new(Client::default()).build(),
|
ClientBuilder::new(Client::default()).build(),
|
||||||
|
@ -108,29 +102,25 @@ mod tests {
|
||||||
RateLimitCell::with_test_config(),
|
RateLimitCell::with_test_config(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await?;
|
||||||
|
|
||||||
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
|
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
|
||||||
|
|
||||||
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![]).await?;
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = TestRequest::default().to_http_request();
|
let req = TestRequest::default().to_http_request();
|
||||||
let jwt = Claims::generate(inserted_local_user.id, req, &context)
|
let jwt = Claims::generate(inserted_local_user.id, req, &context).await?;
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let valid = Claims::validate(&jwt, &context).await;
|
let valid = Claims::validate(&jwt, &context).await;
|
||||||
assert!(valid.is_ok());
|
assert!(valid.is_ok());
|
||||||
|
|
||||||
let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
|
let num_deleted = Person::delete(pool, inserted_person.id).await?;
|
||||||
assert_eq!(1, num_deleted);
|
assert_eq!(1, num_deleted);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@ use ts_rs::TS;
|
||||||
pub struct CreateComment {
|
pub struct CreateComment {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub parent_id: Option<CommentId>,
|
pub parent_id: Option<CommentId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +39,9 @@ pub struct GetComment {
|
||||||
/// Edit a comment.
|
/// Edit a comment.
|
||||||
pub struct EditComment {
|
pub struct EditComment {
|
||||||
pub comment_id: CommentId,
|
pub comment_id: CommentId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +73,7 @@ pub struct DeleteComment {
|
||||||
pub struct RemoveComment {
|
pub struct RemoveComment {
|
||||||
pub comment_id: CommentId,
|
pub comment_id: CommentId,
|
||||||
pub removed: bool,
|
pub removed: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,17 +112,29 @@ pub struct CreateCommentLike {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get a list of comments.
|
/// Get a list of comments.
|
||||||
pub struct GetComments {
|
pub struct GetComments {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub type_: Option<ListingType>,
|
pub type_: Option<ListingType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub sort: Option<CommentSortType>,
|
pub sort: Option<CommentSortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub max_depth: Option<i32>,
|
pub max_depth: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_name: Option<String>,
|
pub community_name: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub post_id: Option<PostId>,
|
pub post_id: Option<PostId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub parent_id: Option<CommentId>,
|
pub parent_id: Option<CommentId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub saved_only: Option<bool>,
|
pub saved_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub liked_only: Option<bool>,
|
pub liked_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub disliked_only: Option<bool>,
|
pub disliked_only: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,12 +178,17 @@ pub struct ResolveCommentReport {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// List comment reports.
|
/// List comment reports.
|
||||||
pub struct ListCommentReports {
|
pub struct ListCommentReports {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub comment_id: Option<CommentId>,
|
pub comment_id: Option<CommentId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
/// Only shows the unresolved reports
|
/// Only shows the unresolved reports
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub unresolved_only: Option<bool>,
|
pub unresolved_only: Option<bool>,
|
||||||
/// if no community is given, it returns reports for all communities moderated by the auth user
|
/// if no community is given, it returns reports for all communities moderated by the auth user
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +207,9 @@ pub struct ListCommentReportsResponse {
|
||||||
/// List comment likes. Admins-only.
|
/// List comment likes. Admins-only.
|
||||||
pub struct ListCommentLikes {
|
pub struct ListCommentLikes {
|
||||||
pub comment_id: CommentId,
|
pub comment_id: CommentId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,14 @@ use lemmy_db_schema::{
|
||||||
source::site::Site,
|
source::site::Site,
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView};
|
use lemmy_db_views_actor::structs::{
|
||||||
|
CommunityModeratorView,
|
||||||
|
CommunitySortType,
|
||||||
|
CommunityView,
|
||||||
|
PendingFollow,
|
||||||
|
PersonView,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
@ -15,10 +20,13 @@ use ts_rs::TS;
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
// TODO make this into a tagged enum
|
||||||
/// Get a community. Must provide either an id, or a name.
|
/// Get a community. Must provide either an id, or a name.
|
||||||
pub struct GetCommunity {
|
pub struct GetCommunity {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub id: Option<CommunityId>,
|
pub id: Option<CommunityId>,
|
||||||
/// Example: star_trek , or star_trek@xyz.tld
|
/// Example: star_trek , or star_trek@xyz.tld
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +37,7 @@ pub struct GetCommunity {
|
||||||
/// The community response.
|
/// The community response.
|
||||||
pub struct GetCommunityResponse {
|
pub struct GetCommunityResponse {
|
||||||
pub community_view: CommunityView,
|
pub community_view: CommunityView,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub site: Option<Site>,
|
pub site: Option<Site>,
|
||||||
pub moderators: Vec<CommunityModeratorView>,
|
pub moderators: Vec<CommunityModeratorView>,
|
||||||
pub discussion_languages: Vec<LanguageId>,
|
pub discussion_languages: Vec<LanguageId>,
|
||||||
|
@ -44,17 +53,27 @@ pub struct CreateCommunity {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// A longer title.
|
/// A longer title.
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// A longer sidebar, or description of your community, in markdown.
|
/// A sidebar for the community in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub sidebar: Option<String>,
|
||||||
|
/// A shorter, one line description of your community.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
/// An icon URL.
|
/// An icon URL.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
/// A banner URL.
|
/// A banner URL.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
/// Whether its an NSFW community.
|
/// Whether its an NSFW community.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
/// Whether to restrict posting only to moderators.
|
/// Whether to restrict posting only to moderators.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub posting_restricted_to_mods: Option<bool>,
|
pub posting_restricted_to_mods: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub discussion_languages: Option<Vec<LanguageId>>,
|
pub discussion_languages: Option<Vec<LanguageId>>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub visibility: Option<CommunityVisibility>,
|
pub visibility: Option<CommunityVisibility>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +92,15 @@ pub struct CommunityResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Fetches a list of communities.
|
/// Fetches a list of communities.
|
||||||
pub struct ListCommunities {
|
pub struct ListCommunities {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub type_: Option<ListingType>,
|
pub type_: Option<ListingType>,
|
||||||
pub sort: Option<SortType>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub sort: Option<CommunitySortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,11 +121,16 @@ pub struct BanFromCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
pub ban: bool,
|
pub ban: bool,
|
||||||
pub remove_data: Option<bool>,
|
/// Optionally remove or restore all their data. Useful for new troll accounts.
|
||||||
|
/// If ban is true, then this means remove. If ban is false, it means restore.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub remove_or_restore_data: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
/// A time that the ban will expire, in unix epoch seconds.
|
/// A time that the ban will expire, in unix epoch seconds.
|
||||||
///
|
///
|
||||||
/// An i64 unix timestamp is used for a simpler API client implementation.
|
/// An i64 unix timestamp is used for a simpler API client implementation.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub expires: Option<i64>,
|
pub expires: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,18 +169,29 @@ pub struct AddModToCommunityResponse {
|
||||||
pub struct EditCommunity {
|
pub struct EditCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
/// A longer title.
|
/// A longer title.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
/// A longer sidebar, or description of your community, in markdown.
|
/// A sidebar for the community in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub sidebar: Option<String>,
|
||||||
|
/// A shorter, one line description of your community.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
/// An icon URL.
|
/// An icon URL.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
/// A banner URL.
|
/// A banner URL.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
/// Whether its an NSFW community.
|
/// Whether its an NSFW community.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
/// Whether to restrict posting only to moderators.
|
/// Whether to restrict posting only to moderators.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub posting_restricted_to_mods: Option<bool>,
|
pub posting_restricted_to_mods: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub discussion_languages: Option<Vec<LanguageId>>,
|
pub discussion_languages: Option<Vec<LanguageId>>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub visibility: Option<CommunityVisibility>,
|
pub visibility: Option<CommunityVisibility>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +203,7 @@ pub struct EditCommunity {
|
||||||
pub struct HideCommunity {
|
pub struct HideCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +225,7 @@ pub struct DeleteCommunity {
|
||||||
pub struct RemoveCommunity {
|
pub struct RemoveCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub removed: bool,
|
pub removed: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,3 +265,60 @@ pub struct TransferCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Fetches a random community
|
||||||
|
pub struct GetRandomCommunity {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub type_: Option<ListingType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct ListCommunityPendingFollows {
|
||||||
|
/// Only shows the unapproved applications
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub pending_only: Option<bool>,
|
||||||
|
// Only for admins, show pending follows for communities which you dont moderate
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub all_communities: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct GetCommunityPendingFollowsCount {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct GetCommunityPendingFollowsCountResponse {
|
||||||
|
pub count: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct ListCommunityPendingFollowsResponse {
|
||||||
|
pub items: Vec<PendingFollow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct ApproveCommunityPendingFollower {
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub follower_id: PersonId,
|
||||||
|
pub approve: bool,
|
||||||
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl LemmyContext {
|
||||||
/// Do not use this in production code.
|
/// Do not use this in production code.
|
||||||
pub async fn init_test_federation_config() -> FederationConfig<LemmyContext> {
|
pub async fn init_test_federation_config() -> FederationConfig<LemmyContext> {
|
||||||
// call this to run migrations
|
// call this to run migrations
|
||||||
let pool = build_db_pool_for_tests().await;
|
let pool = build_db_pool_for_tests();
|
||||||
|
|
||||||
let client = client_builder(&SETTINGS).build().expect("build client");
|
let client = client_builder(&SETTINGS).build().expect("build client");
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use lemmy_db_schema::newtypes::CustomEmojiId;
|
use lemmy_db_schema::newtypes::CustomEmojiId;
|
||||||
use lemmy_db_views::structs::CustomEmojiView;
|
use lemmy_db_views::structs::CustomEmojiView;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -46,3 +47,27 @@ pub struct DeleteCustomEmoji {
|
||||||
pub struct CustomEmojiResponse {
|
pub struct CustomEmojiResponse {
|
||||||
pub custom_emoji: CustomEmojiView,
|
pub custom_emoji: CustomEmojiView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// A response for custom emojis.
|
||||||
|
pub struct ListCustomEmojisResponse {
|
||||||
|
pub custom_emojis: Vec<CustomEmojiView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Fetches a list of custom emojis.
|
||||||
|
pub struct ListCustomEmojis {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub category: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub ignore_page_limits: Option<bool>,
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub mod community;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
|
pub mod oauth_provider;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
@ -15,6 +16,7 @@ pub mod request;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod send_activity;
|
pub mod send_activity;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
pub mod tagline;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
@ -24,7 +26,7 @@ pub extern crate lemmy_db_views_actor;
|
||||||
pub extern crate lemmy_db_views_moderator;
|
pub extern crate lemmy_db_views_moderator;
|
||||||
pub extern crate lemmy_utils;
|
pub extern crate lemmy_utils;
|
||||||
|
|
||||||
pub use lemmy_utils::LemmyErrorType;
|
pub use lemmy_utils::error::LemmyErrorType;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{cmp::min, time::Duration};
|
use std::{cmp::min, time::Duration};
|
||||||
|
|
||||||
|
|
85
crates/api_common/src/oauth_provider.rs
Normal file
85
crates/api_common/src/oauth_provider.rs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
use lemmy_db_schema::newtypes::OAuthProviderId;
|
||||||
|
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)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Create an external auth method.
|
||||||
|
pub struct CreateOAuthProvider {
|
||||||
|
pub display_name: String,
|
||||||
|
pub issuer: String,
|
||||||
|
pub authorization_endpoint: String,
|
||||||
|
pub token_endpoint: String,
|
||||||
|
pub userinfo_endpoint: String,
|
||||||
|
pub id_claim: String,
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
pub scopes: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub auto_verify_email: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Edit an external auth method.
|
||||||
|
pub struct EditOAuthProvider {
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub authorization_endpoint: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub token_endpoint: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub userinfo_endpoint: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub id_claim: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub client_secret: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub scopes: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub auto_verify_email: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Delete an external auth method.
|
||||||
|
pub struct DeleteOAuthProvider {
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Logging in with an OAuth 2.0 authorization
|
||||||
|
pub struct AuthenticateWithOauth {
|
||||||
|
pub code: String,
|
||||||
|
pub oauth_provider_id: OAuthProviderId,
|
||||||
|
pub redirect_uri: Url,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub show_nsfw: Option<bool>,
|
||||||
|
/// Username is mandatory at registration time
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub username: Option<String>,
|
||||||
|
/// An answer is mandatory if require application is enabled on the server
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub answer: Option<String>,
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||||
sensitive::SensitiveString,
|
sensitive::SensitiveString,
|
||||||
source::site::Site,
|
source::{login_token::LoginToken, site::Site},
|
||||||
CommentSortType,
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
PostListingMode,
|
PostListingMode,
|
||||||
SortType,
|
PostSortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalImageView, PostView};
|
use lemmy_db_views::structs::{CommentView, LocalImageView, PostView};
|
||||||
use lemmy_db_views_actor::structs::{
|
use lemmy_db_views_actor::structs::{
|
||||||
|
@ -28,6 +28,7 @@ pub struct Login {
|
||||||
pub username_or_email: SensitiveString,
|
pub username_or_email: SensitiveString,
|
||||||
pub password: SensitiveString,
|
pub password: SensitiveString,
|
||||||
/// May be required, if totp is enabled for their account.
|
/// May be required, if totp is enabled for their account.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub totp_2fa_token: Option<String>,
|
pub totp_2fa_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,16 +41,22 @@ pub struct Register {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: SensitiveString,
|
pub password: SensitiveString,
|
||||||
pub password_verify: SensitiveString,
|
pub password_verify: SensitiveString,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
/// email is mandatory if email verification is enabled on the server
|
/// email is mandatory if email verification is enabled on the server
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub email: Option<SensitiveString>,
|
pub email: Option<SensitiveString>,
|
||||||
/// The UUID of the captcha item.
|
/// The UUID of the captcha item.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub captcha_uuid: Option<String>,
|
pub captcha_uuid: Option<String>,
|
||||||
/// Your captcha answer.
|
/// Your captcha answer.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub captcha_answer: Option<String>,
|
pub captcha_answer: Option<String>,
|
||||||
/// A form field to trick signup bots. Should be None.
|
/// A form field to trick signup bots. Should be None.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub honeypot: Option<String>,
|
pub honeypot: Option<String>,
|
||||||
/// An answer is mandatory if require application is enabled on the server
|
/// An answer is mandatory if require application is enabled on the server
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub answer: Option<String>,
|
pub answer: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +67,7 @@ pub struct Register {
|
||||||
/// A wrapper for the captcha response.
|
/// A wrapper for the captcha response.
|
||||||
pub struct GetCaptchaResponse {
|
pub struct GetCaptchaResponse {
|
||||||
/// Will be None if captchas are disabled.
|
/// Will be None if captchas are disabled.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub ok: Option<CaptchaResponse>,
|
pub ok: Option<CaptchaResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,56 +91,92 @@ pub struct CaptchaResponse {
|
||||||
/// Saves settings for your user.
|
/// Saves settings for your user.
|
||||||
pub struct SaveUserSettings {
|
pub struct SaveUserSettings {
|
||||||
/// Show nsfw posts.
|
/// Show nsfw posts.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
|
/// Blur nsfw posts.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub blur_nsfw: Option<bool>,
|
pub blur_nsfw: Option<bool>,
|
||||||
pub auto_expand: Option<bool>,
|
|
||||||
/// Your user's theme.
|
/// Your user's theme.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
pub default_sort_type: Option<SortType>,
|
/// The default post listing type, usually "local"
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub default_listing_type: Option<ListingType>,
|
pub default_listing_type: Option<ListingType>,
|
||||||
|
/// A post-view mode that changes how multiple post listings look.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub post_listing_mode: Option<PostListingMode>,
|
||||||
|
/// The default post sort, usually "active"
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub default_post_sort_type: Option<PostSortType>,
|
||||||
|
/// The default comment sort, usually "hot"
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub default_comment_sort_type: Option<CommentSortType>,
|
||||||
/// The language of the lemmy interface
|
/// The language of the lemmy interface
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub interface_language: Option<String>,
|
pub interface_language: Option<String>,
|
||||||
/// A URL for your avatar.
|
/// A URL for your avatar.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
/// A URL for your banner.
|
/// A URL for your banner.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
/// Your display name, which can contain strange characters, and does not need to be unique.
|
/// Your display name, which can contain strange characters, and does not need to be unique.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
/// Your email.
|
/// Your email.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub email: Option<SensitiveString>,
|
pub email: Option<SensitiveString>,
|
||||||
/// Your bio / info, in markdown.
|
/// Your bio / info, in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
/// Your matrix user id. Ex: @my_user:matrix.org
|
/// Your matrix user id. Ex: @my_user:matrix.org
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub matrix_user_id: Option<String>,
|
pub matrix_user_id: Option<String>,
|
||||||
/// Whether to show or hide avatars.
|
/// Whether to show or hide avatars.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_avatars: Option<bool>,
|
pub show_avatars: Option<bool>,
|
||||||
/// Sends notifications to your email.
|
/// Sends notifications to your email.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub send_notifications_to_email: Option<bool>,
|
pub send_notifications_to_email: Option<bool>,
|
||||||
/// Whether this account is a bot account. Users can hide these accounts easily if they wish.
|
/// Whether this account is a bot account. Users can hide these accounts easily if they wish.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub bot_account: Option<bool>,
|
pub bot_account: Option<bool>,
|
||||||
/// Whether to show bot accounts.
|
/// Whether to show bot accounts.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_bot_accounts: Option<bool>,
|
pub show_bot_accounts: Option<bool>,
|
||||||
/// Whether to show read posts.
|
/// Whether to show read posts.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_read_posts: Option<bool>,
|
pub show_read_posts: Option<bool>,
|
||||||
/// A list of languages you are able to see discussion in.
|
/// A list of languages you are able to see discussion in.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub discussion_languages: Option<Vec<LanguageId>>,
|
pub discussion_languages: Option<Vec<LanguageId>>,
|
||||||
/// Open links in a new tab
|
/// Open links in a new tab
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub open_links_in_new_tab: Option<bool>,
|
pub open_links_in_new_tab: Option<bool>,
|
||||||
/// Enable infinite scroll
|
/// Enable infinite scroll
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub infinite_scroll_enabled: Option<bool>,
|
pub infinite_scroll_enabled: Option<bool>,
|
||||||
/// A post-view mode that changes how multiple post listings look.
|
|
||||||
pub post_listing_mode: Option<PostListingMode>,
|
|
||||||
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
|
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enable_keyboard_navigation: Option<bool>,
|
pub enable_keyboard_navigation: Option<bool>,
|
||||||
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or
|
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or
|
||||||
/// should be paused
|
/// should be paused
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enable_animated_images: Option<bool>,
|
pub enable_animated_images: Option<bool>,
|
||||||
|
/// Whether a user can send / receive private messages
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub enable_private_messages: Option<bool>,
|
||||||
/// Whether to auto-collapse bot comments.
|
/// Whether to auto-collapse bot comments.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub collapse_bot_comments: Option<bool>,
|
pub collapse_bot_comments: Option<bool>,
|
||||||
/// Some vote display mode settings
|
/// Some vote display mode settings
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_scores: Option<bool>,
|
pub show_scores: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_upvotes: Option<bool>,
|
pub show_upvotes: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_downvotes: Option<bool>,
|
pub show_downvotes: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_upvote_percentage: Option<bool>,
|
pub show_upvote_percentage: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +198,7 @@ pub struct ChangePassword {
|
||||||
pub struct LoginResponse {
|
pub struct LoginResponse {
|
||||||
/// This is None in response to `Register` if email verification is enabled, or the server
|
/// This is None in response to `Register` if email verification is enabled, or the server
|
||||||
/// requires registration applications.
|
/// requires registration applications.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub jwt: Option<SensitiveString>,
|
pub jwt: Option<SensitiveString>,
|
||||||
/// If registration applications are required, this will return true for a signup response.
|
/// If registration applications are required, this will return true for a signup response.
|
||||||
pub registration_created: bool,
|
pub registration_created: bool,
|
||||||
|
@ -169,13 +214,20 @@ pub struct LoginResponse {
|
||||||
///
|
///
|
||||||
/// Either person_id, or username are required.
|
/// Either person_id, or username are required.
|
||||||
pub struct GetPersonDetails {
|
pub struct GetPersonDetails {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub person_id: Option<PersonId>,
|
pub person_id: Option<PersonId>,
|
||||||
/// Example: dessalines , or dessalines@xyz.tld
|
/// Example: dessalines , or dessalines@xyz.tld
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
pub sort: Option<SortType>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub sort: Option<PostSortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub saved_only: Option<bool>,
|
pub saved_only: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +238,7 @@ pub struct GetPersonDetails {
|
||||||
/// A person's details response.
|
/// A person's details response.
|
||||||
pub struct GetPersonDetailsResponse {
|
pub struct GetPersonDetailsResponse {
|
||||||
pub person_view: PersonView,
|
pub person_view: PersonView,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub site: Option<Site>,
|
pub site: Option<Site>,
|
||||||
pub comments: Vec<CommentView>,
|
pub comments: Vec<CommentView>,
|
||||||
pub posts: Vec<PostView>,
|
pub posts: Vec<PostView>,
|
||||||
|
@ -217,12 +270,16 @@ pub struct AddAdminResponse {
|
||||||
pub struct BanPerson {
|
pub struct BanPerson {
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
pub ban: bool,
|
pub ban: bool,
|
||||||
/// Optionally remove all their data. Useful for new troll accounts.
|
/// Optionally remove or restore all their data. Useful for new troll accounts.
|
||||||
pub remove_data: Option<bool>,
|
/// If ban is true, then this means remove. If ban is false, it means restore.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub remove_or_restore_data: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
/// A time that the ban will expire, in unix epoch seconds.
|
/// A time that the ban will expire, in unix epoch seconds.
|
||||||
///
|
///
|
||||||
/// An i64 unix timestamp is used for a simpler API client implementation.
|
/// An i64 unix timestamp is used for a simpler API client implementation.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub expires: Option<i64>,
|
pub expires: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,9 +325,13 @@ pub struct BlockPersonResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get comment replies.
|
/// Get comment replies.
|
||||||
pub struct GetReplies {
|
pub struct GetReplies {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub sort: Option<CommentSortType>,
|
pub sort: Option<CommentSortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub unread_only: Option<bool>,
|
pub unread_only: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,9 +350,13 @@ pub struct GetRepliesResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get mentions for your user.
|
/// Get mentions for your user.
|
||||||
pub struct GetPersonMentions {
|
pub struct GetPersonMentions {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub sort: Option<CommentSortType>,
|
pub sort: Option<CommentSortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub unread_only: Option<bool>,
|
pub unread_only: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,6 +435,7 @@ pub struct PasswordChangeAfterReset {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get a count of the number of reports.
|
/// Get a count of the number of reports.
|
||||||
pub struct GetReportCount {
|
pub struct GetReportCount {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,9 +445,11 @@ pub struct GetReportCount {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// A response for the number of reports.
|
/// A response for the number of reports.
|
||||||
pub struct GetReportCountResponse {
|
pub struct GetReportCountResponse {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
pub comment_reports: i64,
|
pub comment_reports: i64,
|
||||||
pub post_reports: i64,
|
pub post_reports: i64,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub private_message_reports: Option<i64>,
|
pub private_message_reports: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,7 +499,9 @@ pub struct UpdateTotpResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get your user's image / media uploads.
|
/// Get your user's image / media uploads.
|
||||||
pub struct ListMedia {
|
pub struct ListMedia {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,3 +511,10 @@ pub struct ListMedia {
|
||||||
pub struct ListMediaResponse {
|
pub struct ListMediaResponse {
|
||||||
pub images: Vec<LocalImageView>,
|
pub images: Vec<LocalImageView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct ListLoginsResponse {
|
||||||
|
pub logins: Vec<LoginToken>,
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
|
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
|
||||||
ListingType,
|
ListingType,
|
||||||
PostFeatureType,
|
PostFeatureType,
|
||||||
SortType,
|
PostSortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView};
|
use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView};
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||||
|
@ -19,17 +19,27 @@ use ts_rs::TS;
|
||||||
pub struct CreatePost {
|
pub struct CreatePost {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
/// An optional body for the post in markdown.
|
/// An optional body for the post in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
/// An optional alt_text, usable for image posts.
|
/// An optional alt_text, usable for image posts.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub alt_text: Option<String>,
|
pub alt_text: Option<String>,
|
||||||
/// A honeypot to catch bots. Should be None.
|
/// A honeypot to catch bots. Should be None.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub honeypot: Option<String>,
|
pub honeypot: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub custom_thumbnail: Option<String>,
|
pub custom_thumbnail: Option<String>,
|
||||||
|
/// Time when this post should be scheduled. Null means publish immediately.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub scheduled_publish_time: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
@ -43,9 +53,12 @@ pub struct PostResponse {
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
// TODO this should be made into a tagged enum
|
||||||
/// Get a post. Needs either the post id, or comment_id.
|
/// Get a post. Needs either the post id, or comment_id.
|
||||||
pub struct GetPost {
|
pub struct GetPost {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub id: Option<PostId>,
|
pub id: Option<PostId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub comment_id: Option<CommentId>,
|
pub comment_id: Option<CommentId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,21 +81,37 @@ pub struct GetPostResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get a list of posts.
|
/// Get a list of posts.
|
||||||
pub struct GetPosts {
|
pub struct GetPosts {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub type_: Option<ListingType>,
|
pub type_: Option<ListingType>,
|
||||||
pub sort: Option<SortType>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub sort: Option<PostSortType>,
|
||||||
/// DEPRECATED, use page_cursor
|
/// DEPRECATED, use page_cursor
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_name: Option<String>,
|
pub community_name: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub saved_only: Option<bool>,
|
pub saved_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub liked_only: Option<bool>,
|
pub liked_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub disliked_only: Option<bool>,
|
pub disliked_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_hidden: Option<bool>,
|
pub show_hidden: Option<bool>,
|
||||||
/// If true, then show the read posts (even if your user setting is to hide them)
|
/// If true, then show the read posts (even if your user setting is to hide them)
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_read: Option<bool>,
|
pub show_read: Option<bool>,
|
||||||
/// If true, then show the nsfw posts (even if your user setting is to hide them)
|
/// If true, then show the nsfw posts (even if your user setting is to hide them)
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
/// If true, then only show posts with no comments
|
||||||
|
pub no_comments_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page_cursor: Option<PaginationCursor>,
|
pub page_cursor: Option<PaginationCursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +123,7 @@ pub struct GetPosts {
|
||||||
pub struct GetPostsResponse {
|
pub struct GetPostsResponse {
|
||||||
pub posts: Vec<PostView>,
|
pub posts: Vec<PostView>,
|
||||||
/// the pagination cursor to use to fetch the next page
|
/// the pagination cursor to use to fetch the next page
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub next_page: Option<PaginationCursor>,
|
pub next_page: Option<PaginationCursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,16 +144,26 @@ pub struct CreatePostLike {
|
||||||
/// Edit a post.
|
/// Edit a post.
|
||||||
pub struct EditPost {
|
pub struct EditPost {
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
/// An optional body for the post in markdown.
|
/// An optional body for the post in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
/// An optional alt_text, usable for image posts.
|
/// An optional alt_text, usable for image posts.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub alt_text: Option<String>,
|
pub alt_text: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub custom_thumbnail: Option<String>,
|
pub custom_thumbnail: Option<String>,
|
||||||
|
/// Time when this post should be scheduled. Null means publish immediately.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub scheduled_publish_time: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||||
|
@ -143,6 +183,7 @@ pub struct DeletePost {
|
||||||
pub struct RemovePost {
|
pub struct RemovePost {
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
pub removed: bool,
|
pub removed: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,12 +267,18 @@ pub struct ResolvePostReport {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// List post reports.
|
/// List post reports.
|
||||||
pub struct ListPostReports {
|
pub struct ListPostReports {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
/// Only shows the unresolved reports
|
/// Only shows the unresolved reports
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub unresolved_only: Option<bool>,
|
pub unresolved_only: Option<bool>,
|
||||||
|
// TODO make into tagged enum at some point
|
||||||
/// if no community is given, it returns reports for all communities moderated by the auth user
|
/// if no community is given, it returns reports for all communities moderated by the auth user
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub post_id: Option<PostId>,
|
pub post_id: Option<PostId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,6 +314,7 @@ pub struct GetSiteMetadataResponse {
|
||||||
pub struct LinkMetadata {
|
pub struct LinkMetadata {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub opengraph_data: OpenGraphData,
|
pub opengraph_data: OpenGraphData,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,9 +324,13 @@ pub struct LinkMetadata {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Site metadata, from its opengraph tags.
|
/// Site metadata, from its opengraph tags.
|
||||||
pub struct OpenGraphData {
|
pub struct OpenGraphData {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub(crate) image: Option<DbUrl>,
|
pub(crate) image: Option<DbUrl>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub embed_video_url: Option<DbUrl>,
|
pub embed_video_url: Option<DbUrl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,7 +341,9 @@ pub struct OpenGraphData {
|
||||||
/// List post likes. Admins-only.
|
/// List post likes. Admins-only.
|
||||||
pub struct ListPostLikes {
|
pub struct ListPostLikes {
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,13 @@ pub struct MarkPrivateMessageAsRead {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get your private messages.
|
/// Get your private messages.
|
||||||
pub struct GetPrivateMessages {
|
pub struct GetPrivateMessages {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub unread_only: Option<bool>,
|
pub unread_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub creator_id: Option<PersonId>,
|
pub creator_id: Option<PersonId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,9 +106,12 @@ pub struct ResolvePrivateMessageReport {
|
||||||
/// List private message reports.
|
/// List private message reports.
|
||||||
// TODO , perhaps GetReports should be a tagged enum list too.
|
// TODO , perhaps GetReports should be a tagged enum list too.
|
||||||
pub struct ListPrivateMessageReports {
|
pub struct ListPrivateMessageReports {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
/// Only shows the unresolved reports
|
/// Only shows the unresolved reports
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub unresolved_only: Option<bool>,
|
pub unresolved_only: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
lemmy_db_schema::traits::Crud,
|
lemmy_db_schema::traits::Crud,
|
||||||
post::{LinkMetadata, OpenGraphData},
|
post::{LinkMetadata, OpenGraphData},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{local_site_opt_to_sensitive, proxy_image_link},
|
utils::proxy_image_link,
|
||||||
};
|
};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
@ -13,8 +13,8 @@ use lemmy_db_schema::{
|
||||||
newtypes::DbUrl,
|
newtypes::DbUrl,
|
||||||
source::{
|
source::{
|
||||||
images::{ImageDetailsForm, LocalImage, LocalImageForm},
|
images::{ImageDetailsForm, LocalImage, LocalImageForm},
|
||||||
local_site::LocalSite,
|
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
|
site::Site,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -44,6 +44,7 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder {
|
||||||
.user_agent(user_agent.clone())
|
.user_agent(user_agent.clone())
|
||||||
.timeout(REQWEST_TIMEOUT)
|
.timeout(REQWEST_TIMEOUT)
|
||||||
.connect_timeout(REQWEST_TIMEOUT)
|
.connect_timeout(REQWEST_TIMEOUT)
|
||||||
|
.use_rustls_tls()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches metadata for the given link and optionally generates thumbnail.
|
/// Fetches metadata for the given link and optionally generates thumbnail.
|
||||||
|
@ -130,7 +131,6 @@ pub async fn generate_post_link_metadata(
|
||||||
post: Post,
|
post: Post,
|
||||||
custom_thumbnail: Option<Url>,
|
custom_thumbnail: Option<Url>,
|
||||||
send_activity: impl FnOnce(Post) -> Option<SendActivityData> + Send + 'static,
|
send_activity: impl FnOnce(Post) -> Option<SendActivityData> + Send + 'static,
|
||||||
local_site: Option<LocalSite>,
|
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
let metadata = match &post.url {
|
let metadata = match &post.url {
|
||||||
|
@ -144,7 +144,8 @@ pub async fn generate_post_link_metadata(
|
||||||
.is_some_and(|content_type| content_type.starts_with("image"));
|
.is_some_and(|content_type| content_type.starts_with("image"));
|
||||||
|
|
||||||
// Decide if we are allowed to generate local thumbnail
|
// Decide if we are allowed to generate local thumbnail
|
||||||
let allow_sensitive = local_site_opt_to_sensitive(&local_site);
|
let site = Site::read_local(&mut context.pool()).await?;
|
||||||
|
let allow_sensitive = site.content_warning.is_some();
|
||||||
let allow_generate_thumbnail = allow_sensitive || !post.nsfw;
|
let allow_generate_thumbnail = allow_sensitive || !post.nsfw;
|
||||||
|
|
||||||
let image_url = if is_image_post {
|
let image_url = if is_image_post {
|
||||||
|
@ -174,7 +175,7 @@ pub async fn generate_post_link_metadata(
|
||||||
};
|
};
|
||||||
let updated_post = Post::update(&mut context.pool(), post.id, &form).await?;
|
let updated_post = Post::update(&mut context.pool(), post.id, &form).await?;
|
||||||
if let Some(send_activity) = send_activity(updated_post) {
|
if let Some(send_activity) = send_activity(updated_post) {
|
||||||
ActivityChannel::submit_activity(send_activity, &context).await?;
|
ActivityChannel::submit_activity(send_activity, &context)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -353,9 +354,10 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L
|
||||||
// fetch remote non-pictrs images for persistent thumbnail link
|
// fetch remote non-pictrs images for persistent thumbnail link
|
||||||
// TODO: should limit size once supported by pictrs
|
// TODO: should limit size once supported by pictrs
|
||||||
let fetch_url = format!(
|
let fetch_url = format!(
|
||||||
"{}image/download?url={}",
|
"{}image/download?url={}&resize={}",
|
||||||
pictrs_config.url,
|
pictrs_config.url,
|
||||||
encode(image_url.as_str())
|
encode(image_url.as_str()),
|
||||||
|
context.settings().pictrs_config()?.max_thumbnail_size
|
||||||
);
|
);
|
||||||
|
|
||||||
let res = context
|
let res = context
|
||||||
|
@ -470,14 +472,13 @@ pub async fn replace_image(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::{extract_opengraph_data, fetch_link_metadata},
|
request::{extract_opengraph_data, fetch_link_metadata},
|
||||||
};
|
};
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -485,10 +486,10 @@ mod tests {
|
||||||
// These helped with testing
|
// These helped with testing
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_link_metadata() {
|
async fn test_link_metadata() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ").unwrap();
|
let sample_url = Url::parse("https://gitlab.com/IzzyOnDroid/repo/-/wikis/FAQ")?;
|
||||||
let sample_res = fetch_link_metadata(&sample_url, &context).await.unwrap();
|
let sample_res = fetch_link_metadata(&sample_url, &context).await?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some("FAQ · Wiki · IzzyOnDroid / repo · GitLab".to_string()),
|
Some("FAQ · Wiki · IzzyOnDroid / repo · GitLab".to_string()),
|
||||||
sample_res.opengraph_data.title
|
sample_res.opengraph_data.title
|
||||||
|
@ -499,8 +500,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(
|
Some(
|
||||||
Url::parse("https://gitlab.com/uploads/-/system/project/avatar/4877469/iod_logo.png")
|
Url::parse("https://gitlab.com/uploads/-/system/project/avatar/4877469/iod_logo.png")?
|
||||||
.unwrap()
|
|
||||||
.into()
|
.into()
|
||||||
),
|
),
|
||||||
sample_res.opengraph_data.image
|
sample_res.opengraph_data.image
|
||||||
|
@ -510,19 +510,21 @@ mod tests {
|
||||||
Some(mime::TEXT_HTML_UTF_8.to_string()),
|
Some(mime::TEXT_HTML_UTF_8.to_string()),
|
||||||
sample_res.content_type
|
sample_res.content_type
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_resolve_image_url() {
|
fn test_resolve_image_url() -> LemmyResult<()> {
|
||||||
// url that lists the opengraph fields
|
// url that lists the opengraph fields
|
||||||
let url = Url::parse("https://example.com/one/two.html").unwrap();
|
let url = Url::parse("https://example.com/one/two.html")?;
|
||||||
|
|
||||||
// root relative url
|
// root relative url
|
||||||
let html_bytes = b"<!DOCTYPE html><html><head><meta property='og:image' content='/image.jpg'></head><body></body></html>";
|
let html_bytes = b"<!DOCTYPE html><html><head><meta property='og:image' content='/image.jpg'></head><body></body></html>";
|
||||||
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metadata.image,
|
metadata.image,
|
||||||
Some(Url::parse("https://example.com/image.jpg").unwrap().into())
|
Some(Url::parse("https://example.com/image.jpg")?.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
// base relative url
|
// base relative url
|
||||||
|
@ -530,11 +532,7 @@ mod tests {
|
||||||
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metadata.image,
|
metadata.image,
|
||||||
Some(
|
Some(Url::parse("https://example.com/one/image.jpg")?.into())
|
||||||
Url::parse("https://example.com/one/image.jpg")
|
|
||||||
.unwrap()
|
|
||||||
.into()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// absolute url
|
// absolute url
|
||||||
|
@ -542,7 +540,7 @@ mod tests {
|
||||||
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metadata.image,
|
metadata.image,
|
||||||
Some(Url::parse("https://cdn.host.com/image.jpg").unwrap().into())
|
Some(Url::parse("https://cdn.host.com/image.jpg")?.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
// protocol relative url
|
// protocol relative url
|
||||||
|
@ -550,7 +548,9 @@ mod tests {
|
||||||
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
let metadata = extract_opengraph_data(html_bytes, &url).expect("Unable to parse metadata");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
metadata.image,
|
metadata.image,
|
||||||
Some(Url::parse("https://example.com/image.jpg").unwrap().into())
|
Some(Url::parse("https://example.com/image.jpg")?.into())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,8 @@ pub enum SendActivityData {
|
||||||
score: i16,
|
score: i16,
|
||||||
},
|
},
|
||||||
FollowCommunity(Community, Person, bool),
|
FollowCommunity(Community, Person, bool),
|
||||||
|
AcceptFollower(CommunityId, PersonId),
|
||||||
|
RejectFollower(CommunityId, PersonId),
|
||||||
UpdateCommunity(Person, Community),
|
UpdateCommunity(Person, Community),
|
||||||
DeleteCommunity(Person, Community, bool),
|
DeleteCommunity(Person, Community, bool),
|
||||||
RemoveCommunity {
|
RemoveCommunity {
|
||||||
|
@ -83,7 +85,7 @@ pub enum SendActivityData {
|
||||||
moderator: Person,
|
moderator: Person,
|
||||||
banned_user: Person,
|
banned_user: Person,
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
remove_data: Option<bool>,
|
remove_or_restore_data: Option<bool>,
|
||||||
ban: bool,
|
ban: bool,
|
||||||
expires: Option<i64>,
|
expires: Option<i64>,
|
||||||
},
|
},
|
||||||
|
@ -123,10 +125,7 @@ impl ActivityChannel {
|
||||||
lock.recv().await
|
lock.recv().await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn submit_activity(
|
pub fn submit_activity(data: SendActivityData, _context: &Data<LemmyContext>) -> LemmyResult<()> {
|
||||||
data: SendActivityData,
|
|
||||||
_context: &Data<LemmyContext>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
// could do `ACTIVITY_CHANNEL.keepalive_sender.lock()` instead and get rid of weak_sender,
|
// could do `ACTIVITY_CHANNEL.keepalive_sender.lock()` instead and get rid of weak_sender,
|
||||||
// not sure which way is more efficient
|
// not sure which way is more efficient
|
||||||
if let Some(sender) = ACTIVITY_CHANNEL.weak_sender.upgrade() {
|
if let Some(sender) = ACTIVITY_CHANNEL.weak_sender.upgrade() {
|
||||||
|
|
|
@ -11,34 +11,35 @@ use lemmy_db_schema::{
|
||||||
RegistrationApplicationId,
|
RegistrationApplicationId,
|
||||||
},
|
},
|
||||||
source::{
|
source::{
|
||||||
|
community::Community,
|
||||||
federation_queue_state::FederationQueueState,
|
federation_queue_state::FederationQueueState,
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
language::Language,
|
language::Language,
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
|
oauth_provider::{OAuthProvider, PublicOAuthProvider},
|
||||||
|
person::Person,
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
|
CommentSortType,
|
||||||
|
FederationMode,
|
||||||
ListingType,
|
ListingType,
|
||||||
ModlogActionType,
|
ModlogActionType,
|
||||||
PostListingMode,
|
PostListingMode,
|
||||||
|
PostSortType,
|
||||||
RegistrationMode,
|
RegistrationMode,
|
||||||
SearchType,
|
SearchType,
|
||||||
SortType,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{
|
use lemmy_db_views::structs::{
|
||||||
CommentView,
|
CommentView,
|
||||||
CustomEmojiView,
|
|
||||||
LocalUserView,
|
LocalUserView,
|
||||||
PostView,
|
PostView,
|
||||||
RegistrationApplicationView,
|
RegistrationApplicationView,
|
||||||
SiteView,
|
SiteView,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::{
|
use lemmy_db_views_actor::structs::{
|
||||||
CommunityBlockView,
|
|
||||||
CommunityFollowerView,
|
CommunityFollowerView,
|
||||||
CommunityModeratorView,
|
CommunityModeratorView,
|
||||||
CommunityView,
|
CommunityView,
|
||||||
InstanceBlockView,
|
|
||||||
PersonBlockView,
|
|
||||||
PersonView,
|
PersonView,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_moderator::structs::{
|
use lemmy_db_views_moderator::structs::{
|
||||||
|
@ -70,14 +71,32 @@ use ts_rs::TS;
|
||||||
/// Searches the site, given a query string, and some optional filters.
|
/// Searches the site, given a query string, and some optional filters.
|
||||||
pub struct Search {
|
pub struct Search {
|
||||||
pub q: String,
|
pub q: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_name: Option<String>,
|
pub community_name: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub creator_id: Option<PersonId>,
|
pub creator_id: Option<PersonId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub type_: Option<SearchType>,
|
pub type_: Option<SearchType>,
|
||||||
pub sort: Option<SortType>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub sort: Option<PostSortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub listing_type: Option<ListingType>,
|
pub listing_type: Option<ListingType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub title_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub post_url_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub saved_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub liked_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub disliked_only: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
@ -109,9 +128,13 @@ pub struct ResolveObject {
|
||||||
// TODO Change this to an enum
|
// TODO Change this to an enum
|
||||||
/// The response of an apub object fetch.
|
/// The response of an apub object fetch.
|
||||||
pub struct ResolveObjectResponse {
|
pub struct ResolveObjectResponse {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub comment: Option<CommentView>,
|
pub comment: Option<CommentView>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub post: Option<PostView>,
|
pub post: Option<PostView>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community: Option<CommunityView>,
|
pub community: Option<CommunityView>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub person: Option<PersonView>,
|
pub person: Option<PersonView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,13 +144,21 @@ pub struct ResolveObjectResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Fetches the modlog.
|
/// Fetches the modlog.
|
||||||
pub struct GetModlog {
|
pub struct GetModlog {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub mod_person_id: Option<PersonId>,
|
pub mod_person_id: Option<PersonId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub type_: Option<ModlogActionType>,
|
pub type_: Option<ModlogActionType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub other_person_id: Option<PersonId>,
|
pub other_person_id: Option<PersonId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub post_id: Option<PostId>,
|
pub post_id: Option<PostId>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub comment_id: Option<CommentId>,
|
pub comment_id: Option<CommentId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,47 +192,96 @@ pub struct GetModlogResponse {
|
||||||
/// Creates a site. Should be done after first running lemmy.
|
/// Creates a site. Should be done after first running lemmy.
|
||||||
pub struct CreateSite {
|
pub struct CreateSite {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub sidebar: Option<String>,
|
pub sidebar: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
pub enable_downvotes: Option<bool>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enable_nsfw: Option<bool>,
|
pub enable_nsfw: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_creation_admin_only: Option<bool>,
|
pub community_creation_admin_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub require_email_verification: Option<bool>,
|
pub require_email_verification: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub application_question: Option<String>,
|
pub application_question: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub private_instance: Option<bool>,
|
pub private_instance: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub default_theme: Option<String>,
|
pub default_theme: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub default_post_listing_type: Option<ListingType>,
|
pub default_post_listing_type: Option<ListingType>,
|
||||||
pub default_sort_type: Option<SortType>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub legal_information: Option<String>,
|
|
||||||
pub application_email_admins: Option<bool>,
|
|
||||||
pub hide_modlog_mod_names: Option<bool>,
|
|
||||||
pub discussion_languages: Option<Vec<LanguageId>>,
|
|
||||||
pub slur_filter_regex: Option<String>,
|
|
||||||
pub actor_name_max_length: Option<i32>,
|
|
||||||
pub rate_limit_message: Option<i32>,
|
|
||||||
pub rate_limit_message_per_second: Option<i32>,
|
|
||||||
pub rate_limit_post: Option<i32>,
|
|
||||||
pub rate_limit_post_per_second: Option<i32>,
|
|
||||||
pub rate_limit_register: Option<i32>,
|
|
||||||
pub rate_limit_register_per_second: Option<i32>,
|
|
||||||
pub rate_limit_image: Option<i32>,
|
|
||||||
pub rate_limit_image_per_second: Option<i32>,
|
|
||||||
pub rate_limit_comment: Option<i32>,
|
|
||||||
pub rate_limit_comment_per_second: Option<i32>,
|
|
||||||
pub rate_limit_search: Option<i32>,
|
|
||||||
pub rate_limit_search_per_second: Option<i32>,
|
|
||||||
pub federation_enabled: Option<bool>,
|
|
||||||
pub federation_debug: Option<bool>,
|
|
||||||
pub captcha_enabled: Option<bool>,
|
|
||||||
pub captcha_difficulty: Option<String>,
|
|
||||||
pub allowed_instances: Option<Vec<String>>,
|
|
||||||
pub blocked_instances: Option<Vec<String>>,
|
|
||||||
pub taglines: Option<Vec<String>>,
|
|
||||||
pub registration_mode: Option<RegistrationMode>,
|
|
||||||
pub content_warning: Option<String>,
|
|
||||||
pub default_post_listing_mode: Option<PostListingMode>,
|
pub default_post_listing_mode: Option<PostListingMode>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub default_post_sort_type: Option<PostSortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub default_comment_sort_type: Option<CommentSortType>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub legal_information: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub application_email_admins: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub hide_modlog_mod_names: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub discussion_languages: Option<Vec<LanguageId>>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub slur_filter_regex: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub actor_name_max_length: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_message: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_message_per_second: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_post: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_post_per_second: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_register: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_register_per_second: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_image: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_image_per_second: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_comment: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_comment_per_second: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_search: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub rate_limit_search_per_second: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub federation_enabled: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub federation_debug: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub captcha_enabled: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub captcha_difficulty: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub allowed_instances: Option<Vec<String>>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub blocked_instances: Option<Vec<String>>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub registration_mode: Option<RegistrationMode>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub oauth_registration: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub content_warning: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub post_upvotes: Option<FederationMode>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub post_downvotes: Option<FederationMode>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub comment_upvotes: Option<FederationMode>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub comment_downvotes: Option<FederationMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
@ -210,85 +290,143 @@ pub struct CreateSite {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Edits a site.
|
/// Edits a site.
|
||||||
pub struct EditSite {
|
pub struct EditSite {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
/// A sidebar for the site, in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub sidebar: Option<String>,
|
pub sidebar: Option<String>,
|
||||||
/// A shorter, one line description of your site.
|
/// A shorter, one line description of your site.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
/// A url for your site's icon.
|
/// A url for your site's icon.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
/// A url for your site's banner.
|
/// A url for your site's banner.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub banner: Option<String>,
|
pub banner: Option<String>,
|
||||||
/// Whether to enable downvotes.
|
|
||||||
pub enable_downvotes: Option<bool>,
|
|
||||||
/// Whether to enable NSFW.
|
/// Whether to enable NSFW.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enable_nsfw: Option<bool>,
|
pub enable_nsfw: Option<bool>,
|
||||||
/// Limits community creation to admins only.
|
/// Limits community creation to admins only.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub community_creation_admin_only: Option<bool>,
|
pub community_creation_admin_only: Option<bool>,
|
||||||
/// Whether to require email verification.
|
/// Whether to require email verification.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub require_email_verification: Option<bool>,
|
pub require_email_verification: Option<bool>,
|
||||||
/// Your application question form. This is in markdown, and can be many questions.
|
/// Your application question form. This is in markdown, and can be many questions.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub application_question: Option<String>,
|
pub application_question: Option<String>,
|
||||||
/// Whether your instance is public, or private.
|
/// Whether your instance is public, or private.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub private_instance: Option<bool>,
|
pub private_instance: Option<bool>,
|
||||||
/// The default theme. Usually "browser"
|
/// The default theme. Usually "browser"
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub default_theme: Option<String>,
|
pub default_theme: Option<String>,
|
||||||
|
/// The default post listing type, usually "local"
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub default_post_listing_type: Option<ListingType>,
|
pub default_post_listing_type: Option<ListingType>,
|
||||||
/// The default sort, usually "active"
|
/// Default value for listing mode, usually "list"
|
||||||
pub default_sort_type: Option<SortType>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub default_post_listing_mode: Option<PostListingMode>,
|
||||||
|
/// The default post sort, usually "active"
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub default_post_sort_type: Option<PostSortType>,
|
||||||
|
/// The default comment sort, usually "hot"
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub default_comment_sort_type: Option<CommentSortType>,
|
||||||
/// An optional page of legal information
|
/// An optional page of legal information
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub legal_information: Option<String>,
|
pub legal_information: Option<String>,
|
||||||
/// Whether to email admins when receiving a new application.
|
/// Whether to email admins when receiving a new application.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub application_email_admins: Option<bool>,
|
pub application_email_admins: Option<bool>,
|
||||||
/// Whether to hide moderator names from the modlog.
|
/// Whether to hide moderator names from the modlog.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub hide_modlog_mod_names: Option<bool>,
|
pub hide_modlog_mod_names: Option<bool>,
|
||||||
/// A list of allowed discussion languages.
|
/// A list of allowed discussion languages.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub discussion_languages: Option<Vec<LanguageId>>,
|
pub discussion_languages: Option<Vec<LanguageId>>,
|
||||||
/// A regex string of items to filter.
|
/// A regex string of items to filter.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub slur_filter_regex: Option<String>,
|
pub slur_filter_regex: Option<String>,
|
||||||
/// The max length of actor names.
|
/// The max length of actor names.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub actor_name_max_length: Option<i32>,
|
pub actor_name_max_length: Option<i32>,
|
||||||
/// The number of messages allowed in a given time frame.
|
/// The number of messages allowed in a given time frame.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_message: Option<i32>,
|
pub rate_limit_message: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_message_per_second: Option<i32>,
|
pub rate_limit_message_per_second: Option<i32>,
|
||||||
/// The number of posts allowed in a given time frame.
|
/// The number of posts allowed in a given time frame.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_post: Option<i32>,
|
pub rate_limit_post: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_post_per_second: Option<i32>,
|
pub rate_limit_post_per_second: Option<i32>,
|
||||||
/// The number of registrations allowed in a given time frame.
|
/// The number of registrations allowed in a given time frame.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_register: Option<i32>,
|
pub rate_limit_register: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_register_per_second: Option<i32>,
|
pub rate_limit_register_per_second: Option<i32>,
|
||||||
/// The number of image uploads allowed in a given time frame.
|
/// The number of image uploads allowed in a given time frame.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_image: Option<i32>,
|
pub rate_limit_image: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_image_per_second: Option<i32>,
|
pub rate_limit_image_per_second: Option<i32>,
|
||||||
/// The number of comments allowed in a given time frame.
|
/// The number of comments allowed in a given time frame.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_comment: Option<i32>,
|
pub rate_limit_comment: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_comment_per_second: Option<i32>,
|
pub rate_limit_comment_per_second: Option<i32>,
|
||||||
/// The number of searches allowed in a given time frame.
|
/// The number of searches allowed in a given time frame.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_search: Option<i32>,
|
pub rate_limit_search: Option<i32>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub rate_limit_search_per_second: Option<i32>,
|
pub rate_limit_search_per_second: Option<i32>,
|
||||||
/// Whether to enable federation.
|
/// Whether to enable federation.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub federation_enabled: Option<bool>,
|
pub federation_enabled: Option<bool>,
|
||||||
/// Enables federation debugging.
|
/// Enables federation debugging.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub federation_debug: Option<bool>,
|
pub federation_debug: Option<bool>,
|
||||||
/// Whether to enable captchas for signups.
|
/// Whether to enable captchas for signups.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub captcha_enabled: Option<bool>,
|
pub captcha_enabled: Option<bool>,
|
||||||
/// The captcha difficulty. Can be easy, medium, or hard
|
/// The captcha difficulty. Can be easy, medium, or hard
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub captcha_difficulty: Option<String>,
|
pub captcha_difficulty: Option<String>,
|
||||||
/// A list of allowed instances. If none are set, federation is open.
|
/// A list of allowed instances. If none are set, federation is open.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub allowed_instances: Option<Vec<String>>,
|
pub allowed_instances: Option<Vec<String>>,
|
||||||
/// A list of blocked instances.
|
/// A list of blocked instances.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub blocked_instances: Option<Vec<String>>,
|
pub blocked_instances: Option<Vec<String>>,
|
||||||
/// A list of blocked URLs
|
/// A list of blocked URLs
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub blocked_urls: Option<Vec<String>>,
|
pub blocked_urls: Option<Vec<String>>,
|
||||||
/// A list of taglines shown at the top of the front page.
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub taglines: Option<Vec<String>>,
|
|
||||||
pub registration_mode: Option<RegistrationMode>,
|
pub registration_mode: Option<RegistrationMode>,
|
||||||
/// Whether to email admins for new reports.
|
/// Whether to email admins for new reports.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reports_email_admins: Option<bool>,
|
pub reports_email_admins: Option<bool>,
|
||||||
/// If present, nsfw content is visible by default. Should be displayed by frontends/clients
|
/// If present, nsfw content is visible by default. Should be displayed by frontends/clients
|
||||||
/// when the site is first opened by a user.
|
/// when the site is first opened by a user.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub content_warning: Option<String>,
|
pub content_warning: Option<String>,
|
||||||
/// Default value for [LocalUser.post_listing_mode]
|
/// Whether or not external auth methods can auto-register users.
|
||||||
pub default_post_listing_mode: Option<PostListingMode>,
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub oauth_registration: Option<bool>,
|
||||||
|
/// What kind of post upvotes your site allows.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub post_upvotes: Option<FederationMode>,
|
||||||
|
/// What kind of post downvotes your site allows.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub post_downvotes: Option<FederationMode>,
|
||||||
|
/// What kind of comment upvotes your site allows.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub comment_upvotes: Option<FederationMode>,
|
||||||
|
/// What kind of comment downvotes your site allows.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub comment_downvotes: Option<FederationMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
@ -297,7 +435,8 @@ pub struct EditSite {
|
||||||
/// The response for a site.
|
/// The response for a site.
|
||||||
pub struct SiteResponse {
|
pub struct SiteResponse {
|
||||||
pub site_view: SiteView,
|
pub site_view: SiteView,
|
||||||
pub taglines: Vec<Tagline>,
|
/// deprecated, use field `tagline` or /api/v3/tagline/list
|
||||||
|
pub taglines: Vec<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
@ -309,13 +448,22 @@ pub struct GetSiteResponse {
|
||||||
pub site_view: SiteView,
|
pub site_view: SiteView,
|
||||||
pub admins: Vec<PersonView>,
|
pub admins: Vec<PersonView>,
|
||||||
pub version: String,
|
pub version: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub my_user: Option<MyUserInfo>,
|
pub my_user: Option<MyUserInfo>,
|
||||||
pub all_languages: Vec<Language>,
|
pub all_languages: Vec<Language>,
|
||||||
pub discussion_languages: Vec<LanguageId>,
|
pub discussion_languages: Vec<LanguageId>,
|
||||||
/// A list of taglines shown at the top of the front page.
|
/// deprecated, use field `tagline` or /api/v3/tagline/list
|
||||||
pub taglines: Vec<Tagline>,
|
pub taglines: Vec<()>,
|
||||||
/// A list of custom emojis your site supports.
|
/// deprecated, use /api/v3/custom_emoji/list
|
||||||
pub custom_emojis: Vec<CustomEmojiView>,
|
pub custom_emojis: Vec<()>,
|
||||||
|
/// If the site has any taglines, a random one is included here for displaying
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub tagline: Option<Tagline>,
|
||||||
|
/// A list of external auth methods your site supports.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub oauth_providers: Option<Vec<PublicOAuthProvider>>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub admin_oauth_providers: Option<Vec<OAuthProvider>>,
|
||||||
pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
|
pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +474,7 @@ pub struct GetSiteResponse {
|
||||||
/// A response of federated instances.
|
/// A response of federated instances.
|
||||||
pub struct GetFederatedInstancesResponse {
|
pub struct GetFederatedInstancesResponse {
|
||||||
/// Optional, because federation may be disabled.
|
/// Optional, because federation may be disabled.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub federated_instances: Option<FederatedInstances>,
|
pub federated_instances: Option<FederatedInstances>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,9 +486,9 @@ pub struct MyUserInfo {
|
||||||
pub local_user_view: LocalUserView,
|
pub local_user_view: LocalUserView,
|
||||||
pub follows: Vec<CommunityFollowerView>,
|
pub follows: Vec<CommunityFollowerView>,
|
||||||
pub moderates: Vec<CommunityModeratorView>,
|
pub moderates: Vec<CommunityModeratorView>,
|
||||||
pub community_blocks: Vec<CommunityBlockView>,
|
pub community_blocks: Vec<Community>,
|
||||||
pub instance_blocks: Vec<InstanceBlockView>,
|
pub instance_blocks: Vec<Instance>,
|
||||||
pub person_blocks: Vec<PersonBlockView>,
|
pub person_blocks: Vec<Person>,
|
||||||
pub discussion_languages: Vec<LanguageId>,
|
pub discussion_languages: Vec<LanguageId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,6 +510,7 @@ pub struct ReadableFederationState {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
internal_state: FederationQueueState,
|
internal_state: FederationQueueState,
|
||||||
/// timestamp of the next retry attempt (null if fail count is 0)
|
/// timestamp of the next retry attempt (null if fail count is 0)
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
next_retry: Option<DateTime<Utc>>,
|
next_retry: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +535,7 @@ pub struct InstanceWithFederationState {
|
||||||
pub instance: Instance,
|
pub instance: Instance,
|
||||||
/// if federation to this instance is or was active, show state of outgoing federation to this
|
/// if federation to this instance is or was active, show state of outgoing federation to this
|
||||||
/// instance
|
/// instance
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub federation_state: Option<ReadableFederationState>,
|
pub federation_state: Option<ReadableFederationState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,6 +546,7 @@ pub struct InstanceWithFederationState {
|
||||||
/// Purges a person from the database. This will delete all content attached to that person.
|
/// Purges a person from the database. This will delete all content attached to that person.
|
||||||
pub struct PurgePerson {
|
pub struct PurgePerson {
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,6 +557,7 @@ pub struct PurgePerson {
|
||||||
/// Purges a community from the database. This will delete all content attached to that community.
|
/// Purges a community from the database. This will delete all content attached to that community.
|
||||||
pub struct PurgeCommunity {
|
pub struct PurgeCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,6 +568,7 @@ pub struct PurgeCommunity {
|
||||||
/// Purges a post from the database. This will delete all content attached to that post.
|
/// Purges a post from the database. This will delete all content attached to that post.
|
||||||
pub struct PurgePost {
|
pub struct PurgePost {
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,6 +579,7 @@ pub struct PurgePost {
|
||||||
/// Purges a comment from the database. This will delete all content attached to that comment.
|
/// Purges a comment from the database. This will delete all content attached to that comment.
|
||||||
pub struct PurgeComment {
|
pub struct PurgeComment {
|
||||||
pub comment_id: CommentId,
|
pub comment_id: CommentId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,8 +590,11 @@ pub struct PurgeComment {
|
||||||
/// Fetches a list of registration applications.
|
/// Fetches a list of registration applications.
|
||||||
pub struct ListRegistrationApplications {
|
pub struct ListRegistrationApplications {
|
||||||
/// Only shows the unread applications (IE those without an admin actor)
|
/// Only shows the unread applications (IE those without an admin actor)
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub unread_only: Option<bool>,
|
pub unread_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,6 +623,7 @@ pub struct GetRegistrationApplication {
|
||||||
pub struct ApproveRegistrationApplication {
|
pub struct ApproveRegistrationApplication {
|
||||||
pub id: RegistrationApplicationId,
|
pub id: RegistrationApplicationId,
|
||||||
pub approve: bool,
|
pub approve: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub deny_reason: Option<String>,
|
pub deny_reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
57
crates/api_common/src/tagline.rs
Normal file
57
crates/api_common/src/tagline.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use lemmy_db_schema::{newtypes::TaglineId, source::tagline::Tagline};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Create a tagline
|
||||||
|
pub struct CreateTagline {
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Update a tagline
|
||||||
|
pub struct UpdateTagline {
|
||||||
|
pub id: TaglineId,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Delete a tagline
|
||||||
|
pub struct DeleteTagline {
|
||||||
|
pub id: TaglineId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct TaglineResponse {
|
||||||
|
pub tagline: Tagline,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// A response for taglines.
|
||||||
|
pub struct ListTaglinesResponse {
|
||||||
|
pub taglines: Vec<Tagline>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Fetches a list of taglines.
|
||||||
|
pub struct ListTaglines {
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub page: Option<i64>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
}
|
|
@ -11,32 +11,38 @@ use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
||||||
use enum_map::{enum_map, EnumMap};
|
use enum_map::{enum_map, EnumMap};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||||
newtypes::{CommunityId, DbUrl, InstanceId, PersonId, PostId},
|
newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId},
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentLike, CommentUpdateForm},
|
||||||
community::{Community, CommunityModerator, CommunityUpdateForm},
|
community::{Community, CommunityModerator, CommunityUpdateForm},
|
||||||
community_block::CommunityBlock,
|
community_block::CommunityBlock,
|
||||||
email_verification::{EmailVerification, EmailVerificationForm},
|
email_verification::{EmailVerification, EmailVerificationForm},
|
||||||
images::RemoteImage,
|
images::{ImageDetails, RemoteImage},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
instance_block::InstanceBlock,
|
instance_block::InstanceBlock,
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
local_site_rate_limit::LocalSiteRateLimit,
|
local_site_rate_limit::LocalSiteRateLimit,
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
|
moderator::{ModRemoveComment, ModRemoveCommentForm, ModRemovePost, ModRemovePostForm},
|
||||||
|
oauth_account::OAuthAccount,
|
||||||
password_reset_request::PasswordResetRequest,
|
password_reset_request::PasswordResetRequest,
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
person_block::PersonBlock,
|
person_block::PersonBlock,
|
||||||
post::{Post, PostRead},
|
post::{Post, PostLike, PostRead},
|
||||||
|
registration_application::RegistrationApplication,
|
||||||
site::Site,
|
site::Site,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::{Crud, Likeable},
|
||||||
utils::DbPool,
|
utils::DbPool,
|
||||||
|
FederationMode,
|
||||||
|
RegistrationMode,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{
|
use lemmy_db_views::{
|
||||||
comment_view::CommentQuery,
|
comment_view::CommentQuery,
|
||||||
structs::{LocalImageView, LocalUserView},
|
structs::{LocalImageView, LocalUserView, SiteView},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::{
|
use lemmy_db_views_actor::structs::{
|
||||||
|
CommunityFollowerView,
|
||||||
CommunityModeratorView,
|
CommunityModeratorView,
|
||||||
CommunityPersonBanView,
|
CommunityPersonBanView,
|
||||||
CommunityView,
|
CommunityView,
|
||||||
|
@ -45,10 +51,14 @@ use lemmy_utils::{
|
||||||
email::{send_email, translations::Lang},
|
email::{send_email, translations::Lang},
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
rate_limit::{ActionType, BucketConfig},
|
rate_limit::{ActionType, BucketConfig},
|
||||||
settings::structs::{PictrsImageMode, Settings},
|
settings::{
|
||||||
|
structs::{PictrsImageMode, Settings},
|
||||||
|
SETTINGS,
|
||||||
|
},
|
||||||
utils::{
|
utils::{
|
||||||
markdown::{markdown_check_for_blocked_urls, markdown_rewrite_image_links},
|
markdown::{image_links::markdown_rewrite_image_links, markdown_check_for_blocked_urls},
|
||||||
slurs::{build_slur_regex, remove_slurs},
|
slurs::{build_slur_regex, remove_slurs},
|
||||||
|
validation::clean_urls_in_text,
|
||||||
},
|
},
|
||||||
CACHE_DURATION_FEDERATION,
|
CACHE_DURATION_FEDERATION,
|
||||||
};
|
};
|
||||||
|
@ -69,13 +79,7 @@ pub async fn is_mod_or_admin(
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
check_user_valid(person)?;
|
check_user_valid(person)?;
|
||||||
|
CommunityView::check_is_mod_or_admin(pool, person.id, community_id).await
|
||||||
let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?;
|
|
||||||
if !is_mod_or_admin {
|
|
||||||
Err(LemmyErrorType::NotAModOrAdmin)?
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
|
@ -106,13 +110,7 @@ pub async fn check_community_mod_of_any_or_admin_action(
|
||||||
let person = &local_user_view.person;
|
let person = &local_user_view.person;
|
||||||
|
|
||||||
check_user_valid(person)?;
|
check_user_valid(person)?;
|
||||||
|
CommunityView::check_is_mod_of_any_or_admin(pool, person.id).await
|
||||||
let is_mod_of_any_or_admin = CommunityView::is_mod_of_any_or_admin(pool, person.id).await?;
|
|
||||||
if !is_mod_of_any_or_admin {
|
|
||||||
Err(LemmyErrorType::NotAModOrAdmin)?
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
|
pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
|
||||||
|
@ -168,12 +166,9 @@ pub async fn update_read_comments(
|
||||||
person_id,
|
person_id,
|
||||||
post_id,
|
post_id,
|
||||||
read_comments,
|
read_comments,
|
||||||
..PersonPostAggregatesForm::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PersonPostAggregates::upsert(pool, &person_post_agg_form)
|
PersonPostAggregates::upsert(pool, &person_post_agg_form).await?;
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -191,63 +186,85 @@ pub fn check_user_valid(person: &Person) -> LemmyResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the user's email is verified if email verification is turned on
|
||||||
|
/// However, skip checking verification if the user is an admin
|
||||||
|
pub fn check_email_verified(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
site_view: &SiteView,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if !local_user_view.local_user.admin
|
||||||
|
&& site_view.local_site.require_email_verification
|
||||||
|
&& !local_user_view.local_user.email_verified
|
||||||
|
{
|
||||||
|
Err(LemmyErrorType::EmailNotVerified)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_registration_application(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
local_site: &LocalSite,
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if (local_site.registration_mode == RegistrationMode::RequireApplication
|
||||||
|
|| local_site.registration_mode == RegistrationMode::Closed)
|
||||||
|
&& !local_user_view.local_user.accepted_application
|
||||||
|
&& !local_user_view.local_user.admin
|
||||||
|
{
|
||||||
|
// 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?;
|
||||||
|
if registration.admin_id.is_some() {
|
||||||
|
Err(LemmyErrorType::RegistrationDenied {
|
||||||
|
reason: registration.deny_reason,
|
||||||
|
})?
|
||||||
|
} else {
|
||||||
|
Err(LemmyErrorType::RegistrationApplicationIsPending)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks that a normal user action (eg posting or voting) is allowed in a given community.
|
/// Checks that a normal user action (eg posting or voting) is allowed in a given community.
|
||||||
///
|
///
|
||||||
/// In particular it checks that neither the user nor community are banned or deleted, and that
|
/// In particular it checks that neither the user nor community are banned or deleted, and that
|
||||||
/// the user isn't banned.
|
/// the user isn't banned.
|
||||||
pub async fn check_community_user_action(
|
pub async fn check_community_user_action(
|
||||||
person: &Person,
|
person: &Person,
|
||||||
community_id: CommunityId,
|
community: &Community,
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
check_user_valid(person)?;
|
check_user_valid(person)?;
|
||||||
check_community_deleted_removed(community_id, pool).await?;
|
check_community_deleted_removed(community)?;
|
||||||
check_community_ban(person, community_id, pool).await?;
|
CommunityPersonBanView::check(pool, person.id, community.id).await?;
|
||||||
|
CommunityFollowerView::check_private_community_action(pool, person.id, community).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_community_deleted_removed(
|
pub fn check_community_deleted_removed(community: &Community) -> LemmyResult<()> {
|
||||||
community_id: CommunityId,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
let community = Community::read(pool, community_id)
|
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
if community.deleted || community.removed {
|
if community.deleted || community.removed {
|
||||||
Err(LemmyErrorType::Deleted)?
|
Err(LemmyErrorType::Deleted)?
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_community_ban(
|
|
||||||
person: &Person,
|
|
||||||
community_id: CommunityId,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
// check if user was banned from site or community
|
|
||||||
let is_banned = CommunityPersonBanView::get(pool, person.id, community_id).await?;
|
|
||||||
if is_banned {
|
|
||||||
Err(LemmyErrorType::BannedFromCommunity)?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that the given user can perform a mod action in the community.
|
/// Check that the given user can perform a mod action in the community.
|
||||||
///
|
///
|
||||||
/// In particular it checks that he is an admin or mod, wasn't banned and the community isn't
|
/// In particular it checks that he is an admin or mod, wasn't banned and the community isn't
|
||||||
/// removed/deleted.
|
/// removed/deleted.
|
||||||
pub async fn check_community_mod_action(
|
pub async fn check_community_mod_action(
|
||||||
person: &Person,
|
person: &Person,
|
||||||
community_id: CommunityId,
|
community: &Community,
|
||||||
allow_deleted: bool,
|
allow_deleted: bool,
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
is_mod_or_admin(pool, person, community_id).await?;
|
is_mod_or_admin(pool, person, community.id).await?;
|
||||||
check_community_ban(person, community_id, pool).await?;
|
CommunityPersonBanView::check(pool, person.id, community.id).await?;
|
||||||
|
|
||||||
// it must be possible to restore deleted community
|
// it must be possible to restore deleted community
|
||||||
if !allow_deleted {
|
if !allow_deleted {
|
||||||
check_community_deleted_removed(community_id, pool).await?;
|
check_community_deleted_removed(community)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -269,51 +286,6 @@ pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Throws an error if a recipient has blocked a person.
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn check_person_block(
|
|
||||||
my_id: PersonId,
|
|
||||||
potential_blocker_id: PersonId,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id).await?;
|
|
||||||
if is_blocked {
|
|
||||||
Err(LemmyErrorType::PersonIsBlocked)?
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Throws an error if a recipient has blocked a community.
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
async fn check_community_block(
|
|
||||||
community_id: CommunityId,
|
|
||||||
person_id: PersonId,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
let is_blocked = CommunityBlock::read(pool, person_id, community_id).await?;
|
|
||||||
if is_blocked {
|
|
||||||
Err(LemmyErrorType::CommunityIsBlocked)?
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Throws an error if a recipient has blocked an instance.
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
async fn check_instance_block(
|
|
||||||
instance_id: InstanceId,
|
|
||||||
person_id: PersonId,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
let is_blocked = InstanceBlock::read(pool, person_id, instance_id).await?;
|
|
||||||
if is_blocked {
|
|
||||||
Err(LemmyErrorType::InstanceIsBlocked)?
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn check_person_instance_community_block(
|
pub async fn check_person_instance_community_block(
|
||||||
my_id: PersonId,
|
my_id: PersonId,
|
||||||
|
@ -322,19 +294,42 @@ pub async fn check_person_instance_community_block(
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
check_person_block(my_id, potential_blocker_id, pool).await?;
|
PersonBlock::read(pool, potential_blocker_id, my_id).await?;
|
||||||
check_instance_block(community_instance_id, potential_blocker_id, pool).await?;
|
InstanceBlock::read(pool, potential_blocker_id, community_instance_id).await?;
|
||||||
check_community_block(community_id, potential_blocker_id, pool).await?;
|
CommunityBlock::read(pool, potential_blocker_id, community_id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A vote item type used to check the vote mode.
|
||||||
|
pub enum VoteItem {
|
||||||
|
Post(PostId),
|
||||||
|
Comment(CommentId),
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> LemmyResult<()> {
|
pub async fn check_local_vote_mode(
|
||||||
if score == -1 && !local_site.enable_downvotes {
|
score: i16,
|
||||||
Err(LemmyErrorType::DownvotesAreDisabled)?
|
vote_item: VoteItem,
|
||||||
} else {
|
local_site: &LocalSite,
|
||||||
Ok(())
|
person_id: PersonId,
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
let (downvote_setting, upvote_setting) = match vote_item {
|
||||||
|
VoteItem::Post(_) => (local_site.post_downvotes, local_site.post_upvotes),
|
||||||
|
VoteItem::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
|
||||||
|
};
|
||||||
|
|
||||||
|
let downvote_fail = score == -1 && downvote_setting == FederationMode::Disable;
|
||||||
|
let upvote_fail = score == 1 && upvote_setting == FederationMode::Disable;
|
||||||
|
|
||||||
|
// Undo previous vote for item if new vote fails
|
||||||
|
if downvote_fail || upvote_fail {
|
||||||
|
match vote_item {
|
||||||
|
VoteItem::Post(post_id) => PostLike::remove(pool, person_id, post_id).await?,
|
||||||
|
VoteItem::Comment(comment_id) => CommentLike::remove(pool, person_id, comment_id).await?,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dont allow bots to do certain actions, like voting
|
/// Dont allow bots to do certain actions, like voting
|
||||||
|
@ -359,6 +354,16 @@ pub fn check_private_instance(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If private messages are disabled, dont allow them to be sent / received
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn check_private_messages_enabled(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
|
||||||
|
if !local_user_view.local_user.enable_private_messages {
|
||||||
|
Err(LemmyErrorType::CouldntCreatePrivateMessage)?
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn build_federated_instances(
|
pub async fn build_federated_instances(
|
||||||
local_site: &LocalSite,
|
local_site: &LocalSite,
|
||||||
|
@ -537,13 +542,6 @@ pub fn local_site_opt_to_slur_regex(local_site: &Option<LocalSite>) -> Option<Re
|
||||||
.unwrap_or(None)
|
.unwrap_or(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_site_opt_to_sensitive(local_site: &Option<LocalSite>) -> bool {
|
|
||||||
local_site
|
|
||||||
.as_ref()
|
|
||||||
.map(|site| site.enable_nsfw)
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet> {
|
pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet> {
|
||||||
static URL_BLOCKLIST: LazyLock<Cache<(), RegexSet>> = LazyLock::new(|| {
|
static URL_BLOCKLIST: LazyLock<Cache<(), RegexSet>> = LazyLock::new(|| {
|
||||||
Cache::builder()
|
Cache::builder()
|
||||||
|
@ -667,7 +665,7 @@ pub async fn purge_image_posts_for_person(
|
||||||
|
|
||||||
/// Delete a local_user's images
|
/// Delete a local_user's images
|
||||||
async fn delete_local_user_images(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> {
|
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 {
|
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await {
|
||||||
let pictrs_uploads =
|
let pictrs_uploads =
|
||||||
LocalImageView::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id)
|
LocalImageView::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -706,106 +704,189 @@ pub async fn purge_image_posts_for_community(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_user_data(
|
/// Removes or restores user data.
|
||||||
|
pub async fn remove_or_restore_user_data(
|
||||||
|
mod_person_id: PersonId,
|
||||||
banned_person_id: PersonId,
|
banned_person_id: PersonId,
|
||||||
|
removed: bool,
|
||||||
|
reason: &Option<String>,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
let pool = &mut context.pool();
|
let pool = &mut context.pool();
|
||||||
// Purge user images
|
|
||||||
let person = Person::read(pool, banned_person_id)
|
// Only these actions are possible when removing, not restoring
|
||||||
.await?
|
if removed {
|
||||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
// Purge user images
|
||||||
if let Some(avatar) = person.avatar {
|
let person = Person::read(pool, banned_person_id).await?;
|
||||||
purge_image_from_pictrs(&avatar, context).await.ok();
|
if let Some(avatar) = person.avatar {
|
||||||
}
|
purge_image_from_pictrs(&avatar, context).await.ok();
|
||||||
if let Some(banner) = person.banner {
|
}
|
||||||
purge_image_from_pictrs(&banner, context).await.ok();
|
if let Some(banner) = person.banner {
|
||||||
|
purge_image_from_pictrs(&banner, context).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the fields to None
|
||||||
|
Person::update(
|
||||||
|
pool,
|
||||||
|
banned_person_id,
|
||||||
|
&PersonUpdateForm {
|
||||||
|
avatar: Some(None),
|
||||||
|
banner: Some(None),
|
||||||
|
bio: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Purge image posts
|
||||||
|
purge_image_posts_for_person(banned_person_id, context).await?;
|
||||||
|
|
||||||
|
// Communities
|
||||||
|
// Remove all communities where they're the top mod
|
||||||
|
// for now, remove the communities manually
|
||||||
|
let first_mod_communities = CommunityModeratorView::get_community_first_mods(pool).await?;
|
||||||
|
|
||||||
|
// Filter to only this banned users top communities
|
||||||
|
let banned_user_first_communities: Vec<CommunityModeratorView> = first_mod_communities
|
||||||
|
.into_iter()
|
||||||
|
.filter(|fmc| fmc.moderator.id == banned_person_id)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for first_mod_community in banned_user_first_communities {
|
||||||
|
let community_id = first_mod_community.community.id;
|
||||||
|
Community::update(
|
||||||
|
pool,
|
||||||
|
community_id,
|
||||||
|
&CommunityUpdateForm {
|
||||||
|
removed: Some(removed),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Delete the community images
|
||||||
|
if let Some(icon) = first_mod_community.community.icon {
|
||||||
|
purge_image_from_pictrs(&icon, context).await.ok();
|
||||||
|
}
|
||||||
|
if let Some(banner) = first_mod_community.community.banner {
|
||||||
|
purge_image_from_pictrs(&banner, context).await.ok();
|
||||||
|
}
|
||||||
|
// Update the fields to None
|
||||||
|
Community::update(
|
||||||
|
pool,
|
||||||
|
community_id,
|
||||||
|
&CommunityUpdateForm {
|
||||||
|
icon: Some(None),
|
||||||
|
banner: Some(None),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the fields to None
|
// Posts
|
||||||
Person::update(
|
let removed_or_restored_posts =
|
||||||
|
Post::update_removed_for_creator(pool, banned_person_id, None, removed).await?;
|
||||||
|
create_modlog_entries_for_removed_or_restored_posts(
|
||||||
pool,
|
pool,
|
||||||
banned_person_id,
|
mod_person_id,
|
||||||
&PersonUpdateForm {
|
removed_or_restored_posts.iter().map(|r| r.id).collect(),
|
||||||
avatar: Some(None),
|
removed,
|
||||||
banner: Some(None),
|
reason,
|
||||||
bio: Some(None),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Posts
|
|
||||||
Post::update_removed_for_creator(pool, banned_person_id, None, true).await?;
|
|
||||||
|
|
||||||
// Purge image posts
|
|
||||||
purge_image_posts_for_person(banned_person_id, context).await?;
|
|
||||||
|
|
||||||
// Communities
|
|
||||||
// Remove all communities where they're the top mod
|
|
||||||
// for now, remove the communities manually
|
|
||||||
let first_mod_communities = CommunityModeratorView::get_community_first_mods(pool).await?;
|
|
||||||
|
|
||||||
// Filter to only this banned users top communities
|
|
||||||
let banned_user_first_communities: Vec<CommunityModeratorView> = first_mod_communities
|
|
||||||
.into_iter()
|
|
||||||
.filter(|fmc| fmc.moderator.id == banned_person_id)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for first_mod_community in banned_user_first_communities {
|
|
||||||
let community_id = first_mod_community.community.id;
|
|
||||||
Community::update(
|
|
||||||
pool,
|
|
||||||
community_id,
|
|
||||||
&CommunityUpdateForm {
|
|
||||||
removed: Some(true),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Delete the community images
|
|
||||||
if let Some(icon) = first_mod_community.community.icon {
|
|
||||||
purge_image_from_pictrs(&icon, context).await.ok();
|
|
||||||
}
|
|
||||||
if let Some(banner) = first_mod_community.community.banner {
|
|
||||||
purge_image_from_pictrs(&banner, context).await.ok();
|
|
||||||
}
|
|
||||||
// Update the fields to None
|
|
||||||
Community::update(
|
|
||||||
pool,
|
|
||||||
community_id,
|
|
||||||
&CommunityUpdateForm {
|
|
||||||
icon: Some(None),
|
|
||||||
banner: Some(None),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
Comment::update_removed_for_creator(pool, banned_person_id, true).await?;
|
let removed_or_restored_comments =
|
||||||
|
Comment::update_removed_for_creator(pool, banned_person_id, removed).await?;
|
||||||
|
create_modlog_entries_for_removed_or_restored_comments(
|
||||||
|
pool,
|
||||||
|
mod_person_id,
|
||||||
|
removed_or_restored_comments.iter().map(|r| r.id).collect(),
|
||||||
|
removed,
|
||||||
|
reason,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_user_data_in_community(
|
async fn create_modlog_entries_for_removed_or_restored_posts(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
mod_person_id: PersonId,
|
||||||
|
post_ids: Vec<PostId>,
|
||||||
|
removed: bool,
|
||||||
|
reason: &Option<String>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
// Build the forms
|
||||||
|
let forms = post_ids
|
||||||
|
.iter()
|
||||||
|
.map(|&post_id| ModRemovePostForm {
|
||||||
|
mod_person_id,
|
||||||
|
post_id,
|
||||||
|
removed: Some(removed),
|
||||||
|
reason: reason.clone(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
ModRemovePost::create_multiple(pool, &forms).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_modlog_entries_for_removed_or_restored_comments(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
mod_person_id: PersonId,
|
||||||
|
comment_ids: Vec<CommentId>,
|
||||||
|
removed: bool,
|
||||||
|
reason: &Option<String>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
// Build the forms
|
||||||
|
let forms = comment_ids
|
||||||
|
.iter()
|
||||||
|
.map(|&comment_id| ModRemoveCommentForm {
|
||||||
|
mod_person_id,
|
||||||
|
comment_id,
|
||||||
|
removed: Some(removed),
|
||||||
|
reason: reason.clone(),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
ModRemoveComment::create_multiple(pool, &forms).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_or_restore_user_data_in_community(
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
|
mod_person_id: PersonId,
|
||||||
banned_person_id: PersonId,
|
banned_person_id: PersonId,
|
||||||
|
remove: bool,
|
||||||
|
reason: &Option<String>,
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
// Posts
|
// Posts
|
||||||
Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), true).await?;
|
let posts =
|
||||||
|
Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), remove).await?;
|
||||||
|
create_modlog_entries_for_removed_or_restored_posts(
|
||||||
|
pool,
|
||||||
|
mod_person_id,
|
||||||
|
posts.iter().map(|r| r.id).collect(),
|
||||||
|
remove,
|
||||||
|
reason,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
// TODO Diesel doesn't allow updates with joins, so this has to be a loop
|
// TODO Diesel doesn't allow updates with joins, so this has to be a loop
|
||||||
|
let site = Site::read_local(pool).await?;
|
||||||
let comments = CommentQuery {
|
let comments = CommentQuery {
|
||||||
creator_id: Some(banned_person_id),
|
creator_id: Some(banned_person_id),
|
||||||
community_id: Some(community_id),
|
community_id: Some(community_id),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(pool)
|
.list(&site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for comment_view in &comments {
|
for comment_view in &comments {
|
||||||
|
@ -814,22 +895,29 @@ pub async fn remove_user_data_in_community(
|
||||||
pool,
|
pool,
|
||||||
comment_id,
|
comment_id,
|
||||||
&CommentUpdateForm {
|
&CommentUpdateForm {
|
||||||
removed: Some(true),
|
removed: Some(remove),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_modlog_entries_for_removed_or_restored_comments(
|
||||||
|
pool,
|
||||||
|
mod_person_id,
|
||||||
|
comments.iter().map(|r| r.comment.id).collect(),
|
||||||
|
remove,
|
||||||
|
reason,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> {
|
pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) -> LemmyResult<()> {
|
||||||
let pool = &mut context.pool();
|
let pool = &mut context.pool();
|
||||||
|
|
||||||
let person = Person::read(pool, person_id)
|
let person = Person::read(pool, person_id).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
|
||||||
|
|
||||||
// Delete their local images, if they're a local user
|
// Delete their local images, if they're a local user
|
||||||
delete_local_user_images(person_id, context).await.ok();
|
delete_local_user_images(person_id, context).await.ok();
|
||||||
|
@ -858,6 +946,11 @@ pub async fn purge_user_account(person_id: PersonId, context: &LemmyContext) ->
|
||||||
// Leave communities they mod
|
// Leave communities they mod
|
||||||
CommunityModerator::leave_all_communities(pool, person_id).await?;
|
CommunityModerator::leave_all_communities(pool, person_id).await?;
|
||||||
|
|
||||||
|
// Delete the oauth accounts linked to the local user
|
||||||
|
if let Ok(local_user) = LocalUserView::read_person(pool, person_id).await {
|
||||||
|
OAuthAccount::delete_user_accounts(pool, local_user.local_user.id).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Person::delete_account(pool, person_id).await?;
|
Person::delete_account(pool, person_id).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -892,12 +985,8 @@ pub fn generate_followers_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
||||||
Ok(Url::parse(&format!("{actor_id}/followers"))?.into())
|
Ok(Url::parse(&format!("{actor_id}/followers"))?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
|
pub fn generate_inbox_url() -> LemmyResult<DbUrl> {
|
||||||
Ok(Url::parse(&format!("{actor_id}/inbox"))?.into())
|
let url = format!("{}/inbox", SETTINGS.get_protocol_and_hostname());
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_shared_inbox_url(settings: &Settings) -> LemmyResult<DbUrl> {
|
|
||||||
let url = format!("{}/inbox", settings.get_protocol_and_hostname());
|
|
||||||
Ok(Url::parse(&url)?.into())
|
Ok(Url::parse(&url)?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -940,6 +1029,18 @@ fn limit_expire_time(expires: DateTime<Utc>) -> LemmyResult<Option<DateTime<Utc>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn check_conflicting_like_filters(
|
||||||
|
liked_only: Option<bool>,
|
||||||
|
disliked_only: Option<bool>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
|
||||||
|
Err(LemmyErrorType::ContradictingFilters)?
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn process_markdown(
|
pub async fn process_markdown(
|
||||||
text: &str,
|
text: &str,
|
||||||
slur_regex: &Option<Regex>,
|
slur_regex: &Option<Regex>,
|
||||||
|
@ -947,11 +1048,13 @@ pub async fn process_markdown(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> LemmyResult<String> {
|
) -> LemmyResult<String> {
|
||||||
let text = remove_slurs(text, slur_regex);
|
let text = remove_slurs(text, slur_regex);
|
||||||
|
let text = clean_urls_in_text(&text);
|
||||||
|
|
||||||
markdown_check_for_blocked_urls(&text, url_blocklist)?;
|
markdown_check_for_blocked_urls(&text, url_blocklist)?;
|
||||||
|
|
||||||
if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages {
|
if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages {
|
||||||
let (text, links) = markdown_rewrite_image_links(text);
|
let (text, links) = markdown_rewrite_image_links(text);
|
||||||
|
RemoteImage::create(&mut context.pool(), links.clone()).await?;
|
||||||
|
|
||||||
// Create images and image detail rows
|
// Create images and image detail rows
|
||||||
for link in links {
|
for link in links {
|
||||||
|
@ -961,7 +1064,7 @@ pub async fn process_markdown(
|
||||||
let proxied =
|
let proxied =
|
||||||
build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
||||||
let details_form = details.build_image_details_form(&proxied);
|
let details_form = details.build_image_details_form(&proxied);
|
||||||
RemoteImage::create(&mut context.pool(), &details_form).await?;
|
ImageDetails::create(&mut context.pool(), &details_form).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(text)
|
Ok(text)
|
||||||
|
@ -997,13 +1100,15 @@ async fn proxy_image_link_internal(
|
||||||
if link.domain() == Some(&context.settings().hostname) {
|
if link.domain() == Some(&context.settings().hostname) {
|
||||||
Ok(link.into())
|
Ok(link.into())
|
||||||
} else if image_mode == PictrsImageMode::ProxyAllImages {
|
} else if image_mode == PictrsImageMode::ProxyAllImages {
|
||||||
|
RemoteImage::create(&mut context.pool(), vec![link.clone()]).await?;
|
||||||
|
|
||||||
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
||||||
// This should fail softly, since pictrs might not even be running
|
// This should fail softly, since pictrs might not even be running
|
||||||
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
|
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
|
||||||
|
|
||||||
if let Ok(details) = details_res {
|
if let Ok(details) = details_res {
|
||||||
let details_form = details.build_image_details_form(&proxied);
|
let details_form = details.build_image_details_form(&proxied);
|
||||||
RemoteImage::create(&mut context.pool(), &details_form).await?;
|
ImageDetails::create(&mut context.pool(), &details_form).await?;
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(proxied.into())
|
Ok(proxied.into())
|
||||||
|
@ -1014,7 +1119,7 @@ async fn proxy_image_link_internal(
|
||||||
|
|
||||||
/// Rewrite a link to go through `/api/v3/image_proxy` endpoint. This is only for remote urls and
|
/// Rewrite a link to go through `/api/v3/image_proxy` endpoint. This is only for remote urls and
|
||||||
/// if image_proxy setting is enabled.
|
/// if image_proxy setting is enabled.
|
||||||
pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
|
pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
|
||||||
proxy_image_link_internal(
|
proxy_image_link_internal(
|
||||||
link,
|
link,
|
||||||
context.settings().pictrs_config()?.image_mode(),
|
context.settings().pictrs_config()?.image_mode(),
|
||||||
|
@ -1071,11 +1176,20 @@ fn build_proxied_image_url(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use lemmy_db_schema::source::{
|
||||||
|
comment::CommentInsertForm,
|
||||||
|
community::CommunityInsertForm,
|
||||||
|
person::PersonInsertForm,
|
||||||
|
post::PostInsertForm,
|
||||||
|
};
|
||||||
|
use lemmy_db_views_moderator::structs::{
|
||||||
|
ModRemoveCommentView,
|
||||||
|
ModRemovePostView,
|
||||||
|
ModlogListParams,
|
||||||
|
};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
|
@ -1097,48 +1211,42 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_limit_ban_term() {
|
fn test_limit_ban_term() -> LemmyResult<()> {
|
||||||
// Ban expires in past, should throw error
|
// Ban expires in past, should throw error
|
||||||
assert!(limit_expire_time(Utc::now() - Days::new(5)).is_err());
|
assert!(limit_expire_time(Utc::now() - Days::new(5)).is_err());
|
||||||
|
|
||||||
// Legitimate ban term, return same value
|
// Legitimate ban term, return same value
|
||||||
let fourteen_days = Utc::now() + Days::new(14);
|
let fourteen_days = Utc::now() + Days::new(14);
|
||||||
assert_eq!(
|
assert_eq!(limit_expire_time(fourteen_days)?, Some(fourteen_days));
|
||||||
limit_expire_time(fourteen_days).unwrap(),
|
|
||||||
Some(fourteen_days)
|
|
||||||
);
|
|
||||||
let nine_years = Utc::now() + Days::new(365 * 9);
|
let nine_years = Utc::now() + Days::new(365 * 9);
|
||||||
assert_eq!(limit_expire_time(nine_years).unwrap(), Some(nine_years));
|
assert_eq!(limit_expire_time(nine_years)?, Some(nine_years));
|
||||||
|
|
||||||
// Too long ban term, changes to None (permanent ban)
|
// Too long ban term, changes to None (permanent ban)
|
||||||
assert_eq!(
|
assert_eq!(limit_expire_time(Utc::now() + Days::new(365 * 11))?, None);
|
||||||
limit_expire_time(Utc::now() + Days::new(365 * 11)).unwrap(),
|
|
||||||
None
|
Ok(())
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_proxy_image_link() {
|
async fn test_proxy_image_link() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
|
|
||||||
// image from local domain is unchanged
|
// image from local domain is unchanged
|
||||||
let local_url = Url::parse("http://lemmy-alpha/image.png").unwrap();
|
let local_url = Url::parse("http://lemmy-alpha/image.png")?;
|
||||||
let proxied =
|
let proxied =
|
||||||
proxy_image_link_internal(local_url.clone(), PictrsImageMode::ProxyAllImages, &context)
|
proxy_image_link_internal(local_url.clone(), PictrsImageMode::ProxyAllImages, &context)
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
assert_eq!(&local_url, proxied.inner());
|
assert_eq!(&local_url, proxied.inner());
|
||||||
|
|
||||||
// image from remote domain is proxied
|
// image from remote domain is proxied
|
||||||
let remote_image = Url::parse("http://lemmy-beta/image.png").unwrap();
|
let remote_image = Url::parse("http://lemmy-beta/image.png")?;
|
||||||
let proxied = proxy_image_link_internal(
|
let proxied = proxy_image_link_internal(
|
||||||
remote_image.clone(),
|
remote_image.clone(),
|
||||||
PictrsImageMode::ProxyAllImages,
|
PictrsImageMode::ProxyAllImages,
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
|
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
|
||||||
proxied.as_str()
|
proxied.as_str()
|
||||||
|
@ -1149,7 +1257,161 @@ mod tests {
|
||||||
assert!(
|
assert!(
|
||||||
RemoteImage::validate(&mut context.pool(), remote_image.into())
|
RemoteImage::validate(&mut context.pool(), remote_image.into())
|
||||||
.await
|
.await
|
||||||
.is_err()
|
.is_ok()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_mod_remove_or_restore_data() -> LemmyResult<()> {
|
||||||
|
let context = LemmyContext::init_test_context().await;
|
||||||
|
let pool = &mut context.pool();
|
||||||
|
|
||||||
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
|
let new_mod = PersonInsertForm::test_form(inserted_instance.id, "modder");
|
||||||
|
let inserted_mod = Person::create(pool, &new_mod).await?;
|
||||||
|
|
||||||
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "chrimbus");
|
||||||
|
let inserted_person = Person::create(pool, &new_person).await?;
|
||||||
|
|
||||||
|
let new_community = CommunityInsertForm::new(
|
||||||
|
inserted_instance.id,
|
||||||
|
"mod_community crepes".to_string(),
|
||||||
|
"nada".to_owned(),
|
||||||
|
"pubkey".to_string(),
|
||||||
|
);
|
||||||
|
let inserted_community = Community::create(pool, &new_community).await?;
|
||||||
|
|
||||||
|
let post_form_1 = PostInsertForm::new(
|
||||||
|
"A test post tubular".into(),
|
||||||
|
inserted_person.id,
|
||||||
|
inserted_community.id,
|
||||||
|
);
|
||||||
|
let inserted_post_1 = Post::create(pool, &post_form_1).await?;
|
||||||
|
|
||||||
|
let post_form_2 = PostInsertForm::new(
|
||||||
|
"A test post radical".into(),
|
||||||
|
inserted_person.id,
|
||||||
|
inserted_community.id,
|
||||||
|
);
|
||||||
|
let inserted_post_2 = Post::create(pool, &post_form_2).await?;
|
||||||
|
|
||||||
|
let comment_form_1 = CommentInsertForm::new(
|
||||||
|
inserted_person.id,
|
||||||
|
inserted_post_1.id,
|
||||||
|
"A test comment tubular".into(),
|
||||||
|
);
|
||||||
|
let _inserted_comment_1 = Comment::create(pool, &comment_form_1, None).await?;
|
||||||
|
|
||||||
|
let comment_form_2 = CommentInsertForm::new(
|
||||||
|
inserted_person.id,
|
||||||
|
inserted_post_2.id,
|
||||||
|
"A test comment radical".into(),
|
||||||
|
);
|
||||||
|
let _inserted_comment_2 = Comment::create(pool, &comment_form_2, None).await?;
|
||||||
|
|
||||||
|
// Remove the user data
|
||||||
|
remove_or_restore_user_data(
|
||||||
|
inserted_mod.id,
|
||||||
|
inserted_person.id,
|
||||||
|
true,
|
||||||
|
&Some("a remove reason".to_string()),
|
||||||
|
&context,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Verify that their posts and comments are removed.
|
||||||
|
let params = ModlogListParams {
|
||||||
|
community_id: None,
|
||||||
|
mod_person_id: None,
|
||||||
|
other_person_id: None,
|
||||||
|
post_id: None,
|
||||||
|
comment_id: None,
|
||||||
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
hide_modlog_names: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
let post_modlog = ModRemovePostView::list(pool, params).await?;
|
||||||
|
assert_eq!(2, post_modlog.len());
|
||||||
|
|
||||||
|
let mod_removed_posts = post_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.mod_remove_post.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
assert_eq!(vec![true, true], mod_removed_posts);
|
||||||
|
|
||||||
|
let removed_posts = post_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.post.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
assert_eq!(vec![true, true], removed_posts);
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
let comment_modlog = ModRemoveCommentView::list(pool, params).await?;
|
||||||
|
assert_eq!(2, comment_modlog.len());
|
||||||
|
|
||||||
|
let mod_removed_comments = comment_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.mod_remove_comment.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
assert_eq!(vec![true, true], mod_removed_comments);
|
||||||
|
|
||||||
|
let removed_comments = comment_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.comment.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
assert_eq!(vec![true, true], removed_comments);
|
||||||
|
|
||||||
|
// Now restore the content, and make sure it got appended
|
||||||
|
remove_or_restore_user_data(
|
||||||
|
inserted_mod.id,
|
||||||
|
inserted_person.id,
|
||||||
|
false,
|
||||||
|
&Some("a restore reason".to_string()),
|
||||||
|
&context,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
let post_modlog = ModRemovePostView::list(pool, params).await?;
|
||||||
|
assert_eq!(4, post_modlog.len());
|
||||||
|
|
||||||
|
let mod_restored_posts = post_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.mod_remove_post.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
assert_eq!(vec![false, false, true, true], mod_restored_posts);
|
||||||
|
|
||||||
|
let restored_posts = post_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.post.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
// All of these will be false, cause its the current state of the post
|
||||||
|
assert_eq!(vec![false, false, false, false], restored_posts);
|
||||||
|
|
||||||
|
// Comments
|
||||||
|
let comment_modlog = ModRemoveCommentView::list(pool, params).await?;
|
||||||
|
assert_eq!(4, comment_modlog.len());
|
||||||
|
|
||||||
|
let mod_restored_comments = comment_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.mod_remove_comment.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
assert_eq!(vec![false, false, true, true], mod_restored_comments);
|
||||||
|
|
||||||
|
let restored_comments = comment_modlog
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.comment.removed)
|
||||||
|
.collect::<Vec<bool>>();
|
||||||
|
assert_eq!(vec![false, false, false, false], restored_comments);
|
||||||
|
|
||||||
|
Instance::delete(pool, inserted_instance.id).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,12 @@ futures.workspace = true
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
moka.workspace = true
|
moka.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
webmention = "0.5.0"
|
chrono.workspace = true
|
||||||
|
webmention = "0.6.0"
|
||||||
accept-language = "3.1.0"
|
accept-language = "3.1.0"
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_with = { workspace = true }
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-shear]
|
||||||
ignored = ["futures"]
|
ignored = ["futures"]
|
||||||
|
|
|
@ -30,10 +30,9 @@ use lemmy_db_views::structs::{LocalUserView, PostView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
|
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
|
||||||
|
MAX_COMMENT_DEPTH_LIMIT,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MAX_COMMENT_DEPTH_LIMIT: usize = 100;
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn create_comment(
|
pub async fn create_comment(
|
||||||
data: Json<CreateComment>,
|
data: Json<CreateComment>,
|
||||||
|
@ -57,13 +56,17 @@ pub async fn create_comment(
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
|
||||||
|
|
||||||
let post = post_view.post;
|
let post = post_view.post;
|
||||||
let community_id = post_view.community.id;
|
let community_id = post_view.community.id;
|
||||||
|
|
||||||
check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?;
|
check_community_user_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
&post_view.community,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
check_post_deleted_or_removed(&post)?;
|
check_post_deleted_or_removed(&post)?;
|
||||||
|
|
||||||
// Check if post is locked, no new comments
|
// Check if post is locked, no new comments
|
||||||
|
@ -79,8 +82,7 @@ pub async fn create_comment(
|
||||||
Comment::read(&mut context.pool(), parent_id).await.ok()
|
Comment::read(&mut context.pool(), parent_id).await.ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
};
|
||||||
.flatten();
|
|
||||||
|
|
||||||
// If there's a parent_id, check to make sure that comment is in that post
|
// 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
|
// Strange issue where sometimes the post ID of the parent comment is incorrect
|
||||||
|
@ -91,16 +93,9 @@ pub async fn create_comment(
|
||||||
check_comment_depth(parent)?;
|
check_comment_depth(parent)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommunityLanguage::is_allowed_community_language(
|
|
||||||
&mut context.pool(),
|
|
||||||
data.language_id,
|
|
||||||
community_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// attempt to set default language if none was provided
|
// attempt to set default language if none was provided
|
||||||
let language_id = match data.language_id {
|
let language_id = match data.language_id {
|
||||||
Some(lid) => Some(lid),
|
Some(lid) => lid,
|
||||||
None => {
|
None => {
|
||||||
default_post_language(
|
default_post_language(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
|
@ -111,12 +106,13 @@ pub async fn create_comment(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let comment_form = CommentInsertForm::builder()
|
CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community_id)
|
||||||
.content(content.clone())
|
.await?;
|
||||||
.post_id(data.post_id)
|
|
||||||
.creator_id(local_user_view.person.id)
|
let comment_form = CommentInsertForm {
|
||||||
.language_id(language_id)
|
language_id: Some(language_id),
|
||||||
.build();
|
..CommentInsertForm::new(local_user_view.person.id, data.post_id, content.clone())
|
||||||
|
};
|
||||||
|
|
||||||
// Create the comment
|
// Create the comment
|
||||||
let parent_path = parent_opt.clone().map(|t| t.path);
|
let parent_path = parent_opt.clone().map(|t| t.path);
|
||||||
|
@ -141,7 +137,6 @@ pub async fn create_comment(
|
||||||
// You like your own comment by default
|
// You like your own comment by default
|
||||||
let like_form = CommentLikeForm {
|
let like_form = CommentLikeForm {
|
||||||
comment_id: inserted_comment.id,
|
comment_id: inserted_comment.id,
|
||||||
post_id: post.id,
|
|
||||||
person_id: local_user_view.person.id,
|
person_id: local_user_view.person.id,
|
||||||
score: 1,
|
score: 1,
|
||||||
};
|
};
|
||||||
|
@ -153,8 +148,7 @@ pub async fn create_comment(
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::CreateComment(inserted_comment.clone()),
|
SendActivityData::CreateComment(inserted_comment.clone()),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Update the read comments, so your own new comment doesn't appear as a +1 unread
|
// Update the read comments, so your own new comment doesn't appear as a +1 unread
|
||||||
update_read_comments(
|
update_read_comments(
|
||||||
|
|
|
@ -26,8 +26,7 @@ pub async fn delete_comment(
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
// Dont delete it if its already been deleted.
|
// Dont delete it if its already been deleted.
|
||||||
if orig_comment.comment.deleted == data.deleted {
|
if orig_comment.comment.deleted == data.deleted {
|
||||||
|
@ -36,7 +35,7 @@ pub async fn delete_comment(
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
&orig_comment.community,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -77,8 +76,7 @@ pub async fn delete_comment(
|
||||||
orig_comment.community,
|
orig_comment.community,
|
||||||
),
|
),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
build_comment_response(
|
build_comment_response(
|
||||||
|
|
|
@ -31,12 +31,11 @@ pub async fn remove_comment(
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
&orig_comment.community,
|
||||||
false,
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -100,8 +99,7 @@ pub async fn remove_comment(
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
build_comment_response(
|
build_comment_response(
|
||||||
|
|
|
@ -41,12 +41,11 @@ pub async fn update_comment(
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(&local_user_view.local_user),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?
|
.await?;
|
||||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
&orig_comment.community,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -56,13 +55,14 @@ pub async fn update_comment(
|
||||||
Err(LemmyErrorType::NoCommentEditAllowed)?
|
Err(LemmyErrorType::NoCommentEditAllowed)?
|
||||||
}
|
}
|
||||||
|
|
||||||
let language_id = data.language_id;
|
if let Some(language_id) = data.language_id {
|
||||||
CommunityLanguage::is_allowed_community_language(
|
CommunityLanguage::is_allowed_community_language(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
language_id,
|
language_id,
|
||||||
orig_comment.community.id,
|
orig_comment.community.id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
|
@ -98,8 +98,7 @@ pub async fn update_comment(
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::UpdateComment(updated_comment.clone()),
|
SendActivityData::UpdateComment(updated_comment.clone()),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
build_comment_response(
|
build_comment_response(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::check_community_visibility_allowed;
|
||||||
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
|
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
|
@ -8,7 +9,6 @@ use lemmy_api_common::{
|
||||||
generate_followers_url,
|
generate_followers_url,
|
||||||
generate_inbox_url,
|
generate_inbox_url,
|
||||||
generate_local_apub_endpoint,
|
generate_local_apub_endpoint,
|
||||||
generate_shared_inbox_url,
|
|
||||||
get_url_blocklist,
|
get_url_blocklist,
|
||||||
is_admin,
|
is_admin,
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
|
@ -24,6 +24,7 @@ use lemmy_db_schema::{
|
||||||
Community,
|
Community,
|
||||||
CommunityFollower,
|
CommunityFollower,
|
||||||
CommunityFollowerForm,
|
CommunityFollowerForm,
|
||||||
|
CommunityFollowerState,
|
||||||
CommunityInsertForm,
|
CommunityInsertForm,
|
||||||
CommunityModerator,
|
CommunityModerator,
|
||||||
CommunityModeratorForm,
|
CommunityModeratorForm,
|
||||||
|
@ -37,7 +38,11 @@ use lemmy_utils::{
|
||||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
utils::{
|
utils::{
|
||||||
slurs::check_slurs,
|
slurs::check_slurs,
|
||||||
validation::{is_valid_actor_name, is_valid_body_field},
|
validation::{
|
||||||
|
is_valid_actor_name,
|
||||||
|
is_valid_body_field,
|
||||||
|
site_or_community_description_length_check,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,9 +52,7 @@ pub async fn create_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CommunityResponse>> {
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
let local_site = site_view.local_site;
|
let local_site = site_view.local_site;
|
||||||
|
|
||||||
if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
|
if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
|
||||||
|
@ -60,8 +63,18 @@ pub async fn create_community(
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
check_slurs(&data.name, &slur_regex)?;
|
check_slurs(&data.name, &slur_regex)?;
|
||||||
check_slurs(&data.title, &slur_regex)?;
|
check_slurs(&data.title, &slur_regex)?;
|
||||||
let description =
|
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
|
||||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
|
||||||
|
// Ensure that the sidebar has fewer than the max num characters...
|
||||||
|
if let Some(sidebar) = &sidebar {
|
||||||
|
is_valid_body_field(sidebar, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let description = data.description.clone();
|
||||||
|
if let Some(desc) = &description {
|
||||||
|
site_or_community_description_length_check(desc)?;
|
||||||
|
check_slurs(desc, &slur_regex)?;
|
||||||
|
}
|
||||||
|
|
||||||
let icon = diesel_url_create(data.icon.as_deref())?;
|
let icon = diesel_url_create(data.icon.as_deref())?;
|
||||||
let icon = proxy_image_link_api(icon, &context).await?;
|
let icon = proxy_image_link_api(icon, &context).await?;
|
||||||
|
@ -75,6 +88,8 @@ pub async fn create_community(
|
||||||
is_valid_body_field(desc, false)?;
|
is_valid_body_field(desc, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
check_community_visibility_allowed(data.visibility, &local_user_view)?;
|
||||||
|
|
||||||
// Double check for duplicate community actor_ids
|
// Double check for duplicate community actor_ids
|
||||||
let community_actor_id = generate_local_apub_endpoint(
|
let community_actor_id = generate_local_apub_endpoint(
|
||||||
EndpointType::Community,
|
EndpointType::Community,
|
||||||
|
@ -90,23 +105,25 @@ pub async fn create_community(
|
||||||
// When you create a community, make sure the user becomes a moderator and a follower
|
// When you create a community, make sure the user becomes a moderator and a follower
|
||||||
let keypair = generate_actor_keypair()?;
|
let keypair = generate_actor_keypair()?;
|
||||||
|
|
||||||
let community_form = CommunityInsertForm::builder()
|
let community_form = CommunityInsertForm {
|
||||||
.name(data.name.clone())
|
sidebar,
|
||||||
.title(data.title.clone())
|
description,
|
||||||
.description(description)
|
icon,
|
||||||
.icon(icon)
|
banner,
|
||||||
.banner(banner)
|
nsfw: data.nsfw,
|
||||||
.nsfw(data.nsfw)
|
actor_id: Some(community_actor_id.clone()),
|
||||||
.actor_id(Some(community_actor_id.clone()))
|
private_key: Some(keypair.private_key),
|
||||||
.private_key(Some(keypair.private_key))
|
followers_url: Some(generate_followers_url(&community_actor_id)?),
|
||||||
.public_key(keypair.public_key)
|
inbox_url: Some(generate_inbox_url()?),
|
||||||
.followers_url(Some(generate_followers_url(&community_actor_id)?))
|
posting_restricted_to_mods: data.posting_restricted_to_mods,
|
||||||
.inbox_url(Some(generate_inbox_url(&community_actor_id)?))
|
visibility: data.visibility,
|
||||||
.shared_inbox_url(Some(generate_shared_inbox_url(context.settings())?))
|
..CommunityInsertForm::new(
|
||||||
.posting_restricted_to_mods(data.posting_restricted_to_mods)
|
site_view.site.instance_id,
|
||||||
.instance_id(site_view.site.instance_id)
|
data.name.clone(),
|
||||||
.visibility(data.visibility)
|
data.title.clone(),
|
||||||
.build();
|
keypair.public_key,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let inserted_community = Community::create(&mut context.pool(), &community_form)
|
let inserted_community = Community::create(&mut context.pool(), &community_form)
|
||||||
.await
|
.await
|
||||||
|
@ -126,7 +143,8 @@ pub async fn create_community(
|
||||||
let community_follower_form = CommunityFollowerForm {
|
let community_follower_form = CommunityFollowerForm {
|
||||||
community_id: inserted_community.id,
|
community_id: inserted_community.id,
|
||||||
person_id: local_user_view.person.id,
|
person_id: local_user_view.person.id,
|
||||||
pending: false,
|
state: Some(CommunityFollowerState::Accepted),
|
||||||
|
approver_id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
|
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
|
||||||
|
|
|
@ -22,13 +22,13 @@ pub async fn delete_community(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CommunityResponse>> {
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
// Fetch the community mods
|
// Fetch the community mods
|
||||||
let community_id = data.community_id;
|
|
||||||
let community_mods =
|
let community_mods =
|
||||||
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
CommunityModeratorView::for_community(&mut context.pool(), data.community_id).await?;
|
||||||
|
|
||||||
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
community_id,
|
&community,
|
||||||
true,
|
true,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -54,8 +54,7 @@ pub async fn delete_community(
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::DeleteCommunity(local_user_view.person.clone(), community, data.deleted),
|
SendActivityData::DeleteCommunity(local_user_view.person.clone(), community, data.deleted),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
build_community_response(&context, local_user_view, community_id).await
|
build_community_response(&context, local_user_view, community_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_db_views_actor::community_view::CommunityQuery;
|
use lemmy_db_views_actor::community_view::CommunityQuery;
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_communities(
|
pub async fn list_communities(
|
||||||
|
@ -14,9 +14,7 @@ pub async fn list_communities(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: Option<LocalUserView>,
|
local_user_view: Option<LocalUserView>,
|
||||||
) -> LemmyResult<Json<ListCommunitiesResponse>> {
|
) -> LemmyResult<Json<ListCommunitiesResponse>> {
|
||||||
let local_site = SiteView::read_local(&mut context.pool())
|
let local_site = SiteView::read_local(&mut context.pool()).await?;
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
|
||||||
let is_admin = local_user_view
|
let is_admin = local_user_view
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|luv| is_admin(luv).is_ok())
|
.map(|luv| is_admin(luv).is_ok())
|
||||||
|
|
|
@ -1,5 +1,22 @@
|
||||||
|
use lemmy_api_common::utils::is_admin;
|
||||||
|
use lemmy_db_schema::CommunityVisibility;
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
pub mod create;
|
pub mod create;
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod remove;
|
pub mod remove;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
|
||||||
|
/// For now only admins can make communities private, in order to prevent abuse.
|
||||||
|
/// Need to implement admin approval for new communities to get rid of this.
|
||||||
|
fn check_community_visibility_allowed(
|
||||||
|
visibility: Option<CommunityVisibility>,
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if visibility == Some(lemmy_db_schema::CommunityVisibility::Private) {
|
||||||
|
is_admin(local_user_view)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -23,9 +23,10 @@ pub async fn remove_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CommunityResponse>> {
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
data.community_id,
|
&community,
|
||||||
true,
|
true,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -65,8 +66,7 @@ pub async fn remove_community(
|
||||||
removed: data.removed,
|
removed: data.removed,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
build_community_response(&context, local_user_view, community_id).await
|
build_community_response(&context, local_user_view, community_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::check_community_visibility_allowed;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
|
@ -41,19 +42,20 @@ pub async fn update_community(
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
check_slurs_opt(&data.title, &slur_regex)?;
|
check_slurs_opt(&data.title, &slur_regex)?;
|
||||||
|
|
||||||
let description = diesel_string_update(
|
let sidebar = diesel_string_update(
|
||||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context)
|
process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
|
||||||
.await?
|
.await?
|
||||||
.as_deref(),
|
.as_deref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(Some(desc)) = &description {
|
if let Some(Some(sidebar)) = &sidebar {
|
||||||
is_valid_body_field(desc, false)?;
|
is_valid_body_field(sidebar, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_community = Community::read(&mut context.pool(), data.community_id)
|
check_community_visibility_allowed(data.visibility, &local_user_view)?;
|
||||||
.await?
|
let description = diesel_string_update(data.description.as_deref());
|
||||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
|
let old_community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
|
|
||||||
let icon = diesel_url_update(data.icon.as_deref())?;
|
let icon = diesel_url_update(data.icon.as_deref())?;
|
||||||
replace_image(&icon, &old_community.icon, &context).await?;
|
replace_image(&icon, &old_community.icon, &context).await?;
|
||||||
|
@ -66,7 +68,7 @@ pub async fn update_community(
|
||||||
// Verify its a mod (only mods can edit it)
|
// Verify its a mod (only mods can edit it)
|
||||||
check_community_mod_action(
|
check_community_mod_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
data.community_id,
|
&old_community,
|
||||||
false,
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -86,6 +88,7 @@ pub async fn update_community(
|
||||||
|
|
||||||
let community_form = CommunityUpdateForm {
|
let community_form = CommunityUpdateForm {
|
||||||
title: data.title.clone(),
|
title: data.title.clone(),
|
||||||
|
sidebar,
|
||||||
description,
|
description,
|
||||||
icon,
|
icon,
|
||||||
banner,
|
banner,
|
||||||
|
@ -104,8 +107,7 @@ pub async fn update_community(
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
|
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
build_community_response(&context, local_user_view, community_id).await
|
build_community_response(&context, local_user_view, community_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,12 @@ use lemmy_api_common::{
|
||||||
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
|
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
|
||||||
utils::is_admin,
|
utils::is_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::{
|
||||||
custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
|
source::{
|
||||||
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
|
custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
|
||||||
local_site::LocalSite,
|
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
|
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
@ -19,24 +21,20 @@ pub async fn create_custom_emoji(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CustomEmojiResponse>> {
|
) -> LemmyResult<Json<CustomEmojiResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let emoji_form = CustomEmojiInsertForm::builder()
|
let emoji_form = CustomEmojiInsertForm::new(
|
||||||
.local_site_id(local_site.id)
|
data.shortcode.to_lowercase().trim().to_string(),
|
||||||
.shortcode(data.shortcode.to_lowercase().trim().to_string())
|
data.clone().image_url.into(),
|
||||||
.alt_text(data.alt_text.to_string())
|
data.alt_text.to_string(),
|
||||||
.category(data.category.to_string())
|
data.category.to_string(),
|
||||||
.image_url(data.clone().image_url.into())
|
);
|
||||||
.build();
|
|
||||||
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
|
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
|
||||||
let mut keywords = vec![];
|
let mut keywords = vec![];
|
||||||
for keyword in &data.keywords {
|
for keyword in &data.keywords {
|
||||||
let keyword_form = CustomEmojiKeywordInsertForm::builder()
|
let keyword_form =
|
||||||
.custom_emoji_id(emoji.id)
|
CustomEmojiKeywordInsertForm::new(emoji.id, keyword.to_lowercase().trim().to_string());
|
||||||
.keyword(keyword.to_lowercase().trim().to_string())
|
|
||||||
.build();
|
|
||||||
keywords.push(keyword_form);
|
keywords.push(keyword_form);
|
||||||
}
|
}
|
||||||
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
|
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
utils::is_admin,
|
utils::is_admin,
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::custom_emoji::CustomEmoji;
|
use lemmy_db_schema::{source::custom_emoji::CustomEmoji, traits::Crud};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
|
25
crates/api_crud/src/custom_emoji/list.rs
Normal file
25
crates/api_crud/src/custom_emoji/list.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use actix_web::web::{Data, Json, Query};
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
custom_emoji::{ListCustomEmojis, ListCustomEmojisResponse},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
|
||||||
|
use lemmy_utils::error::LemmyError;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn list_custom_emojis(
|
||||||
|
data: Query<ListCustomEmojis>,
|
||||||
|
local_user_view: Option<LocalUserView>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> Result<Json<ListCustomEmojisResponse>, LemmyError> {
|
||||||
|
let custom_emojis = CustomEmojiView::list(
|
||||||
|
&mut context.pool(),
|
||||||
|
&data.category,
|
||||||
|
data.page,
|
||||||
|
data.limit,
|
||||||
|
data.ignore_page_limits.unwrap_or(false),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(ListCustomEmojisResponse { custom_emojis }))
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod create;
|
pub mod create;
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
|
pub mod list;
|
||||||
pub mod update;
|
pub mod update;
|
||||||
|
|
|
@ -5,10 +5,12 @@ use lemmy_api_common::{
|
||||||
custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
|
custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
|
||||||
utils::is_admin,
|
utils::is_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::{
|
||||||
custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
|
source::{
|
||||||
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
|
custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
|
||||||
local_site::LocalSite,
|
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
|
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
@ -19,24 +21,20 @@ pub async fn update_custom_emoji(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<CustomEmojiResponse>> {
|
) -> LemmyResult<Json<CustomEmojiResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let emoji_form = CustomEmojiUpdateForm::builder()
|
let emoji_form = CustomEmojiUpdateForm::new(
|
||||||
.local_site_id(local_site.id)
|
data.clone().image_url.into(),
|
||||||
.alt_text(data.alt_text.to_string())
|
data.alt_text.to_string(),
|
||||||
.category(data.category.to_string())
|
data.category.to_string(),
|
||||||
.image_url(data.clone().image_url.into())
|
);
|
||||||
.build();
|
|
||||||
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
|
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
|
||||||
CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
|
CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
|
||||||
let mut keywords = vec![];
|
let mut keywords = vec![];
|
||||||
for keyword in &data.keywords {
|
for keyword in &data.keywords {
|
||||||
let keyword_form = CustomEmojiKeywordInsertForm::builder()
|
let keyword_form =
|
||||||
.custom_emoji_id(emoji.id)
|
CustomEmojiKeywordInsertForm::new(emoji.id, keyword.to_lowercase().trim().to_string());
|
||||||
.keyword(keyword.to_lowercase().trim().to_string())
|
|
||||||
.build();
|
|
||||||
keywords.push(keyword_form);
|
keywords.push(keyword_form);
|
||||||
}
|
}
|
||||||
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
|
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
|
pub mod oauth_provider;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
pub mod tagline;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
42
crates/api_crud/src/oauth_provider/create.rs
Normal file
42
crates/api_crud/src/oauth_provider/create.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
oauth_provider::CreateOAuthProvider,
|
||||||
|
utils::is_admin,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::oauth_provider::{OAuthProvider, OAuthProviderInsertForm},
|
||||||
|
traits::Crud,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyError;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn create_oauth_provider(
|
||||||
|
data: Json<CreateOAuthProvider>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> Result<Json<OAuthProvider>, LemmyError> {
|
||||||
|
// Make sure user is an admin
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let cloned_data = data.clone();
|
||||||
|
let oauth_provider_form = OAuthProviderInsertForm {
|
||||||
|
display_name: cloned_data.display_name,
|
||||||
|
issuer: Url::parse(&cloned_data.issuer)?.into(),
|
||||||
|
authorization_endpoint: Url::parse(&cloned_data.authorization_endpoint)?.into(),
|
||||||
|
token_endpoint: Url::parse(&cloned_data.token_endpoint)?.into(),
|
||||||
|
userinfo_endpoint: Url::parse(&cloned_data.userinfo_endpoint)?.into(),
|
||||||
|
id_claim: cloned_data.id_claim,
|
||||||
|
client_id: data.client_id.to_string(),
|
||||||
|
client_secret: data.client_secret.to_string(),
|
||||||
|
scopes: data.scopes.to_string(),
|
||||||
|
auto_verify_email: data.auto_verify_email,
|
||||||
|
account_linking_enabled: data.account_linking_enabled,
|
||||||
|
enabled: data.enabled,
|
||||||
|
};
|
||||||
|
let oauth_provider = OAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?;
|
||||||
|
Ok(Json(oauth_provider))
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue