mirror of https://github.com/LemmyNet/lemmy.git
Merge branch 'main' into markdown-link-rule
commit
650e3a71d7
|
@ -2,7 +2,7 @@
|
||||||
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- &muslrust_image "clux/muslrust:1.70.0"
|
- &rust_image "rust:1.72.1"
|
||||||
- &slow_check_paths
|
- &slow_check_paths
|
||||||
- path:
|
- path:
|
||||||
# rust source code
|
# rust source code
|
||||||
|
@ -57,15 +57,13 @@ steps:
|
||||||
|
|
||||||
cargo_fmt:
|
cargo_fmt:
|
||||||
group: format
|
group: format
|
||||||
image: *muslrust_image
|
image: rustlang/rust:nightly
|
||||||
environment:
|
environment:
|
||||||
# store cargo data in repo folder so that it gets cached between steps
|
# store cargo data in repo folder so that it gets cached between steps
|
||||||
CARGO_HOME: .cargo
|
CARGO_HOME: .cargo
|
||||||
commands:
|
commands:
|
||||||
# need make existing toolchain available
|
# need make existing toolchain available
|
||||||
- cp -n ~/.cargo . -r
|
- cargo +nightly fmt -- --check
|
||||||
- rustup toolchain install nightly-2023-07-10 --no-self-update --profile minimal --component rustfmt
|
|
||||||
- cargo +nightly-2023-07-10 fmt -- --check
|
|
||||||
|
|
||||||
restore-cache:
|
restore-cache:
|
||||||
image: meltwater/drone-cache:v1
|
image: meltwater/drone-cache:v1
|
||||||
|
@ -93,7 +91,7 @@ steps:
|
||||||
|
|
||||||
# make sure api builds with default features (used by other crates relying on lemmy api)
|
# make sure api builds with default features (used by other crates relying on lemmy api)
|
||||||
check_api_common_default_features:
|
check_api_common_default_features:
|
||||||
image: *muslrust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo
|
CARGO_HOME: .cargo
|
||||||
commands:
|
commands:
|
||||||
|
@ -101,7 +99,7 @@ steps:
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
lemmy_api_common_doesnt_depend_on_diesel:
|
lemmy_api_common_doesnt_depend_on_diesel:
|
||||||
image: *muslrust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo
|
CARGO_HOME: .cargo
|
||||||
commands:
|
commands:
|
||||||
|
@ -109,7 +107,7 @@ steps:
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
lemmy_api_common_works_with_wasm:
|
lemmy_api_common_works_with_wasm:
|
||||||
image: *muslrust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo
|
CARGO_HOME: .cargo
|
||||||
commands:
|
commands:
|
||||||
|
@ -118,7 +116,7 @@ steps:
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
check_defaults_hjson_updated:
|
check_defaults_hjson_updated:
|
||||||
image: *muslrust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo
|
CARGO_HOME: .cargo
|
||||||
commands:
|
commands:
|
||||||
|
@ -149,7 +147,7 @@ steps:
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
cargo_clippy:
|
cargo_clippy:
|
||||||
image: *muslrust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo
|
CARGO_HOME: .cargo
|
||||||
commands:
|
commands:
|
||||||
|
@ -173,17 +171,17 @@ steps:
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
cargo_build:
|
cargo_build:
|
||||||
image: *muslrust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo
|
CARGO_HOME: .cargo
|
||||||
commands:
|
commands:
|
||||||
- cargo build
|
- cargo build
|
||||||
- mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server
|
- mv target/debug/lemmy_server target/lemmy_server
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
cargo_test:
|
cargo_test:
|
||||||
group: tests
|
group: tests
|
||||||
image: *muslrust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||||
RUST_BACKTRACE: "1"
|
RUST_BACKTRACE: "1"
|
||||||
|
@ -195,12 +193,12 @@ steps:
|
||||||
|
|
||||||
run_federation_tests:
|
run_federation_tests:
|
||||||
group: tests
|
group: tests
|
||||||
image: node:alpine
|
image: node:20-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"
|
||||||
commands:
|
commands:
|
||||||
- apk add bash curl postgresql-client
|
- apt update && apt install -y bash curl postgresql-client
|
||||||
- bash api_tests/prepare-drone-federation-test.sh
|
- bash api_tests/prepare-drone-federation-test.sh
|
||||||
- cd api_tests/
|
- cd api_tests/
|
||||||
- yarn
|
- yarn
|
||||||
|
@ -239,7 +237,9 @@ steps:
|
||||||
settings:
|
settings:
|
||||||
repo: dessalines/lemmy
|
repo: dessalines/lemmy
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
# TODO fix arm build: see: https://woodpecker.join-lemmy.org/repos/129/pipeline/2888/20
|
||||||
|
# platforms: linux/amd64,linux/arm64
|
||||||
|
platforms: linux/amd64
|
||||||
build_args:
|
build_args:
|
||||||
- RUST_RELEASE_MODE=release
|
- RUST_RELEASE_MODE=release
|
||||||
tag: ${CI_COMMIT_TAG}
|
tag: ${CI_COMMIT_TAG}
|
||||||
|
|
|
@ -2621,7 +2621,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_api"
|
name = "lemmy_api"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -2652,13 +2652,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_api_common"
|
name = "lemmy_api_common"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"encoding",
|
"encoding",
|
||||||
|
"enum-map",
|
||||||
"futures",
|
"futures",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
|
@ -2686,7 +2687,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_api_crud"
|
name = "lemmy_api_crud"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -2707,7 +2708,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_apub"
|
name = "lemmy_apub"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -2719,6 +2720,7 @@ dependencies = [
|
||||||
"enum_delegate",
|
"enum_delegate",
|
||||||
"futures",
|
"futures",
|
||||||
"html2md",
|
"html2md",
|
||||||
|
"html2text",
|
||||||
"http",
|
"http",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"lemmy_api_common",
|
"lemmy_api_common",
|
||||||
|
@ -2734,6 +2736,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
|
"stringreader",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"task-local-extensions",
|
"task-local-extensions",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2744,7 +2747,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_schema"
|
name = "lemmy_db_schema"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
@ -2780,7 +2783,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_views"
|
name = "lemmy_db_views"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"diesel",
|
"diesel",
|
||||||
|
@ -2798,7 +2801,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_views_actor"
|
name = "lemmy_db_views_actor"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
|
@ -2806,12 +2809,14 @@ dependencies = [
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_db_views_moderator"
|
name = "lemmy_db_views_moderator"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"diesel",
|
"diesel",
|
||||||
"diesel-async",
|
"diesel-async",
|
||||||
|
@ -2823,7 +2828,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_federate"
|
name = "lemmy_federate"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -2855,7 +2860,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_routes"
|
name = "lemmy_routes"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -2881,7 +2886,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_server"
|
name = "lemmy_server"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"actix-cors",
|
"actix-cors",
|
||||||
|
@ -2929,7 +2934,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lemmy_utils"
|
name = "lemmy_utils"
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -4952,6 +4957,12 @@ dependencies = [
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stringreader"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "913e7b03d63752f6cdd2df77da36749d82669904798fe8944b9ec3d23f159905"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
|
27
Cargo.toml
27
Cargo.toml
|
@ -1,5 +1,5 @@
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.19.0-beta.7"
|
version = "0.19.0-rc.3"
|
||||||
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"
|
||||||
|
@ -23,6 +23,8 @@ doctest = false
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = 0
|
debug = 0
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
strip = true # Automatically strip symbols from the binary.
|
||||||
|
opt-level = "z" # Optimize for size.
|
||||||
|
|
||||||
# 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.
|
||||||
|
@ -58,16 +60,16 @@ members = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lemmy_api = { version = "=0.19.0-beta.7", path = "./crates/api" }
|
lemmy_api = { version = "=0.19.0-rc.3", path = "./crates/api" }
|
||||||
lemmy_api_crud = { version = "=0.19.0-beta.7", path = "./crates/api_crud" }
|
lemmy_api_crud = { version = "=0.19.0-rc.3", path = "./crates/api_crud" }
|
||||||
lemmy_apub = { version = "=0.19.0-beta.7", path = "./crates/apub" }
|
lemmy_apub = { version = "=0.19.0-rc.3", path = "./crates/apub" }
|
||||||
lemmy_utils = { version = "=0.19.0-beta.7", path = "./crates/utils" }
|
lemmy_utils = { version = "=0.19.0-rc.3", path = "./crates/utils" }
|
||||||
lemmy_db_schema = { version = "=0.19.0-beta.7", path = "./crates/db_schema" }
|
lemmy_db_schema = { version = "=0.19.0-rc.3", path = "./crates/db_schema" }
|
||||||
lemmy_api_common = { version = "=0.19.0-beta.7", path = "./crates/api_common" }
|
lemmy_api_common = { version = "=0.19.0-rc.3", path = "./crates/api_common" }
|
||||||
lemmy_routes = { version = "=0.19.0-beta.7", path = "./crates/routes" }
|
lemmy_routes = { version = "=0.19.0-rc.3", path = "./crates/routes" }
|
||||||
lemmy_db_views = { version = "=0.19.0-beta.7", path = "./crates/db_views" }
|
lemmy_db_views = { version = "=0.19.0-rc.3", path = "./crates/db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.19.0-beta.7", path = "./crates/db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.19.0-rc.3", path = "./crates/db_views_actor" }
|
||||||
lemmy_db_views_moderator = { version = "=0.19.0-beta.7", path = "./crates/db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.19.0-rc.3", path = "./crates/db_views_moderator" }
|
||||||
activitypub_federation = { version = "0.5.0-beta.3", default-features = false, features = [
|
activitypub_federation = { version = "0.5.0-beta.3", default-features = false, features = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
] }
|
] }
|
||||||
|
@ -128,6 +130,7 @@ futures-util = "0.3.28"
|
||||||
tokio-postgres = "0.7.8"
|
tokio-postgres = "0.7.8"
|
||||||
tokio-postgres-rustls = "0.10.0"
|
tokio-postgres-rustls = "0.10.0"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
|
enum-map = "2.6"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { workspace = true }
|
lemmy_api = { workspace = true }
|
||||||
|
@ -137,7 +140,7 @@ lemmy_utils = { workspace = true }
|
||||||
lemmy_db_schema = { workspace = true }
|
lemmy_db_schema = { workspace = true }
|
||||||
lemmy_api_common = { workspace = true }
|
lemmy_api_common = { workspace = true }
|
||||||
lemmy_routes = { workspace = true }
|
lemmy_routes = { workspace = true }
|
||||||
lemmy_federate = { version = "0.19.0-beta.7", path = "crates/federate" }
|
lemmy_federate = { version = "0.19.0-rc.3", path = "crates/federate" }
|
||||||
activitypub_federation = { workspace = true }
|
activitypub_federation = { workspace = true }
|
||||||
diesel = { workspace = true }
|
diesel = { workspace = true }
|
||||||
diesel-async = { workspace = true }
|
diesel-async = { workspace = true }
|
||||||
|
|
|
@ -13,11 +13,11 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.1",
|
"@types/jest": "^29.5.1",
|
||||||
"@types/node": "^20.1.2",
|
"@types/node": "^20.8.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||||
"@typescript-eslint/parser": "^5.59.5",
|
"@typescript-eslint/parser": "^6.7.5",
|
||||||
"eslint": "^8.40.0",
|
"eslint": "^8.51.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"lemmy-js-client": "0.19.0-rc.12",
|
"lemmy-js-client": "0.19.0-rc.12",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
|
|
@ -32,9 +32,9 @@ import {
|
||||||
getReplies,
|
getReplies,
|
||||||
getUnreadCount,
|
getUnreadCount,
|
||||||
waitUntil,
|
waitUntil,
|
||||||
delay,
|
|
||||||
waitForPost,
|
waitForPost,
|
||||||
alphaUrl,
|
alphaUrl,
|
||||||
|
followCommunity,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { CommentView } from "lemmy-js-client/dist/types/CommentView";
|
import { CommentView } from "lemmy-js-client/dist/types/CommentView";
|
||||||
import { CommunityView } from "lemmy-js-client";
|
import { CommunityView } from "lemmy-js-client";
|
||||||
|
@ -500,6 +500,13 @@ test("A and G subscribe to B (center) A posts, G mentions B, it gets announced t
|
||||||
throw "Missing alpha community";
|
throw "Missing alpha community";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// follow community from beta so that it accepts the mention
|
||||||
|
let betaCommunity = await resolveCommunity(
|
||||||
|
beta,
|
||||||
|
alphaCommunity.community.actor_id,
|
||||||
|
);
|
||||||
|
await followCommunity(beta, true, betaCommunity.community!.community.id);
|
||||||
|
|
||||||
let alphaPost = await createPost(alpha, alphaCommunity.community.id);
|
let alphaPost = await createPost(alpha, alphaCommunity.community.id);
|
||||||
expect(alphaPost.post_view.community.local).toBe(true);
|
expect(alphaPost.post_view.community.local).toBe(true);
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,14 @@ import {
|
||||||
blockInstance,
|
blockInstance,
|
||||||
waitUntil,
|
waitUntil,
|
||||||
delay,
|
delay,
|
||||||
waitForPost,
|
|
||||||
alphaUrl,
|
alphaUrl,
|
||||||
|
delta,
|
||||||
|
betaAllowedInstances,
|
||||||
|
searchPostLocal,
|
||||||
|
resolveBetaCommunity,
|
||||||
|
longDelay,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { LemmyHttp } from "lemmy-js-client";
|
import { EditSite, LemmyHttp } from "lemmy-js-client";
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupLogins();
|
await setupLogins();
|
||||||
|
@ -376,3 +380,109 @@ test("User blocks instance, communities are hidden", async () => {
|
||||||
let listing_ids3 = listing3.posts.map(p => p.post.ap_id);
|
let listing_ids3 = listing3.posts.map(p => p.post.ap_id);
|
||||||
expect(listing_ids3).toContain(postRes.post_view.post.ap_id);
|
expect(listing_ids3).toContain(postRes.post_view.post.ap_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Community follower count is federated", async () => {
|
||||||
|
// Follow the beta community from alpha
|
||||||
|
let resolved = await resolveBetaCommunity(alpha);
|
||||||
|
if (!resolved.community) {
|
||||||
|
throw "Missing beta community";
|
||||||
|
}
|
||||||
|
|
||||||
|
await followCommunity(alpha, true, resolved.community.community.id);
|
||||||
|
let followed = (
|
||||||
|
await waitUntil(
|
||||||
|
() => resolveBetaCommunity(alpha),
|
||||||
|
c => c.community?.subscribed === "Subscribed",
|
||||||
|
)
|
||||||
|
).community;
|
||||||
|
|
||||||
|
// Make sure there is 1 subscriber
|
||||||
|
expect(followed?.counts.subscribers).toBe(1);
|
||||||
|
|
||||||
|
// Follow the community from gamma
|
||||||
|
resolved = await resolveBetaCommunity(gamma);
|
||||||
|
if (!resolved.community) {
|
||||||
|
throw "Missing beta community";
|
||||||
|
}
|
||||||
|
|
||||||
|
await followCommunity(gamma, true, resolved.community.community.id);
|
||||||
|
followed = (
|
||||||
|
await waitUntil(
|
||||||
|
() => resolveBetaCommunity(gamma),
|
||||||
|
c => c.community?.subscribed === "Subscribed",
|
||||||
|
)
|
||||||
|
).community;
|
||||||
|
|
||||||
|
// Make sure there are 2 subscribers
|
||||||
|
expect(followed?.counts?.subscribers).toBe(2);
|
||||||
|
|
||||||
|
// Follow the community from delta
|
||||||
|
resolved = await resolveBetaCommunity(delta);
|
||||||
|
if (!resolved.community) {
|
||||||
|
throw "Missing beta community";
|
||||||
|
}
|
||||||
|
|
||||||
|
await followCommunity(delta, true, resolved.community.community.id);
|
||||||
|
followed = (
|
||||||
|
await waitUntil(
|
||||||
|
() => resolveBetaCommunity(delta),
|
||||||
|
c => c.community?.subscribed === "Subscribed",
|
||||||
|
)
|
||||||
|
).community;
|
||||||
|
|
||||||
|
// Make sure there are 3 subscribers
|
||||||
|
expect(followed?.counts?.subscribers).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Dont receive community activities after unsubscribe", async () => {
|
||||||
|
let communityRes = await createCommunity(alpha);
|
||||||
|
expect(communityRes.community_view.community.name).toBeDefined();
|
||||||
|
expect(communityRes.community_view.counts.subscribers).toBe(1);
|
||||||
|
|
||||||
|
let betaCommunity = (
|
||||||
|
await resolveCommunity(beta, communityRes.community_view.community.actor_id)
|
||||||
|
).community;
|
||||||
|
assertCommunityFederation(betaCommunity, communityRes.community_view);
|
||||||
|
|
||||||
|
// follow alpha community from beta
|
||||||
|
await followCommunity(beta, true, betaCommunity!.community.id);
|
||||||
|
|
||||||
|
// ensure that follower count was updated
|
||||||
|
let communityRes1 = await getCommunity(
|
||||||
|
alpha,
|
||||||
|
communityRes.community_view.community.id,
|
||||||
|
);
|
||||||
|
expect(communityRes1.community_view.counts.subscribers).toBe(2);
|
||||||
|
|
||||||
|
// temporarily block alpha, so that it doesnt know about unfollow
|
||||||
|
let editSiteForm: EditSite = {};
|
||||||
|
editSiteForm.allowed_instances = ["lemmy-epsilon"];
|
||||||
|
await beta.editSite(editSiteForm);
|
||||||
|
await longDelay();
|
||||||
|
|
||||||
|
// unfollow
|
||||||
|
await followCommunity(beta, false, betaCommunity!.community.id);
|
||||||
|
|
||||||
|
// ensure that alpha still sees beta as follower
|
||||||
|
let communityRes2 = await getCommunity(
|
||||||
|
alpha,
|
||||||
|
communityRes.community_view.community.id,
|
||||||
|
);
|
||||||
|
expect(communityRes2.community_view.counts.subscribers).toBe(2);
|
||||||
|
|
||||||
|
// unblock alpha
|
||||||
|
editSiteForm.allowed_instances = betaAllowedInstances;
|
||||||
|
await beta.editSite(editSiteForm);
|
||||||
|
await longDelay();
|
||||||
|
|
||||||
|
// create a post, it shouldnt reach beta
|
||||||
|
let postRes = await createPost(
|
||||||
|
alpha,
|
||||||
|
communityRes.community_view.community.id,
|
||||||
|
);
|
||||||
|
expect(postRes.post_view.post.id).toBeDefined();
|
||||||
|
// await longDelay();
|
||||||
|
|
||||||
|
let postResBeta = searchPostLocal(beta, postRes.post_view.post);
|
||||||
|
expect((await postResBeta).posts.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
|
@ -39,8 +39,7 @@ import {
|
||||||
loginUser,
|
loginUser,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { PostView } from "lemmy-js-client/dist/types/PostView";
|
import { PostView } from "lemmy-js-client/dist/types/PostView";
|
||||||
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
import { LemmyHttp } from "lemmy-js-client";
|
||||||
import { LemmyHttp, Login } from "lemmy-js-client";
|
|
||||||
|
|
||||||
let betaCommunity: CommunityView | undefined;
|
let betaCommunity: CommunityView | undefined;
|
||||||
|
|
||||||
|
@ -426,7 +425,7 @@ test("Enforce site ban for federated user", async () => {
|
||||||
expect(alphaUserOnBeta1.person?.person.banned).toBe(true);
|
expect(alphaUserOnBeta1.person?.person.banned).toBe(true);
|
||||||
|
|
||||||
// existing alpha post should be removed on beta
|
// existing alpha post should be removed on beta
|
||||||
let searchBeta2 = await waitUntil(
|
await waitUntil(
|
||||||
() => getPost(beta, searchBeta1.post.id),
|
() => getPost(beta, searchBeta1.post.id),
|
||||||
s => s.post_view.post.removed,
|
s => s.post_view.post.removed,
|
||||||
);
|
);
|
||||||
|
@ -441,13 +440,16 @@ test("Enforce site ban for federated user", async () => {
|
||||||
expect(unBanAlpha.banned).toBe(false);
|
expect(unBanAlpha.banned).toBe(false);
|
||||||
|
|
||||||
// Login gets invalidated by ban, need to login again
|
// Login gets invalidated by ban, need to login again
|
||||||
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson?.name!);
|
if (!alphaUserPerson) {
|
||||||
|
throw "Missing alpha person";
|
||||||
|
}
|
||||||
|
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
|
||||||
alpha_user.setHeaders({
|
alpha_user.setHeaders({
|
||||||
Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "",
|
Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "",
|
||||||
});
|
});
|
||||||
// alpha makes new post in beta community, it federates
|
// alpha makes new post in beta community, it federates
|
||||||
let postRes2 = await createPost(alpha_user, betaCommunity!.community.id);
|
let postRes2 = await createPost(alpha_user, betaCommunity!.community.id);
|
||||||
let searchBeta3 = await waitForPost(beta, postRes2.post_view.post);
|
await waitForPost(beta, postRes2.post_view.post);
|
||||||
|
|
||||||
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId!);
|
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId!);
|
||||||
expect(alphaUserOnBeta2.person?.person.banned).toBe(false);
|
expect(alphaUserOnBeta2.person?.person.banned).toBe(false);
|
||||||
|
@ -554,29 +556,3 @@ test("Report a post", async () => {
|
||||||
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body);
|
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body);
|
||||||
expect(betaReport.reason).toBe(alphaReport.reason);
|
expect(betaReport.reason).toBe(alphaReport.reason);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Sanitize HTML", async () => {
|
|
||||||
let betaCommunity = (await resolveBetaCommunity(beta)).community;
|
|
||||||
if (!betaCommunity) {
|
|
||||||
throw "Missing beta community";
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = randomString(5);
|
|
||||||
let body = "<script>alert('xss');</script> hello &\"'";
|
|
||||||
let form: CreatePost = {
|
|
||||||
name,
|
|
||||||
body,
|
|
||||||
community_id: betaCommunity.community.id,
|
|
||||||
};
|
|
||||||
let post = await beta.createPost(form);
|
|
||||||
// first escaping for the api
|
|
||||||
expect(post.post_view.post.body).toBe(
|
|
||||||
"<script>alert('xss');</script> hello &"'",
|
|
||||||
);
|
|
||||||
|
|
||||||
let alphaPost = (await resolvePost(alpha, post.post_view.post)).post;
|
|
||||||
// second escaping over federation, avoid double escape of &
|
|
||||||
expect(alphaPost?.post.body).toBe(
|
|
||||||
"<script>alert('xss');</script> hello &"'",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
|
@ -84,6 +84,13 @@ export let gamma = new LemmyHttp(gammaUrl);
|
||||||
export let delta = new LemmyHttp(deltaUrl);
|
export let delta = new LemmyHttp(deltaUrl);
|
||||||
export let epsilon = new LemmyHttp(epsilonUrl);
|
export let epsilon = new LemmyHttp(epsilonUrl);
|
||||||
|
|
||||||
|
export let betaAllowedInstances = [
|
||||||
|
"lemmy-alpha",
|
||||||
|
"lemmy-gamma",
|
||||||
|
"lemmy-delta",
|
||||||
|
"lemmy-epsilon",
|
||||||
|
];
|
||||||
|
|
||||||
const password = "lemmylemmy";
|
const password = "lemmylemmy";
|
||||||
|
|
||||||
export async function setupLogins() {
|
export async function setupLogins() {
|
||||||
|
@ -150,12 +157,7 @@ export async function setupLogins() {
|
||||||
];
|
];
|
||||||
await alpha.editSite(editSiteForm);
|
await alpha.editSite(editSiteForm);
|
||||||
|
|
||||||
editSiteForm.allowed_instances = [
|
editSiteForm.allowed_instances = betaAllowedInstances;
|
||||||
"lemmy-alpha",
|
|
||||||
"lemmy-gamma",
|
|
||||||
"lemmy-delta",
|
|
||||||
"lemmy-epsilon",
|
|
||||||
];
|
|
||||||
await beta.editSite(editSiteForm);
|
await beta.editSite(editSiteForm);
|
||||||
|
|
||||||
editSiteForm.allowed_instances = [
|
editSiteForm.allowed_instances = [
|
||||||
|
|
1843
api_tests/yarn.lock
1843
api_tests/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
comment::{CommentResponse, DistinguishComment},
|
comment::{CommentResponse, DistinguishComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{check_community_ban, is_mod_or_admin},
|
utils::{check_community_mod_action, check_community_user_action},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::comment::{Comment, CommentUpdateForm},
|
source::comment::{Comment, CommentUpdateForm},
|
||||||
|
@ -19,18 +19,19 @@ pub async fn distinguish_comment(
|
||||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
) -> Result<Json<CommentResponse>, LemmyError> {
|
||||||
let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None).await?;
|
let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
orig_comment.community.id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Verify that only a mod or admin can distinguish a comment
|
// Verify that only a mod or admin can distinguish a comment
|
||||||
is_mod_or_admin(
|
check_community_mod_action(
|
||||||
&mut context.pool(),
|
&local_user_view.person,
|
||||||
local_user_view.person.id,
|
|
||||||
orig_comment.community.id,
|
orig_comment.community.id,
|
||||||
|
false,
|
||||||
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -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_community_ban, check_downvotes_enabled},
|
utils::{check_community_user_action, check_downvotes_enabled},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
|
@ -36,8 +36,8 @@ pub async fn like_comment(
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
orig_comment.community.id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentReportResponse, CreateCommentReport},
|
comment::{CommentReportResponse, CreateCommentReport},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, sanitize_html_api, send_new_report_email_to_admins},
|
utils::{check_community_user_action, send_new_report_email_to_admins},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -26,14 +26,19 @@ pub async fn create_comment_report(
|
||||||
) -> Result<Json<CommentReportResponse>, LemmyError> {
|
) -> Result<Json<CommentReportResponse>, LemmyError> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let reason = sanitize_html_api(data.reason.trim());
|
let reason = data.reason.trim().to_string();
|
||||||
check_report_reason(&reason, &local_site)?;
|
check_report_reason(&reason, &local_site)?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||||
|
|
||||||
check_community_ban(person_id, comment_view.community.id, &mut context.pool()).await?;
|
check_community_user_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
comment_view.community.id,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let report_form = CommentReportForm {
|
let report_form = CommentReportForm {
|
||||||
creator_id: person_id,
|
creator_id: person_id,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use actix_web::web::{Data, Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
comment::{ListCommentReports, ListCommentReportsResponse},
|
comment::{ListCommentReports, ListCommentReportsResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
utils::check_community_mod_action_opt,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView};
|
use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
|
@ -17,6 +18,8 @@ pub async fn list_comment_reports(
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||||
|
|
||||||
|
check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool()).await?;
|
||||||
|
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let comment_reports = CommentReportQuery {
|
let comment_reports = CommentReportQuery {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
comment::{CommentReportResponse, ResolveCommentReport},
|
comment::{CommentReportResponse, ResolveCommentReport},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::is_mod_or_admin,
|
utils::check_community_mod_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
|
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
|
||||||
use lemmy_db_views::structs::{CommentReportView, LocalUserView};
|
use lemmy_db_views::structs::{CommentReportView, LocalUserView};
|
||||||
|
@ -20,7 +20,13 @@ pub async fn resolve_comment_report(
|
||||||
let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
is_mod_or_admin(&mut context.pool(), person_id, report.community.id).await?;
|
check_community_mod_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
report.community.id,
|
||||||
|
false,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if data.resolved {
|
if data.resolved {
|
||||||
CommentReport::resolve(&mut context.pool(), report_id, person_id)
|
CommentReport::resolve(&mut context.pool(), report_id, person_id)
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
community::{AddModToCommunity, AddModToCommunityResponse},
|
community::{AddModToCommunity, AddModToCommunityResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::is_mod_or_admin,
|
utils::check_community_mod_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -26,7 +26,13 @@ pub async fn add_mod_to_community(
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
|
||||||
// Verify that only mods or admins can add mod
|
// Verify that only mods or admins can add mod
|
||||||
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?;
|
check_community_mod_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
community_id,
|
||||||
|
false,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let community = Community::read(&mut context.pool(), community_id).await?;
|
let community = Community::read(&mut context.pool(), community_id).await?;
|
||||||
if local_user_view.local_user.admin && !community.local {
|
if local_user_view.local_user.admin && !community.local {
|
||||||
Err(LemmyErrorType::NotAModerator)?
|
Err(LemmyErrorType::NotAModerator)?
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
community::{BanFromCommunity, BanFromCommunityResponse},
|
community::{BanFromCommunity, BanFromCommunityResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{is_mod_or_admin, remove_user_data_in_community, sanitize_html_api_opt},
|
utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -22,7 +22,7 @@ use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||||
utils::{time::naive_from_unix, validation::is_valid_body_field},
|
utils::validation::is_valid_body_field,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -33,13 +33,14 @@ pub async fn ban_from_community(
|
||||||
) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
|
) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
|
||||||
let banned_person_id = data.person_id;
|
let banned_person_id = data.person_id;
|
||||||
let remove_data = data.remove_data.unwrap_or(false);
|
let remove_data = data.remove_data.unwrap_or(false);
|
||||||
let expires = data.expires.map(naive_from_unix);
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
|
||||||
// Verify that only mods or admins can ban
|
// Verify that only mods or admins can ban
|
||||||
is_mod_or_admin(
|
check_community_mod_action(
|
||||||
&mut context.pool(),
|
&local_user_view.person,
|
||||||
local_user_view.person.id,
|
|
||||||
data.community_id,
|
data.community_id,
|
||||||
|
false,
|
||||||
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
is_valid_body_field(&data.reason, false)?;
|
is_valid_body_field(&data.reason, false)?;
|
||||||
|
@ -81,7 +82,7 @@ pub async fn ban_from_community(
|
||||||
mod_person_id: local_user_view.person.id,
|
mod_person_id: local_user_view.person.id,
|
||||||
other_person_id: data.person_id,
|
other_person_id: data.person_id,
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
reason: sanitize_html_api_opt(&data.reason),
|
reason: data.reason.clone(),
|
||||||
banned: Some(data.ban),
|
banned: Some(data.ban),
|
||||||
expires,
|
expires,
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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_ban, check_community_deleted_or_removed},
|
utils::check_community_user_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -32,8 +32,8 @@ pub async fn follow_community(
|
||||||
|
|
||||||
if data.follow {
|
if data.follow {
|
||||||
if community.local {
|
if community.local {
|
||||||
check_community_ban(local_user_view.person.id, community.id, &mut context.pool()).await?;
|
check_community_user_action(&local_user_view.person, community.id, &mut context.pool())
|
||||||
check_community_deleted_or_removed(community.id, &mut context.pool()).await?;
|
.await?;
|
||||||
|
|
||||||
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
|
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
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::{
|
||||||
build_response::build_community_response,
|
community::HideCommunity,
|
||||||
community::{CommunityResponse, HideCommunity},
|
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{is_admin, sanitize_html_api_opt},
|
utils::is_admin,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -22,7 +22,7 @@ pub async fn hide_community(
|
||||||
data: Json<HideCommunity>,
|
data: Json<HideCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
// Verify its a admin (only admin can hide or unhide it)
|
// Verify its a admin (only admin can hide or unhide it)
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ pub async fn hide_community(
|
||||||
let mod_hide_community_form = ModHideCommunityForm {
|
let mod_hide_community_form = ModHideCommunityForm {
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
mod_person_id: local_user_view.person.id,
|
mod_person_id: local_user_view.person.id,
|
||||||
reason: sanitize_html_api_opt(&data.reason),
|
reason: data.reason.clone(),
|
||||||
hidden: Some(data.hidden),
|
hidden: Some(data.hidden),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,5 +51,5 @@ pub async fn hide_community(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_community_response(&context, local_user_view, community_id).await
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use anyhow::Context;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
community::{GetCommunityResponse, TransferCommunity},
|
community::{GetCommunityResponse, TransferCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{is_admin, is_top_mod},
|
utils::{check_community_user_action, is_admin, is_top_mod},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -27,11 +27,12 @@ pub async fn transfer_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetCommunityResponse>, LemmyError> {
|
) -> Result<Json<GetCommunityResponse>, LemmyError> {
|
||||||
// Fetch the community mods
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
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?;
|
||||||
|
|
||||||
// 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())
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,11 +2,15 @@ use actix_web::{http::header::Header, HttpRequest};
|
||||||
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
|
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
|
||||||
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
|
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
|
||||||
use captcha::Captcha;
|
use captcha::Captcha;
|
||||||
use lemmy_api_common::utils::{local_site_to_slur_regex, AUTH_COOKIE_NAME};
|
use lemmy_api_common::{
|
||||||
|
claims::Claims,
|
||||||
|
context::LemmyContext,
|
||||||
|
utils::{check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME},
|
||||||
|
};
|
||||||
use lemmy_db_schema::source::local_site::LocalSite;
|
use lemmy_db_schema::source::local_site::LocalSite;
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||||
utils::slurs::check_slurs,
|
utils::slurs::check_slurs,
|
||||||
};
|
};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
@ -144,6 +148,20 @@ pub(crate) fn build_totp_2fa(
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
|
.with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn local_user_view_from_jwt(
|
||||||
|
jwt: &str,
|
||||||
|
context: &LemmyContext,
|
||||||
|
) -> Result<LocalUserView, LemmyError> {
|
||||||
|
let local_user_id = Claims::validate(jwt, context)
|
||||||
|
.await
|
||||||
|
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
||||||
|
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
|
||||||
|
check_user_valid(&local_user_view.person)?;
|
||||||
|
|
||||||
|
Ok(local_user_view)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{BanPerson, BanPersonResponse},
|
person::{BanPerson, BanPersonResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{is_admin, remove_user_data, sanitize_html_api_opt},
|
utils::{check_expire_time, is_admin, remove_user_data},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -18,8 +18,9 @@ use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||||
utils::{time::naive_from_unix, validation::is_valid_body_field},
|
utils::validation::is_valid_body_field,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn ban_from_site(
|
pub async fn ban_from_site(
|
||||||
data: Json<BanPerson>,
|
data: Json<BanPerson>,
|
||||||
|
@ -31,7 +32,7 @@ pub async fn ban_from_site(
|
||||||
|
|
||||||
is_valid_body_field(&data.reason, false)?;
|
is_valid_body_field(&data.reason, false)?;
|
||||||
|
|
||||||
let expires = data.expires.map(naive_from_unix);
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
|
||||||
let person = Person::update(
|
let person = Person::update(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
|
@ -61,7 +62,7 @@ pub async fn ban_from_site(
|
||||||
let form = ModBanForm {
|
let form = ModBanForm {
|
||||||
mod_person_id: local_user_view.person.id,
|
mod_person_id: local_user_view.person.id,
|
||||||
other_person_id: data.person_id,
|
other_person_id: data.person_id,
|
||||||
reason: sanitize_html_api_opt(&data.reason),
|
reason: data.reason.clone(),
|
||||||
banned: Some(data.ban),
|
banned: Some(data.ban),
|
||||||
expires,
|
expires,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
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::{LoginResponse, PasswordChangeAfterReset},
|
person::PasswordChangeAfterReset,
|
||||||
utils::password_length_check,
|
utils::password_length_check,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
|
@ -15,7 +16,7 @@ use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||||
pub async fn change_password_after_reset(
|
pub async fn change_password_after_reset(
|
||||||
data: Json<PasswordChangeAfterReset>,
|
data: Json<PasswordChangeAfterReset>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<LoginResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
// Fetch the user_id from the token
|
// Fetch the user_id from the token
|
||||||
let token = data.token.clone();
|
let token = data.token.clone();
|
||||||
let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token)
|
let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token)
|
||||||
|
@ -37,9 +38,5 @@ pub async fn change_password_after_reset(
|
||||||
|
|
||||||
LoginToken::invalidate_all(&mut context.pool(), local_user_id).await?;
|
LoginToken::invalidate_all(&mut context.pool(), local_user_id).await?;
|
||||||
|
|
||||||
Ok(Json(LoginResponse {
|
Ok(Json(SuccessResponse::default()))
|
||||||
jwt: None,
|
|
||||||
verify_email_sent: false,
|
|
||||||
registration_created: false,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,11 +44,7 @@ pub async fn login(
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
}
|
}
|
||||||
check_user_valid(
|
check_user_valid(&local_user_view.person)?;
|
||||||
local_user_view.person.banned,
|
|
||||||
local_user_view.person.ban_expires,
|
|
||||||
local_user_view.person.deleted,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Check if the user's email is verified if email verification is turned on
|
// Check if the user's email is verified if email verification is turned on
|
||||||
// However, skip checking verification if the user is an admin
|
// However, skip checking verification if the user is an admin
|
||||||
|
|
|
@ -14,4 +14,5 @@ pub mod report_count;
|
||||||
pub mod reset_password;
|
pub mod reset_password;
|
||||||
pub mod save_settings;
|
pub mod save_settings;
|
||||||
pub mod update_totp;
|
pub mod update_totp;
|
||||||
|
pub mod validate_auth;
|
||||||
pub mod verify_email;
|
pub mod verify_email;
|
||||||
|
|
|
@ -2,6 +2,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{GetReportCount, GetReportCountResponse},
|
person::{GetReportCount, GetReportCountResponse},
|
||||||
|
utils::check_community_mod_action_opt,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{
|
use lemmy_db_views::structs::{
|
||||||
CommentReportView,
|
CommentReportView,
|
||||||
|
@ -21,6 +22,8 @@ pub async fn report_count(
|
||||||
let admin = local_user_view.local_user.admin;
|
let admin = local_user_view.local_user.admin;
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
|
||||||
|
check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool()).await?;
|
||||||
|
|
||||||
let comment_reports =
|
let comment_reports =
|
||||||
CommentReportView::get_report_count(&mut context.pool(), person_id, admin, community_id)
|
CommentReportView::get_report_count(&mut context.pool(), person_id, admin, community_id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
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, PasswordResetResponse},
|
person::PasswordReset,
|
||||||
utils::send_password_reset_email,
|
utils::send_password_reset_email,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::password_reset_request::PasswordResetRequest;
|
use lemmy_db_schema::source::password_reset_request::PasswordResetRequest;
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn reset_password(
|
pub async fn reset_password(
|
||||||
data: Json<PasswordReset>,
|
data: Json<PasswordReset>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<PasswordResetResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// 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)
|
||||||
|
@ -31,5 +32,5 @@ pub async fn reset_password(
|
||||||
|
|
||||||
// Email the pure token to the user.
|
// Email the pure token to the user.
|
||||||
send_password_reset_email(&local_user_view, &mut context.pool(), context.settings()).await?;
|
send_password_reset_email(&local_user_view, &mut context.pool(), context.settings()).await?;
|
||||||
Ok(Json(PasswordResetResponse {}))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::SaveUserSettings,
|
person::SaveUserSettings,
|
||||||
utils::{sanitize_html_api_opt, send_verification_email},
|
utils::send_verification_email,
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
@ -28,13 +28,10 @@ pub async fn save_user_settings(
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
|
|
||||||
let bio = sanitize_html_api_opt(&data.bio);
|
|
||||||
let display_name = sanitize_html_api_opt(&data.display_name);
|
|
||||||
|
|
||||||
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
|
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
|
||||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||||
let bio = diesel_option_overwrite(bio);
|
let bio = diesel_option_overwrite(data.bio.clone());
|
||||||
let display_name = diesel_option_overwrite(display_name);
|
let display_name = diesel_option_overwrite(data.display_name.clone());
|
||||||
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
|
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
|
||||||
let email_deref = data.email.as_deref().map(str::to_lowercase);
|
let email_deref = data.email.as_deref().map(str::to_lowercase);
|
||||||
let email = diesel_option_overwrite(email_deref.clone());
|
let email = diesel_option_overwrite(email_deref.clone());
|
||||||
|
@ -82,7 +79,6 @@ pub async fn save_user_settings(
|
||||||
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_sort_type = data.default_sort_type;
|
||||||
let theme = sanitize_html_api_opt(&data.theme);
|
|
||||||
|
|
||||||
let person_form = PersonUpdateForm {
|
let person_form = PersonUpdateForm {
|
||||||
display_name,
|
display_name,
|
||||||
|
@ -106,7 +102,6 @@ pub async fn save_user_settings(
|
||||||
email,
|
email,
|
||||||
show_avatars: data.show_avatars,
|
show_avatars: data.show_avatars,
|
||||||
show_read_posts: data.show_read_posts,
|
show_read_posts: data.show_read_posts,
|
||||||
show_new_post_notifs: data.show_new_post_notifs,
|
|
||||||
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,
|
||||||
|
@ -115,10 +110,13 @@ pub async fn save_user_settings(
|
||||||
show_scores: data.show_scores,
|
show_scores: data.show_scores,
|
||||||
default_sort_type,
|
default_sort_type,
|
||||||
default_listing_type,
|
default_listing_type,
|
||||||
theme,
|
theme: data.theme.clone(),
|
||||||
interface_language: data.interface_language.clone(),
|
interface_language: data.interface_language.clone(),
|
||||||
open_links_in_new_tab: data.open_links_in_new_tab,
|
open_links_in_new_tab: data.open_links_in_new_tab,
|
||||||
infinite_scroll_enabled: data.infinite_scroll_enabled,
|
infinite_scroll_enabled: data.infinite_scroll_enabled,
|
||||||
|
post_listing_mode: data.post_listing_mode,
|
||||||
|
enable_keyboard_navigation: data.enable_keyboard_navigation,
|
||||||
|
enable_animated_images: data.enable_animated_images,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::{local_user_view_from_jwt, read_auth_token};
|
||||||
|
use actix_web::{
|
||||||
|
web::{Data, Json},
|
||||||
|
HttpRequest,
|
||||||
|
};
|
||||||
|
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
|
||||||
|
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||||
|
|
||||||
|
/// Returns an error message if the auth token is invalid for any reason. Necessary because other
|
||||||
|
/// endpoints silently treat any call with invalid auth as unauthenticated.
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn validate_auth(
|
||||||
|
req: HttpRequest,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
|
let jwt = read_auth_token(&req)?;
|
||||||
|
if let Some(jwt) = jwt {
|
||||||
|
local_user_view_from_jwt(&jwt, &context).await?;
|
||||||
|
} else {
|
||||||
|
Err(LemmyErrorType::NotLoggedIn)?;
|
||||||
|
}
|
||||||
|
Ok(Json(SuccessResponse::default()))
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
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::{VerifyEmail, VerifyEmailResponse},
|
person::VerifyEmail,
|
||||||
utils::send_new_applicant_email_to_admins,
|
utils::send_new_applicant_email_to_admins,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -14,12 +15,12 @@ use lemmy_db_schema::{
|
||||||
RegistrationMode,
|
RegistrationMode,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::SiteView;
|
use lemmy_db_views::structs::SiteView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
pub async fn verify_email(
|
pub async fn verify_email(
|
||||||
data: Json<VerifyEmail>,
|
data: Json<VerifyEmail>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<VerifyEmailResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
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)
|
||||||
|
@ -48,5 +49,5 @@ pub async fn verify_email(
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(VerifyEmailResponse {}))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{FeaturePost, PostResponse},
|
post::{FeaturePost, PostResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, check_community_deleted_or_removed, is_admin, is_mod_or_admin},
|
utils::{check_community_mod_action, is_admin},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -27,23 +27,15 @@ pub async fn feature_post(
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_mod_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_post.community_id,
|
orig_post.community_id,
|
||||||
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
|
|
||||||
|
|
||||||
if data.feature_type == PostFeatureType::Community {
|
if data.feature_type == PostFeatureType::Local {
|
||||||
// Verify that only the mods can feature in community
|
|
||||||
is_mod_or_admin(
|
|
||||||
&mut context.pool(),
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_post.community_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,12 +64,17 @@ pub async fn feature_post(
|
||||||
|
|
||||||
ModFeaturePost::create(&mut context.pool(), &form).await?;
|
ModFeaturePost::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::FeaturePost(post, local_user_view.person, data.featured),
|
SendActivityData::FeaturePost(post, local_user_view.person.clone(), data.featured),
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_post_response(&context, orig_post.community_id, person_id, post_id).await
|
build_post_response(
|
||||||
|
&context,
|
||||||
|
orig_post.community_id,
|
||||||
|
&local_user_view.person,
|
||||||
|
post_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{CreatePostLike, PostResponse},
|
post::{CreatePostLike, PostResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{
|
utils::{check_community_user_action, check_downvotes_enabled, mark_post_as_read},
|
||||||
check_community_ban,
|
|
||||||
check_community_deleted_or_removed,
|
|
||||||
check_downvotes_enabled,
|
|
||||||
mark_post_as_read,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -39,13 +34,12 @@ pub async fn like_post(
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let post = Post::read(&mut context.pool(), post_id).await?;
|
let post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
post.community_id,
|
post.community_id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?;
|
|
||||||
|
|
||||||
let like_form = PostLikeForm {
|
let like_form = PostLikeForm {
|
||||||
post_id: data.post_id,
|
post_id: data.post_id,
|
||||||
|
@ -83,7 +77,7 @@ pub async fn like_post(
|
||||||
build_post_response(
|
build_post_response(
|
||||||
context.deref(),
|
context.deref(),
|
||||||
post.community_id,
|
post.community_id,
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
post_id,
|
post_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{LockPost, PostResponse},
|
post::{LockPost, PostResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, check_community_deleted_or_removed, is_mod_or_admin},
|
utils::check_community_mod_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -26,21 +26,13 @@ pub async fn lock_post(
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_mod_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_post.community_id,
|
orig_post.community_id,
|
||||||
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
|
|
||||||
|
|
||||||
// Verify that only the mods can lock
|
|
||||||
is_mod_or_admin(
|
|
||||||
&mut context.pool(),
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_post.community_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Update the post
|
// Update the post
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
|
@ -63,12 +55,17 @@ pub async fn lock_post(
|
||||||
};
|
};
|
||||||
ModLockPost::create(&mut context.pool(), &form).await?;
|
ModLockPost::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::LockPost(post, local_user_view.person, data.locked),
|
SendActivityData::LockPost(post, local_user_view.person.clone(), data.locked),
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_post_response(&context, orig_post.community_id, person_id, post_id).await
|
build_post_response(
|
||||||
|
&context,
|
||||||
|
orig_post.community_id,
|
||||||
|
&local_user_view.person,
|
||||||
|
post_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,41 @@
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse};
|
||||||
context::LemmyContext,
|
use lemmy_db_schema::source::post::PostRead;
|
||||||
post::{MarkPostAsRead, PostResponse},
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
utils,
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS};
|
||||||
};
|
use std::collections::HashSet;
|
||||||
use lemmy_db_views::structs::{LocalUserView, PostView};
|
|
||||||
use lemmy_utils::error::LemmyError;
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn mark_post_as_read(
|
pub async fn mark_post_as_read(
|
||||||
data: Json<MarkPostAsRead>,
|
data: Json<MarkPostAsRead>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PostResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
let post_id = data.post_id;
|
let mut post_ids = HashSet::new();
|
||||||
|
if let Some(post_ids_) = &data.post_ids {
|
||||||
|
post_ids.extend(post_ids_.iter().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(post_id) = data.post_id {
|
||||||
|
post_ids.insert(post_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
||||||
|
Err(LemmyErrorType::TooManyItems)?;
|
||||||
|
}
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
|
|
||||||
// Mark the post as read / unread
|
// Mark the post as read / unread
|
||||||
if data.read {
|
if data.read {
|
||||||
utils::mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
PostRead::mark_as_read(&mut context.pool(), post_ids, person_id)
|
||||||
|
.await
|
||||||
|
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?;
|
||||||
} else {
|
} else {
|
||||||
utils::mark_post_as_unread(person_id, post_id, &mut context.pool()).await?;
|
PostRead::mark_as_unread(&mut context.pool(), post_ids, person_id)
|
||||||
|
.await
|
||||||
|
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch it
|
Ok(Json(SuccessResponse::default()))
|
||||||
let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false).await?;
|
|
||||||
|
|
||||||
Ok(Json(PostResponse { post_view }))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{CreatePostReport, PostReportResponse},
|
post::{CreatePostReport, PostReportResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, sanitize_html_api, send_new_report_email_to_admins},
|
utils::{check_community_user_action, send_new_report_email_to_admins},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -26,14 +26,19 @@ pub async fn create_post_report(
|
||||||
) -> Result<Json<PostReportResponse>, LemmyError> {
|
) -> Result<Json<PostReportResponse>, LemmyError> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let reason = sanitize_html_api(data.reason.trim());
|
let reason = data.reason.trim().to_string();
|
||||||
check_report_reason(&reason, &local_site)?;
|
check_report_reason(&reason, &local_site)?;
|
||||||
|
|
||||||
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).await?;
|
let post_view = PostView::read(&mut context.pool(), post_id, None, false).await?;
|
||||||
|
|
||||||
check_community_ban(person_id, post_view.community.id, &mut context.pool()).await?;
|
check_community_user_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
post_view.community.id,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let report_form = PostReportForm {
|
let report_form = PostReportForm {
|
||||||
creator_id: person_id,
|
creator_id: person_id,
|
||||||
|
|
|
@ -2,6 +2,7 @@ use actix_web::web::{Data, Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{ListPostReports, ListPostReportsResponse},
|
post::{ListPostReports, ListPostReportsResponse},
|
||||||
|
utils::check_community_mod_action_opt,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView};
|
use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
|
@ -17,6 +18,8 @@ pub async fn list_post_reports(
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||||
|
|
||||||
|
check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool()).await?;
|
||||||
|
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let post_reports = PostReportQuery {
|
let post_reports = PostReportQuery {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{PostReportResponse, ResolvePostReport},
|
post::{PostReportResponse, ResolvePostReport},
|
||||||
utils::is_mod_or_admin,
|
utils::check_community_mod_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
|
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PostReportView};
|
use lemmy_db_views::structs::{LocalUserView, PostReportView};
|
||||||
|
@ -20,7 +20,13 @@ pub async fn resolve_post_report(
|
||||||
let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
is_mod_or_admin(&mut context.pool(), person_id, report.community.id).await?;
|
check_community_mod_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
report.community.id,
|
||||||
|
false,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if data.resolved {
|
if data.resolved {
|
||||||
PostReport::resolve(&mut context.pool(), report_id, person_id)
|
PostReport::resolve(&mut context.pool(), report_id, person_id)
|
||||||
|
|
|
@ -3,7 +3,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse},
|
private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse},
|
||||||
utils::{sanitize_html_api, send_new_report_email_to_admins},
|
utils::send_new_report_email_to_admins,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -24,7 +24,7 @@ pub async fn create_pm_report(
|
||||||
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
|
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let reason = sanitize_html_api(data.reason.trim());
|
let reason = data.reason.trim().to_string();
|
||||||
check_report_reason(&reason, &local_site)?;
|
check_report_reason(&reason, &local_site)?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
|
@ -35,7 +35,7 @@ pub async fn create_pm_report(
|
||||||
creator_id: person_id,
|
creator_id: person_id,
|
||||||
private_message_id,
|
private_message_id,
|
||||||
original_pm_text: private_message.content,
|
original_pm_text: private_message.content,
|
||||||
reason: reason.clone(),
|
reason,
|
||||||
};
|
};
|
||||||
|
|
||||||
let report = PrivateMessageReport::report(&mut context.pool(), &report_form)
|
let report = PrivateMessageReport::report(&mut context.pool(), &report_form)
|
||||||
|
|
|
@ -2,13 +2,9 @@ use actix_web::web::{Data, Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{GetModlog, GetModlogResponse},
|
site::{GetModlog, GetModlogResponse},
|
||||||
utils::{check_private_instance, is_admin, is_mod_or_admin},
|
utils::{check_community_mod_action_opt, check_private_instance, is_admin},
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
newtypes::{CommunityId, PersonId},
|
|
||||||
source::local_site::LocalSite,
|
|
||||||
ModlogActionType,
|
|
||||||
};
|
};
|
||||||
|
use lemmy_db_schema::{source::local_site::LocalSite, ModlogActionType};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_moderator::structs::{
|
use lemmy_db_views_moderator::structs::{
|
||||||
AdminPurgeCommentView,
|
AdminPurgeCommentView,
|
||||||
|
@ -44,19 +40,16 @@ pub async fn get_mod_log(
|
||||||
let type_ = data.type_.unwrap_or(All);
|
let type_ = data.type_.unwrap_or(All);
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
|
||||||
let (local_person_id, is_admin) = match local_user_view {
|
let is_mod_or_admin = if let Some(local_user_view) = local_user_view {
|
||||||
Some(s) => (s.person.id, is_admin(&s).is_ok()),
|
let is_mod = community_id.is_some()
|
||||||
None => (PersonId(-1), false),
|
&& check_community_mod_action_opt(&local_user_view, community_id, &mut context.pool())
|
||||||
};
|
|
||||||
let community_id_value = match community_id {
|
|
||||||
Some(s) => s,
|
|
||||||
None => CommunityId(-1),
|
|
||||||
};
|
|
||||||
let is_mod_of_community = data.community_id.is_some()
|
|
||||||
&& is_mod_or_admin(&mut context.pool(), local_person_id, community_id_value)
|
|
||||||
.await
|
.await
|
||||||
.is_ok();
|
.is_ok();
|
||||||
let hide_modlog_names = local_site.hide_modlog_mod_names && !is_mod_of_community && !is_admin;
|
is_mod || is_admin(&local_user_view).is_ok()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let hide_modlog_names = local_site.hide_modlog_mod_names && !is_mod_or_admin;
|
||||||
|
|
||||||
let mod_person_id = if hide_modlog_names {
|
let mod_person_id = if hide_modlog_names {
|
||||||
None
|
None
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{PurgeComment, PurgeItemResponse},
|
site::PurgeComment,
|
||||||
utils::{is_admin, sanitize_html_api_opt},
|
utils::is_admin,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -19,7 +20,7 @@ pub async fn purge_comment(
|
||||||
data: Json<PurgeComment>,
|
data: Json<PurgeComment>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PurgeItemResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
@ -35,14 +36,13 @@ pub async fn purge_comment(
|
||||||
Comment::delete(&mut context.pool(), comment_id).await?;
|
Comment::delete(&mut context.pool(), comment_id).await?;
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let reason = sanitize_html_api_opt(&data.reason);
|
|
||||||
let form = AdminPurgeCommentForm {
|
let form = AdminPurgeCommentForm {
|
||||||
admin_person_id: local_user_view.person.id,
|
admin_person_id: local_user_view.person.id,
|
||||||
reason,
|
reason: data.reason.clone(),
|
||||||
post_id,
|
post_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
AdminPurgeComment::create(&mut context.pool(), &form).await?;
|
AdminPurgeComment::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
Ok(Json(PurgeItemResponse { success: true }))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::purge_image_from_pictrs,
|
request::purge_image_from_pictrs,
|
||||||
site::{PurgeCommunity, PurgeItemResponse},
|
site::PurgeCommunity,
|
||||||
utils::{is_admin, purge_image_posts_for_community, sanitize_html_api_opt},
|
utils::{is_admin, purge_image_posts_for_community},
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -20,7 +21,7 @@ pub async fn purge_community(
|
||||||
data: Json<PurgeCommunity>,
|
data: Json<PurgeCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PurgeItemResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
@ -42,13 +43,12 @@ pub async fn purge_community(
|
||||||
Community::delete(&mut context.pool(), community_id).await?;
|
Community::delete(&mut context.pool(), community_id).await?;
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let reason = sanitize_html_api_opt(&data.reason);
|
|
||||||
let form = AdminPurgeCommunityForm {
|
let form = AdminPurgeCommunityForm {
|
||||||
admin_person_id: local_user_view.person.id,
|
admin_person_id: local_user_view.person.id,
|
||||||
reason,
|
reason: data.reason.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
AdminPurgeCommunity::create(&mut context.pool(), &form).await?;
|
AdminPurgeCommunity::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
Ok(Json(PurgeItemResponse { success: true }))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::delete_image_from_pictrs,
|
request::delete_image_from_pictrs,
|
||||||
site::{PurgeItemResponse, PurgePerson},
|
site::PurgePerson,
|
||||||
utils::{is_admin, sanitize_html_api_opt},
|
utils::is_admin,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -21,7 +22,7 @@ pub async fn purge_person(
|
||||||
data: Json<PurgePerson>,
|
data: Json<PurgePerson>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PurgeItemResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
@ -41,13 +42,12 @@ pub async fn purge_person(
|
||||||
Person::delete(&mut context.pool(), person_id).await?;
|
Person::delete(&mut context.pool(), person_id).await?;
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let reason = sanitize_html_api_opt(&data.reason);
|
|
||||||
let form = AdminPurgePersonForm {
|
let form = AdminPurgePersonForm {
|
||||||
admin_person_id: local_user_view.person.id,
|
admin_person_id: local_user_view.person.id,
|
||||||
reason,
|
reason: data.reason.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
AdminPurgePerson::create(&mut context.pool(), &form).await?;
|
AdminPurgePerson::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
Ok(Json(PurgeItemResponse { success: true }))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::purge_image_from_pictrs,
|
request::purge_image_from_pictrs,
|
||||||
site::{PurgeItemResponse, PurgePost},
|
site::PurgePost,
|
||||||
utils::{is_admin, sanitize_html_api_opt},
|
utils::is_admin,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -20,7 +21,7 @@ pub async fn purge_post(
|
||||||
data: Json<PurgePost>,
|
data: Json<PurgePost>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PurgeItemResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
@ -43,14 +44,13 @@ pub async fn purge_post(
|
||||||
Post::delete(&mut context.pool(), post_id).await?;
|
Post::delete(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let reason = sanitize_html_api_opt(&data.reason);
|
|
||||||
let form = AdminPurgePostForm {
|
let form = AdminPurgePostForm {
|
||||||
admin_person_id: local_user_view.person.id,
|
admin_person_id: local_user_view.person.id,
|
||||||
reason,
|
reason: data.reason.clone(),
|
||||||
community_id,
|
community_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
AdminPurgePost::create(&mut context.pool(), &form).await?;
|
AdminPurgePost::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
Ok(Json(PurgeItemResponse { success: true }))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ actix-web = { workspace = true, optional = true }
|
||||||
jsonwebtoken = { version = "8.3.0", optional = true }
|
jsonwebtoken = { version = "8.3.0", optional = true }
|
||||||
# necessary for wasmt compilation
|
# necessary for wasmt compilation
|
||||||
getrandom = { version = "0.2.10", features = ["js"] }
|
getrandom = { version = "0.2.10", features = ["js"] }
|
||||||
|
enum-map = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
|
newtypes::{CommentId, CommunityId, LocalUserId, PostId},
|
||||||
source::{
|
source::{
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
|
@ -20,7 +20,10 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
|
||||||
use lemmy_db_views_actor::structs::CommunityView;
|
use lemmy_db_views_actor::structs::CommunityView;
|
||||||
use lemmy_utils::{error::LemmyError, utils::mention::MentionData};
|
use lemmy_utils::{
|
||||||
|
error::LemmyError,
|
||||||
|
utils::{markdown::markdown_to_html, mention::MentionData},
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn build_comment_response(
|
pub async fn build_comment_response(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
|
@ -41,8 +44,7 @@ pub async fn build_community_response(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
) -> Result<Json<CommunityResponse>, LemmyError> {
|
||||||
let is_mod_or_admin =
|
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
|
||||||
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id)
|
|
||||||
.await
|
.await
|
||||||
.is_ok();
|
.is_ok();
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
|
@ -64,16 +66,16 @@ pub async fn build_community_response(
|
||||||
pub async fn build_post_response(
|
pub async fn build_post_response(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
person_id: PersonId,
|
person: &Person,
|
||||||
post_id: PostId,
|
post_id: PostId,
|
||||||
) -> Result<Json<PostResponse>, LemmyError> {
|
) -> Result<Json<PostResponse>, LemmyError> {
|
||||||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person_id, community_id)
|
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id)
|
||||||
.await
|
.await
|
||||||
.is_ok();
|
.is_ok();
|
||||||
let post_view = PostView::read(
|
let post_view = PostView::read(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
post_id,
|
post_id,
|
||||||
Some(person_id),
|
Some(person.id),
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -121,10 +123,11 @@ pub async fn send_local_notifs(
|
||||||
// Send an email to those local users that have notifications on
|
// Send an email to those local users that have notifications on
|
||||||
if do_send_email {
|
if do_send_email {
|
||||||
let lang = get_interface_language(&mention_user_view);
|
let lang = get_interface_language(&mention_user_view);
|
||||||
|
let content = markdown_to_html(&comment.content);
|
||||||
send_email_to_user(
|
send_email_to_user(
|
||||||
&mention_user_view,
|
&mention_user_view,
|
||||||
&lang.notification_mentioned_by_subject(&person.name),
|
&lang.notification_mentioned_by_subject(&person.name),
|
||||||
&lang.notification_mentioned_by_body(&comment.content, &inbox_link, &person.name),
|
&lang.notification_mentioned_by_body(&content, &inbox_link, &person.name),
|
||||||
context.settings(),
|
context.settings(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -164,10 +167,11 @@ pub async fn send_local_notifs(
|
||||||
|
|
||||||
if do_send_email {
|
if do_send_email {
|
||||||
let lang = get_interface_language(&parent_user_view);
|
let lang = get_interface_language(&parent_user_view);
|
||||||
|
let content = markdown_to_html(&comment.content);
|
||||||
send_email_to_user(
|
send_email_to_user(
|
||||||
&parent_user_view,
|
&parent_user_view,
|
||||||
&lang.notification_comment_reply_subject(&person.name),
|
&lang.notification_comment_reply_subject(&person.name),
|
||||||
&lang.notification_comment_reply_body(&comment.content, &inbox_link, &person.name),
|
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
|
||||||
context.settings(),
|
context.settings(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -201,10 +205,11 @@ pub async fn send_local_notifs(
|
||||||
|
|
||||||
if do_send_email {
|
if do_send_email {
|
||||||
let lang = get_interface_language(&parent_user_view);
|
let lang = get_interface_language(&parent_user_view);
|
||||||
|
let content = markdown_to_html(&comment.content);
|
||||||
send_email_to_user(
|
send_email_to_user(
|
||||||
&parent_user_view,
|
&parent_user_view,
|
||||||
&lang.notification_post_reply_subject(&person.name),
|
&lang.notification_post_reply_subject(&person.name),
|
||||||
&lang.notification_post_reply_body(&comment.content, &inbox_link, &person.name),
|
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
|
||||||
context.settings(),
|
context.settings(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -88,7 +88,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, RateLimitConfig};
|
use lemmy_utils::rate_limit::RateLimitCell;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest_middleware::ClientBuilder;
|
use reqwest_middleware::ClientBuilder;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
@ -103,9 +103,7 @@ mod tests {
|
||||||
pool_.clone(),
|
pool_.clone(),
|
||||||
ClientBuilder::new(Client::default()).build(),
|
ClientBuilder::new(Client::default()).build(),
|
||||||
secret,
|
secret,
|
||||||
RateLimitCell::new(RateLimitConfig::builder().build())
|
RateLimitCell::with_test_config(),
|
||||||
.await
|
|
||||||
.clone(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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())
|
||||||
|
|
|
@ -154,7 +154,6 @@ pub struct EditCommunity {
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Hide a community from the main view.
|
/// Hide a community from the main view.
|
||||||
// TODO this should really be a part of edit community. And why does it contain a reason, that should be in the mod tables.
|
|
||||||
pub struct HideCommunity {
|
pub struct HideCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
|
@ -180,7 +179,6 @@ pub struct RemoveCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub removed: bool,
|
pub removed: bool,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
pub expires: Option<i64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl LemmyContext {
|
||||||
pub fn secret(&self) -> &Secret {
|
pub fn secret(&self) -> &Secret {
|
||||||
&self.secret
|
&self.secret
|
||||||
}
|
}
|
||||||
pub fn settings_updated_channel(&self) -> &RateLimitCell {
|
pub fn rate_limit_cell(&self) -> &RateLimitCell {
|
||||||
&self.rate_limit_cell
|
&self.rate_limit_cell
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,15 +39,6 @@ pub struct DeleteCustomEmoji {
|
||||||
pub id: CustomEmojiId,
|
pub id: CustomEmojiId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// The response for deleting a custom emoji.
|
|
||||||
pub struct DeleteCustomEmojiResponse {
|
|
||||||
pub id: CustomEmojiId,
|
|
||||||
pub success: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
|
|
@ -3,6 +3,7 @@ use lemmy_db_schema::{
|
||||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||||
CommentSortType,
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
|
PostListingMode,
|
||||||
SortType,
|
SortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, PostView};
|
use lemmy_db_views::structs::{CommentView, PostView};
|
||||||
|
@ -123,8 +124,11 @@ pub struct SaveUserSettings {
|
||||||
pub open_links_in_new_tab: Option<bool>,
|
pub open_links_in_new_tab: Option<bool>,
|
||||||
/// Enable infinite scroll
|
/// Enable infinite scroll
|
||||||
pub infinite_scroll_enabled: Option<bool>,
|
pub infinite_scroll_enabled: Option<bool>,
|
||||||
|
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).
|
||||||
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 should be paused
|
||||||
|
pub enable_animated_images: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
@ -331,12 +335,6 @@ pub struct DeleteAccount {
|
||||||
pub delete_content: bool,
|
pub delete_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// The response of deleting your account.
|
|
||||||
pub struct DeleteAccountResponse {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
@ -345,12 +343,6 @@ pub struct PasswordReset {
|
||||||
pub email: Sensitive<String>,
|
pub email: Sensitive<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// The response of a password reset.
|
|
||||||
pub struct PasswordResetResponse {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
@ -400,12 +392,6 @@ pub struct VerifyEmail {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// A response to verifying your email.
|
|
||||||
pub struct VerifyEmailResponse {}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
|
|
@ -135,12 +135,15 @@ pub struct RemovePost {
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Mark a post as read.
|
/// Mark a post as read.
|
||||||
pub struct MarkPostAsRead {
|
pub struct MarkPostAsRead {
|
||||||
pub post_id: PostId,
|
/// TODO: deprecated, send `post_ids` instead
|
||||||
|
pub post_id: Option<PostId>,
|
||||||
|
pub post_ids: Option<Vec<PostId>>,
|
||||||
pub read: bool,
|
pub read: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -361,14 +361,6 @@ pub struct PurgeComment {
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// The response for purged items.
|
|
||||||
pub struct PurgeItemResponse {
|
|
||||||
pub success: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
|
|
@ -6,9 +6,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
use actix_web::cookie::{Cookie, SameSite};
|
use actix_web::cookie::{Cookie, SameSite};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
||||||
|
use enum_map::{enum_map, EnumMap};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::person::is_banned,
|
|
||||||
newtypes::{CommunityId, DbUrl, PersonId, PostId},
|
newtypes::{CommunityId, DbUrl, PersonId, PostId},
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
|
@ -20,9 +20,9 @@ use lemmy_db_schema::{
|
||||||
password_reset_request::PasswordResetRequest,
|
password_reset_request::PasswordResetRequest,
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
person_block::PersonBlock,
|
person_block::PersonBlock,
|
||||||
post::{Post, PostRead, PostReadForm},
|
post::{Post, PostRead},
|
||||||
},
|
},
|
||||||
traits::{Crud, Readable},
|
traits::Crud,
|
||||||
utils::DbPool,
|
utils::DbPool,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView};
|
use lemmy_db_views::{comment_view::CommentQuery, structs::LocalUserView};
|
||||||
|
@ -33,14 +33,15 @@ use lemmy_db_views_actor::structs::{
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
email::{send_email, translations::Lang},
|
email::{send_email, translations::Lang},
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
location_info,
|
location_info,
|
||||||
rate_limit::RateLimitConfig,
|
rate_limit::{ActionType, BucketConfig},
|
||||||
settings::structs::Settings,
|
settings::structs::Settings,
|
||||||
utils::slurs::build_slur_regex,
|
utils::slurs::build_slur_regex,
|
||||||
};
|
};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rosetta_i18n::{Language, LanguageId};
|
use rosetta_i18n::{Language, LanguageId};
|
||||||
|
use std::collections::HashSet;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
@ -49,10 +50,12 @@ pub static AUTH_COOKIE_NAME: &str = "auth";
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn is_mod_or_admin(
|
pub async fn is_mod_or_admin(
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
person_id: PersonId,
|
person: &Person,
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person_id, community_id).await?;
|
check_user_valid(person)?;
|
||||||
|
|
||||||
|
let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person.id, community_id).await?;
|
||||||
if !is_mod_or_admin {
|
if !is_mod_or_admin {
|
||||||
Err(LemmyErrorType::NotAModOrAdmin)?
|
Err(LemmyErrorType::NotAModOrAdmin)?
|
||||||
} else {
|
} else {
|
||||||
|
@ -68,7 +71,7 @@ pub async fn is_mod_or_admin_opt(
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
if let Some(local_user_view) = local_user_view {
|
if let Some(local_user_view) = local_user_view {
|
||||||
if let Some(community_id) = community_id {
|
if let Some(community_id) = community_id {
|
||||||
is_mod_or_admin(pool, local_user_view.person.id, community_id).await
|
is_mod_or_admin(pool, &local_user_view.person, community_id).await
|
||||||
} else {
|
} else {
|
||||||
is_admin(local_user_view)
|
is_admin(local_user_view)
|
||||||
}
|
}
|
||||||
|
@ -78,8 +81,11 @@ pub async fn is_mod_or_admin_opt(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
|
pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
|
||||||
|
check_user_valid(&local_user_view.person)?;
|
||||||
if !local_user_view.local_user.admin {
|
if !local_user_view.local_user.admin {
|
||||||
Err(LemmyErrorType::NotAnAdmin)?
|
Err(LemmyErrorType::NotAnAdmin)?
|
||||||
|
} else if local_user_view.person.banned {
|
||||||
|
Err(LemmyErrorType::Banned)?
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -89,6 +95,7 @@ pub fn is_top_mod(
|
||||||
local_user_view: &LocalUserView,
|
local_user_view: &LocalUserView,
|
||||||
community_mods: &[CommunityModeratorView],
|
community_mods: &[CommunityModeratorView],
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
check_user_valid(&local_user_view.person)?;
|
||||||
if local_user_view.person.id
|
if local_user_view.person.id
|
||||||
!= community_mods
|
!= community_mods
|
||||||
.first()
|
.first()
|
||||||
|
@ -113,73 +120,98 @@ pub async fn mark_post_as_read(
|
||||||
person_id: PersonId,
|
person_id: PersonId,
|
||||||
post_id: PostId,
|
post_id: PostId,
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> Result<PostRead, LemmyError> {
|
|
||||||
let post_read_form = PostReadForm { post_id, person_id };
|
|
||||||
|
|
||||||
PostRead::mark_as_read(pool, &post_read_form)
|
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn mark_post_as_unread(
|
|
||||||
person_id: PersonId,
|
|
||||||
post_id: PostId,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> Result<usize, LemmyError> {
|
|
||||||
let post_read_form = PostReadForm { post_id, person_id };
|
|
||||||
|
|
||||||
PostRead::mark_as_unread(pool, &post_read_form)
|
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_user_valid(
|
|
||||||
banned: bool,
|
|
||||||
ban_expires: Option<DateTime<Utc>>,
|
|
||||||
deleted: bool,
|
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
|
PostRead::mark_as_read(pool, HashSet::from([post_id]), person_id)
|
||||||
|
.await
|
||||||
|
.with_lemmy_type(LemmyErrorType::CouldntMarkPostAsRead)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_user_valid(person: &Person) -> Result<(), LemmyError> {
|
||||||
// Check for a site ban
|
// Check for a site ban
|
||||||
if is_banned(banned, ban_expires) {
|
if person.banned {
|
||||||
Err(LemmyErrorType::SiteBan)?
|
Err(LemmyErrorType::SiteBan)?
|
||||||
}
|
}
|
||||||
// check for account deletion
|
// check for account deletion
|
||||||
else if deleted {
|
else if person.deleted {
|
||||||
Err(LemmyErrorType::Deleted)?
|
Err(LemmyErrorType::Deleted)?
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
/// Checks that a normal user action (eg posting or voting) is allowed in a given community.
|
||||||
pub async fn check_community_ban(
|
///
|
||||||
person_id: PersonId,
|
/// In particular it checks that neither the user nor community are banned or deleted, and that
|
||||||
|
/// the user isn't banned.
|
||||||
|
pub async fn check_community_user_action(
|
||||||
|
person: &Person,
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> Result<(), LemmyError> {
|
) -> LemmyResult<()> {
|
||||||
let is_banned = CommunityPersonBanView::get(pool, person_id, community_id)
|
check_user_valid(person)?;
|
||||||
.await
|
check_community_deleted_removed(community_id, pool).await?;
|
||||||
.is_ok();
|
check_community_ban(person, community_id, pool).await?;
|
||||||
if is_banned {
|
|
||||||
Err(LemmyErrorType::BannedFromCommunity)?
|
|
||||||
} else {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
async fn check_community_deleted_removed(
|
||||||
pub async fn check_community_deleted_or_removed(
|
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
) -> Result<(), LemmyError> {
|
) -> LemmyResult<()> {
|
||||||
let community = Community::read(pool, community_id)
|
let community = Community::read(pool, community_id)
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
|
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
|
||||||
if community.deleted || community.removed {
|
if community.deleted || community.removed {
|
||||||
Err(LemmyErrorType::Deleted)?
|
Err(LemmyErrorType::Deleted)?
|
||||||
} else {
|
|
||||||
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.
|
||||||
|
///
|
||||||
|
/// In particular it checks that he is an admin or mod, wasn't banned and the community isn't
|
||||||
|
/// removed/deleted.
|
||||||
|
pub async fn check_community_mod_action(
|
||||||
|
person: &Person,
|
||||||
|
community_id: CommunityId,
|
||||||
|
allow_deleted: bool,
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
is_mod_or_admin(pool, person, community_id).await?;
|
||||||
|
check_community_ban(person, community_id, pool).await?;
|
||||||
|
|
||||||
|
// it must be possible to restore deleted community
|
||||||
|
if !allow_deleted {
|
||||||
|
check_community_deleted_removed(community_id, pool).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_community_mod_action_opt(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
community_id: Option<CommunityId>,
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if let Some(community_id) = community_id {
|
||||||
|
check_community_mod_action(&local_user_view.person, community_id, false, pool).await?;
|
||||||
|
} else {
|
||||||
|
is_admin(local_user_view)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
|
pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
|
||||||
|
@ -359,23 +391,21 @@ fn lang_str_to_lang(lang: &str) -> Lang {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_site_rate_limit_to_rate_limit_config(
|
pub fn local_site_rate_limit_to_rate_limit_config(
|
||||||
local_site_rate_limit: &LocalSiteRateLimit,
|
l: &LocalSiteRateLimit,
|
||||||
) -> RateLimitConfig {
|
) -> EnumMap<ActionType, BucketConfig> {
|
||||||
let l = local_site_rate_limit;
|
enum_map! {
|
||||||
RateLimitConfig {
|
ActionType::Message => (l.message, l.message_per_second),
|
||||||
message: l.message,
|
ActionType::Post => (l.post, l.post_per_second),
|
||||||
message_per_second: l.message_per_second,
|
ActionType::Register => (l.register, l.register_per_second),
|
||||||
post: l.post,
|
ActionType::Image => (l.image, l.image_per_second),
|
||||||
post_per_second: l.post_per_second,
|
ActionType::Comment => (l.comment, l.comment_per_second),
|
||||||
register: l.register,
|
ActionType::Search => (l.search, l.search_per_second),
|
||||||
register_per_second: l.register_per_second,
|
ActionType::ImportUserSettings => (l.import_user_settings, l.import_user_settings_per_second),
|
||||||
image: l.image,
|
|
||||||
image_per_second: l.image_per_second,
|
|
||||||
comment: l.comment,
|
|
||||||
comment_per_second: l.comment_per_second,
|
|
||||||
search: l.search,
|
|
||||||
search_per_second: l.search_per_second,
|
|
||||||
}
|
}
|
||||||
|
.map(|_key, (capacity, secs_to_refill)| BucketConfig {
|
||||||
|
capacity: u32::try_from(capacity).unwrap_or(0),
|
||||||
|
secs_to_refill: u32::try_from(secs_to_refill).unwrap_or(0),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option<Regex> {
|
pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option<Regex> {
|
||||||
|
@ -721,37 +751,6 @@ pub fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError
|
||||||
Ok(Url::parse(&format!("{community_id}/moderators"))?.into())
|
Ok(Url::parse(&format!("{community_id}/moderators"))?.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace special HTML characters in API parameters to prevent XSS attacks.
|
|
||||||
///
|
|
||||||
/// Taken from https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md#output-encoding-for-html-contexts
|
|
||||||
///
|
|
||||||
/// `>` is left in place because it is interpreted as markdown quote.
|
|
||||||
pub fn sanitize_html_api(data: &str) -> String {
|
|
||||||
data
|
|
||||||
.replace('&', "&")
|
|
||||||
.replace('<', "<")
|
|
||||||
.replace('\"', """)
|
|
||||||
.replace('\'', "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sanitize_html_api_opt(data: &Option<String>) -> Option<String> {
|
|
||||||
data.as_ref().map(|d| sanitize_html_api(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replace special HTML characters in federation parameters to prevent XSS attacks.
|
|
||||||
///
|
|
||||||
/// Unlike [sanitize_html_api()] it leaves `&` in place to avoid double escaping.
|
|
||||||
pub fn sanitize_html_federation(data: &str) -> String {
|
|
||||||
data
|
|
||||||
.replace('<', "<")
|
|
||||||
.replace('\"', """)
|
|
||||||
.replace('\'', "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sanitize_html_federation_opt(data: &Option<String>) -> Option<String> {
|
|
||||||
data.as_ref().map(|d| sanitize_html_federation(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_login_cookie(jwt: Sensitive<String>) -> Cookie<'static> {
|
pub fn create_login_cookie(jwt: Sensitive<String>) -> Cookie<'static> {
|
||||||
let mut cookie = Cookie::new(AUTH_COOKIE_NAME, jwt.into_inner());
|
let mut cookie = Cookie::new(AUTH_COOKIE_NAME, jwt.into_inner());
|
||||||
cookie.set_secure(true);
|
cookie.set_secure(true);
|
||||||
|
@ -760,12 +759,40 @@ pub fn create_login_cookie(jwt: Sensitive<String>) -> Cookie<'static> {
|
||||||
cookie
|
cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure that ban/block expiry is in valid range. If its in past, throw error. If its more
|
||||||
|
/// than 10 years in future, convert to permanent ban. Otherwise return the same value.
|
||||||
|
pub fn check_expire_time(expires_unix_opt: Option<i64>) -> LemmyResult<Option<DateTime<Utc>>> {
|
||||||
|
if let Some(expires_unix) = expires_unix_opt {
|
||||||
|
let expires = Utc
|
||||||
|
.timestamp_opt(expires_unix, 0)
|
||||||
|
.single()
|
||||||
|
.ok_or(LemmyErrorType::InvalidUnixTime)?;
|
||||||
|
|
||||||
|
limit_expire_time(expires)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn limit_expire_time(expires: DateTime<Utc>) -> LemmyResult<Option<DateTime<Utc>>> {
|
||||||
|
const MAX_BAN_TERM: Days = Days::new(10 * 365);
|
||||||
|
|
||||||
|
if expires < Local::now() {
|
||||||
|
Err(LemmyErrorType::BanExpirationInPast)?
|
||||||
|
} else if expires > Local::now() + MAX_BAN_TERM {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(expires))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
#![allow(clippy::indexing_slicing)]
|
#![allow(clippy::indexing_slicing)]
|
||||||
|
|
||||||
use crate::utils::{honeypot_check, password_length_check};
|
use crate::utils::{honeypot_check, limit_expire_time, password_length_check};
|
||||||
|
use chrono::{Days, Utc};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -783,4 +810,25 @@ mod tests {
|
||||||
assert!(honeypot_check(&Some("1".to_string())).is_err());
|
assert!(honeypot_check(&Some("1".to_string())).is_err());
|
||||||
assert!(honeypot_check(&Some("message".to_string())).is_err());
|
assert!(honeypot_check(&Some("message".to_string())).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_limit_ban_term() {
|
||||||
|
// Ban expires in past, should throw error
|
||||||
|
assert!(limit_expire_time(Utc::now() - Days::new(5)).is_err());
|
||||||
|
|
||||||
|
// Legitimate ban term, return same value
|
||||||
|
let fourteen_days = Utc::now() + Days::new(14);
|
||||||
|
assert_eq!(
|
||||||
|
limit_expire_time(fourteen_days).unwrap(),
|
||||||
|
Some(fourteen_days)
|
||||||
|
);
|
||||||
|
let nine_years = Utc::now() + Days::new(365 * 9);
|
||||||
|
assert_eq!(limit_expire_time(nine_years).unwrap(), Some(nine_years));
|
||||||
|
|
||||||
|
// Too long ban term, changes to None (permanent ban)
|
||||||
|
assert_eq!(
|
||||||
|
limit_expire_time(Utc::now() + Days::new(365 * 11)).unwrap(),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,11 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{
|
utils::{
|
||||||
check_community_ban,
|
check_community_user_action,
|
||||||
check_community_deleted_or_removed,
|
|
||||||
check_post_deleted_or_removed,
|
check_post_deleted_or_removed,
|
||||||
generate_local_apub_endpoint,
|
generate_local_apub_endpoint,
|
||||||
get_post,
|
get_post,
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
sanitize_html_api,
|
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -52,15 +50,13 @@ pub async fn create_comment(
|
||||||
&local_site_to_slur_regex(&local_site),
|
&local_site_to_slur_regex(&local_site),
|
||||||
);
|
);
|
||||||
is_valid_body_field(&Some(content.clone()), false)?;
|
is_valid_body_field(&Some(content.clone()), false)?;
|
||||||
let content = sanitize_html_api(&content);
|
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let post = get_post(post_id, &mut context.pool()).await?;
|
let post = get_post(post_id, &mut context.pool()).await?;
|
||||||
let community_id = post.community_id;
|
let community_id = post.community_id;
|
||||||
|
|
||||||
check_community_ban(local_user_view.person.id, community_id, &mut context.pool()).await?;
|
check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?;
|
||||||
check_community_deleted_or_removed(community_id, &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
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, DeleteComment},
|
comment::{CommentResponse, DeleteComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::check_community_ban,
|
utils::check_community_user_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -31,8 +31,8 @@ pub async fn delete_comment(
|
||||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||||
}
|
}
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
orig_comment.community.id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, RemoveComment},
|
comment::{CommentResponse, RemoveComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, is_mod_or_admin},
|
utils::check_community_mod_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -27,21 +27,14 @@ pub async fn remove_comment(
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_mod_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
orig_comment.community.id,
|
||||||
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Verify that only a mod or admin can remove
|
|
||||||
is_mod_or_admin(
|
|
||||||
&mut context.pool(),
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_comment.community.id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Do the remove
|
// Do the remove
|
||||||
let removed = data.removed;
|
let removed = data.removed;
|
||||||
let updated_comment = Comment::update(
|
let updated_comment = Comment::update(
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
comment::{CommentResponse, EditComment},
|
comment::{CommentResponse, EditComment},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, local_site_to_slur_regex, sanitize_html_api_opt},
|
utils::{check_community_user_action, local_site_to_slur_regex},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -37,8 +37,8 @@ pub async fn update_comment(
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_comment.community.id,
|
orig_comment.community.id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -63,7 +63,6 @@ pub async fn update_comment(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site)));
|
.map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site)));
|
||||||
is_valid_body_field(&content, false)?;
|
is_valid_body_field(&content, false)?;
|
||||||
let content = sanitize_html_api_opt(&content);
|
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let form = CommentUpdateForm {
|
let form = CommentUpdateForm {
|
||||||
|
|
|
@ -11,8 +11,6 @@ use lemmy_api_common::{
|
||||||
generate_shared_inbox_url,
|
generate_shared_inbox_url,
|
||||||
is_admin,
|
is_admin,
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
sanitize_html_api,
|
|
||||||
sanitize_html_api_opt,
|
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -57,14 +55,10 @@ pub async fn create_community(
|
||||||
let icon = diesel_option_overwrite_to_url_create(&data.icon)?;
|
let icon = diesel_option_overwrite_to_url_create(&data.icon)?;
|
||||||
let banner = diesel_option_overwrite_to_url_create(&data.banner)?;
|
let banner = diesel_option_overwrite_to_url_create(&data.banner)?;
|
||||||
|
|
||||||
let name = sanitize_html_api(&data.name);
|
|
||||||
let title = sanitize_html_api(&data.title);
|
|
||||||
let description = sanitize_html_api_opt(&data.description);
|
|
||||||
|
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
check_slurs(&name, &slur_regex)?;
|
check_slurs(&data.name, &slur_regex)?;
|
||||||
check_slurs(&title, &slur_regex)?;
|
check_slurs(&data.title, &slur_regex)?;
|
||||||
check_slurs_opt(&description, &slur_regex)?;
|
check_slurs_opt(&data.description, &slur_regex)?;
|
||||||
|
|
||||||
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
||||||
is_valid_body_field(&data.description, false)?;
|
is_valid_body_field(&data.description, false)?;
|
||||||
|
@ -85,9 +79,9 @@ pub async fn create_community(
|
||||||
let keypair = generate_actor_keypair()?;
|
let keypair = generate_actor_keypair()?;
|
||||||
|
|
||||||
let community_form = CommunityInsertForm::builder()
|
let community_form = CommunityInsertForm::builder()
|
||||||
.name(name)
|
.name(data.name.clone())
|
||||||
.title(title)
|
.title(data.title.clone())
|
||||||
.description(description)
|
.description(data.description.clone())
|
||||||
.icon(icon)
|
.icon(icon)
|
||||||
.banner(banner)
|
.banner(banner)
|
||||||
.nsfw(data.nsfw)
|
.nsfw(data.nsfw)
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, DeleteCommunity},
|
community::{CommunityResponse, DeleteCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::is_top_mod,
|
utils::{check_community_mod_action, is_top_mod},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::community::{Community, CommunityUpdateForm},
|
source::community::{Community, CommunityUpdateForm},
|
||||||
|
@ -26,6 +26,14 @@ pub async fn delete_community(
|
||||||
let community_mods =
|
let community_mods =
|
||||||
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
||||||
|
|
||||||
|
check_community_mod_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
community_id,
|
||||||
|
true,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Make sure deleter is the top mod
|
// Make sure deleter is the top mod
|
||||||
is_top_mod(&local_user_view, &community_mods)?;
|
is_top_mod(&local_user_view, &community_mods)?;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, RemoveCommunity},
|
community::{CommunityResponse, RemoveCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::is_admin,
|
utils::{check_community_mod_action, is_admin},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -15,10 +15,7 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
|
||||||
utils::time::naive_from_unix,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn remove_community(
|
pub async fn remove_community(
|
||||||
|
@ -26,6 +23,14 @@ pub async fn remove_community(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
) -> Result<Json<CommunityResponse>, LemmyError> {
|
||||||
|
check_community_mod_action(
|
||||||
|
&local_user_view.person,
|
||||||
|
data.community_id,
|
||||||
|
true,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Verify its an admin (only an admin can remove a community)
|
// Verify its an admin (only an admin can remove a community)
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
@ -44,13 +49,11 @@ pub async fn remove_community(
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
|
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let expires = data.expires.map(naive_from_unix);
|
|
||||||
let form = ModRemoveCommunityForm {
|
let form = ModRemoveCommunityForm {
|
||||||
mod_person_id: local_user_view.person.id,
|
mod_person_id: local_user_view.person.id,
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
removed: Some(removed),
|
removed: Some(removed),
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
expires,
|
|
||||||
};
|
};
|
||||||
ModRemoveCommunity::create(&mut context.pool(), &form).await?;
|
ModRemoveCommunity::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,9 @@ use lemmy_api_common::{
|
||||||
community::{CommunityResponse, EditCommunity},
|
community::{CommunityResponse, EditCommunity},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{local_site_to_slur_regex, sanitize_html_api_opt},
|
utils::{check_community_mod_action, local_site_to_slur_regex},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PersonId,
|
|
||||||
source::{
|
source::{
|
||||||
actor_language::{CommunityLanguage, SiteLanguage},
|
actor_language::{CommunityLanguage, SiteLanguage},
|
||||||
community::{Community, CommunityUpdateForm},
|
community::{Community, CommunityUpdateForm},
|
||||||
|
@ -18,7 +17,6 @@ use lemmy_db_schema::{
|
||||||
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url, naive_now},
|
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url, naive_now},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||||
utils::{slurs::check_slurs_opt, validation::is_valid_body_field},
|
utils::{slurs::check_slurs_opt, validation::is_valid_body_field},
|
||||||
|
@ -37,22 +35,18 @@ pub async fn update_community(
|
||||||
check_slurs_opt(&data.description, &slur_regex)?;
|
check_slurs_opt(&data.description, &slur_regex)?;
|
||||||
is_valid_body_field(&data.description, false)?;
|
is_valid_body_field(&data.description, false)?;
|
||||||
|
|
||||||
let title = sanitize_html_api_opt(&data.title);
|
|
||||||
let description = sanitize_html_api_opt(&data.description);
|
|
||||||
|
|
||||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||||
let description = diesel_option_overwrite(description);
|
let description = diesel_option_overwrite(data.description.clone());
|
||||||
|
|
||||||
// Verify its a mod (only mods can edit it)
|
// Verify its a mod (only mods can edit it)
|
||||||
let community_id = data.community_id;
|
check_community_mod_action(
|
||||||
let mods: Vec<PersonId> =
|
&local_user_view.person,
|
||||||
CommunityModeratorView::for_community(&mut context.pool(), community_id)
|
data.community_id,
|
||||||
.await
|
false,
|
||||||
.map(|v| v.into_iter().map(|m| m.moderator.id).collect())?;
|
&mut context.pool(),
|
||||||
if !mods.contains(&local_user_view.person.id) {
|
)
|
||||||
Err(LemmyErrorType::NotAModerator)?
|
.await?;
|
||||||
}
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
if let Some(languages) = data.discussion_languages.clone() {
|
if let Some(languages) = data.discussion_languages.clone() {
|
||||||
|
@ -67,7 +61,7 @@ pub async fn update_community(
|
||||||
}
|
}
|
||||||
|
|
||||||
let community_form = CommunityUpdateForm {
|
let community_form = CommunityUpdateForm {
|
||||||
title,
|
title: data.title.clone(),
|
||||||
description,
|
description,
|
||||||
icon,
|
icon,
|
||||||
banner,
|
banner,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
|
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
|
||||||
utils::{is_admin, sanitize_html_api},
|
utils::is_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
|
custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
|
||||||
|
@ -23,15 +23,11 @@ pub async fn create_custom_emoji(
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let shortcode = sanitize_html_api(data.shortcode.to_lowercase().trim());
|
|
||||||
let alt_text = sanitize_html_api(&data.alt_text);
|
|
||||||
let category = sanitize_html_api(&data.category);
|
|
||||||
|
|
||||||
let emoji_form = CustomEmojiInsertForm::builder()
|
let emoji_form = CustomEmojiInsertForm::builder()
|
||||||
.local_site_id(local_site.id)
|
.local_site_id(local_site.id)
|
||||||
.shortcode(shortcode)
|
.shortcode(data.shortcode.to_lowercase().trim().to_string())
|
||||||
.alt_text(alt_text)
|
.alt_text(data.alt_text.to_string())
|
||||||
.category(category)
|
.category(data.category.to_string())
|
||||||
.image_url(data.clone().image_url.into())
|
.image_url(data.clone().image_url.into())
|
||||||
.build();
|
.build();
|
||||||
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
|
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
|
||||||
|
|
|
@ -2,8 +2,9 @@ use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse},
|
custom_emoji::DeleteCustomEmoji,
|
||||||
utils::is_admin,
|
utils::is_admin,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::custom_emoji::CustomEmoji;
|
use lemmy_db_schema::source::custom_emoji::CustomEmoji;
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
@ -14,12 +15,11 @@ pub async fn delete_custom_emoji(
|
||||||
data: Json<DeleteCustomEmoji>,
|
data: Json<DeleteCustomEmoji>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<DeleteCustomEmojiResponse>, LemmyError> {
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
CustomEmoji::delete(&mut context.pool(), data.id).await?;
|
CustomEmoji::delete(&mut context.pool(), data.id).await?;
|
||||||
Ok(Json(DeleteCustomEmojiResponse {
|
|
||||||
id: data.id,
|
Ok(Json(SuccessResponse::default()))
|
||||||
success: true,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
|
custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
|
||||||
utils::{is_admin, sanitize_html_api},
|
utils::is_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
|
custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
|
||||||
|
@ -23,13 +23,10 @@ pub async fn update_custom_emoji(
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let alt_text = sanitize_html_api(&data.alt_text);
|
|
||||||
let category = sanitize_html_api(&data.category);
|
|
||||||
|
|
||||||
let emoji_form = CustomEmojiUpdateForm::builder()
|
let emoji_form = CustomEmojiUpdateForm::builder()
|
||||||
.local_site_id(local_site.id)
|
.local_site_id(local_site.id)
|
||||||
.alt_text(alt_text)
|
.alt_text(data.alt_text.to_string())
|
||||||
.category(category)
|
.category(data.category.to_string())
|
||||||
.image_url(data.clone().image_url.into())
|
.image_url(data.clone().image_url.into())
|
||||||
.build();
|
.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?;
|
||||||
|
|
|
@ -7,14 +7,11 @@ use lemmy_api_common::{
|
||||||
request::fetch_site_data,
|
request::fetch_site_data,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{
|
utils::{
|
||||||
check_community_ban,
|
check_community_user_action,
|
||||||
check_community_deleted_or_removed,
|
|
||||||
generate_local_apub_endpoint,
|
generate_local_apub_endpoint,
|
||||||
honeypot_check,
|
honeypot_check,
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
mark_post_as_read,
|
mark_post_as_read,
|
||||||
sanitize_html_api,
|
|
||||||
sanitize_html_api_opt,
|
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -62,13 +59,12 @@ pub async fn create_post(
|
||||||
is_valid_body_field(&data.body, true)?;
|
is_valid_body_field(&data.body, true)?;
|
||||||
check_url_scheme(&data.url)?;
|
check_url_scheme(&data.url)?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
data.community_id,
|
data.community_id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_community_deleted_or_removed(data.community_id, &mut context.pool()).await?;
|
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let community = Community::read(&mut context.pool(), community_id).await?;
|
let community = Community::read(&mut context.pool(), community_id).await?;
|
||||||
|
@ -92,11 +88,6 @@ pub async fn create_post(
|
||||||
.map(|u| (u.title, u.description, u.embed_video_url))
|
.map(|u| (u.title, u.description, u.embed_video_url))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let name = sanitize_html_api(data.name.trim());
|
|
||||||
let body = sanitize_html_api_opt(&data.body);
|
|
||||||
let embed_title = sanitize_html_api_opt(&embed_title);
|
|
||||||
let embed_description = sanitize_html_api_opt(&embed_description);
|
|
||||||
|
|
||||||
// Only need to check if language is allowed in case user set it explicitly. When using default
|
// Only need to check if language is allowed in case user set it explicitly. When using default
|
||||||
// language, it already only returns allowed languages.
|
// language, it already only returns allowed languages.
|
||||||
CommunityLanguage::is_allowed_community_language(
|
CommunityLanguage::is_allowed_community_language(
|
||||||
|
@ -120,9 +111,9 @@ pub async fn create_post(
|
||||||
};
|
};
|
||||||
|
|
||||||
let post_form = PostInsertForm::builder()
|
let post_form = PostInsertForm::builder()
|
||||||
.name(name)
|
.name(data.name.trim().to_string())
|
||||||
.url(url)
|
.url(url)
|
||||||
.body(body)
|
.body(data.body.clone())
|
||||||
.community_id(data.community_id)
|
.community_id(data.community_id)
|
||||||
.creator_id(local_user_view.person.id)
|
.creator_id(local_user_view.person.id)
|
||||||
.nsfw(data.nsfw)
|
.nsfw(data.nsfw)
|
||||||
|
@ -191,5 +182,5 @@ pub async fn create_post(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
build_post_response(&context, community_id, person_id, post_id).await
|
build_post_response(&context, community_id, &local_user_view.person, post_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{DeletePost, PostResponse},
|
post::{DeletePost, PostResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, check_community_deleted_or_removed},
|
utils::check_community_user_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::post::{Post, PostUpdateForm},
|
source::post::{Post, PostUpdateForm},
|
||||||
|
@ -28,13 +28,12 @@ pub async fn delete_post(
|
||||||
Err(LemmyErrorType::CouldntUpdatePost)?
|
Err(LemmyErrorType::CouldntUpdatePost)?
|
||||||
}
|
}
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_post.community_id,
|
orig_post.community_id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
|
|
||||||
|
|
||||||
// Verify that only the creator can delete
|
// Verify that only the creator can delete
|
||||||
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
|
||||||
|
@ -52,12 +51,17 @@ pub async fn delete_post(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::DeletePost(post, local_user_view.person, data.0.clone()),
|
SendActivityData::DeletePost(post, local_user_view.person.clone(), data.0.clone()),
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_post_response(&context, orig_post.community_id, person_id, data.post_id).await
|
build_post_response(
|
||||||
|
&context,
|
||||||
|
orig_post.community_id,
|
||||||
|
&local_user_view.person,
|
||||||
|
data.post_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
post::{PostResponse, RemovePost},
|
post::{PostResponse, RemovePost},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, is_mod_or_admin},
|
utils::check_community_mod_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -26,21 +26,14 @@ pub async fn remove_post(
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_mod_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_post.community_id,
|
orig_post.community_id,
|
||||||
|
false,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Verify that only the mods can remove
|
|
||||||
is_mod_or_admin(
|
|
||||||
&mut context.pool(),
|
|
||||||
local_user_view.person.id,
|
|
||||||
orig_post.community_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Update the post
|
// Update the post
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let removed = data.removed;
|
let removed = data.removed;
|
||||||
|
@ -63,12 +56,17 @@ pub async fn remove_post(
|
||||||
};
|
};
|
||||||
ModRemovePost::create(&mut context.pool(), &form).await?;
|
ModRemovePost::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::RemovePost(post, local_user_view.person, data.0),
|
SendActivityData::RemovePost(post, local_user_view.person.clone(), data.0),
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_post_response(&context, orig_post.community_id, person_id, post_id).await
|
build_post_response(
|
||||||
|
&context,
|
||||||
|
orig_post.community_id,
|
||||||
|
&local_user_view.person,
|
||||||
|
post_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
post::{EditPost, PostResponse},
|
post::{EditPost, PostResponse},
|
||||||
request::fetch_site_data,
|
request::fetch_site_data,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_ban, local_site_to_slur_regex, sanitize_html_api_opt},
|
utils::{check_community_user_action, local_site_to_slur_regex},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -55,8 +55,8 @@ pub async fn update_post(
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
check_community_ban(
|
check_community_user_action(
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
orig_post.community_id,
|
orig_post.community_id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
|
@ -75,12 +75,6 @@ pub async fn update_post(
|
||||||
.map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
|
.map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let name = sanitize_html_api_opt(&data.name);
|
|
||||||
let body = sanitize_html_api_opt(&data.body);
|
|
||||||
let body = diesel_option_overwrite(body);
|
|
||||||
let embed_title = embed_title.map(|e| sanitize_html_api_opt(&e));
|
|
||||||
let embed_description = embed_description.map(|e| sanitize_html_api_opt(&e));
|
|
||||||
|
|
||||||
let language_id = data.language_id;
|
let language_id = data.language_id;
|
||||||
CommunityLanguage::is_allowed_community_language(
|
CommunityLanguage::is_allowed_community_language(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
|
@ -90,9 +84,9 @@ pub async fn update_post(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let post_form = PostUpdateForm {
|
let post_form = PostUpdateForm {
|
||||||
name,
|
name: data.name.clone(),
|
||||||
url,
|
url,
|
||||||
body,
|
body: diesel_option_overwrite(data.body.clone()),
|
||||||
nsfw: data.nsfw,
|
nsfw: data.nsfw,
|
||||||
embed_title,
|
embed_title,
|
||||||
embed_description,
|
embed_description,
|
||||||
|
@ -113,7 +107,7 @@ pub async fn update_post(
|
||||||
build_post_response(
|
build_post_response(
|
||||||
context.deref(),
|
context.deref(),
|
||||||
orig_post.community_id,
|
orig_post.community_id,
|
||||||
local_user_view.person.id,
|
&local_user_view.person,
|
||||||
post_id,
|
post_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -9,7 +9,6 @@ use lemmy_api_common::{
|
||||||
generate_local_apub_endpoint,
|
generate_local_apub_endpoint,
|
||||||
get_interface_language,
|
get_interface_language,
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
sanitize_html_api,
|
|
||||||
send_email_to_user,
|
send_email_to_user,
|
||||||
EndpointType,
|
EndpointType,
|
||||||
},
|
},
|
||||||
|
@ -24,7 +23,7 @@ use lemmy_db_schema::{
|
||||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||||
utils::{slurs::remove_slurs, validation::is_valid_body_field},
|
utils::{markdown::markdown_to_html, slurs::remove_slurs, validation::is_valid_body_field},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -35,8 +34,7 @@ pub async fn create_private_message(
|
||||||
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
|
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let content = sanitize_html_api(&data.content);
|
let content = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site));
|
||||||
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
|
|
||||||
is_valid_body_field(&Some(content.clone()), false)?;
|
is_valid_body_field(&Some(content.clone()), false)?;
|
||||||
|
|
||||||
check_person_block(
|
check_person_block(
|
||||||
|
@ -83,6 +81,7 @@ pub async fn create_private_message(
|
||||||
let lang = get_interface_language(&local_recipient);
|
let lang = get_interface_language(&local_recipient);
|
||||||
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||||
let sender_name = &local_user_view.person.name;
|
let sender_name = &local_user_view.person.name;
|
||||||
|
let content = markdown_to_html(&content);
|
||||||
send_email_to_user(
|
send_email_to_user(
|
||||||
&local_recipient,
|
&local_recipient,
|
||||||
&lang.notification_private_message_subject(sender_name),
|
&lang.notification_private_message_subject(sender_name),
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
private_message::{EditPrivateMessage, PrivateMessageResponse},
|
private_message::{EditPrivateMessage, PrivateMessageResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{local_site_to_slur_regex, sanitize_html_api},
|
utils::local_site_to_slur_regex,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -36,8 +36,7 @@ pub async fn update_private_message(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doing the update
|
// Doing the update
|
||||||
let content = sanitize_html_api(&data.content);
|
let content = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site));
|
||||||
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
|
|
||||||
is_valid_body_field(&Some(content.clone()), false)?;
|
is_valid_body_field(&Some(content.clone()), false)?;
|
||||||
|
|
||||||
let private_message_id = data.private_message_id;
|
let private_message_id = data.private_message_id;
|
||||||
|
|
|
@ -4,13 +4,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{CreateSite, SiteResponse},
|
site::{CreateSite, SiteResponse},
|
||||||
utils::{
|
utils::{generate_site_inbox_url, is_admin, local_site_rate_limit_to_rate_limit_config},
|
||||||
generate_site_inbox_url,
|
|
||||||
is_admin,
|
|
||||||
local_site_rate_limit_to_rate_limit_config,
|
|
||||||
sanitize_html_api,
|
|
||||||
sanitize_html_api_opt,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::DbUrl,
|
newtypes::DbUrl,
|
||||||
|
@ -55,14 +49,11 @@ pub async fn create_site(
|
||||||
let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into();
|
let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into();
|
||||||
let inbox_url = Some(generate_site_inbox_url(&actor_id)?);
|
let inbox_url = Some(generate_site_inbox_url(&actor_id)?);
|
||||||
let keypair = generate_actor_keypair()?;
|
let keypair = generate_actor_keypair()?;
|
||||||
let name = sanitize_html_api(&data.name);
|
|
||||||
let sidebar = sanitize_html_api_opt(&data.sidebar);
|
|
||||||
let description = sanitize_html_api_opt(&data.description);
|
|
||||||
|
|
||||||
let site_form = SiteUpdateForm {
|
let site_form = SiteUpdateForm {
|
||||||
name: Some(name),
|
name: Some(data.name.clone()),
|
||||||
sidebar: diesel_option_overwrite(sidebar),
|
sidebar: diesel_option_overwrite(data.sidebar.clone()),
|
||||||
description: diesel_option_overwrite(description),
|
description: diesel_option_overwrite(data.description.clone()),
|
||||||
icon: diesel_option_overwrite_to_url(&data.icon)?,
|
icon: diesel_option_overwrite_to_url(&data.icon)?,
|
||||||
banner: diesel_option_overwrite_to_url(&data.banner)?,
|
banner: diesel_option_overwrite_to_url(&data.banner)?,
|
||||||
actor_id: Some(actor_id),
|
actor_id: Some(actor_id),
|
||||||
|
@ -77,10 +68,6 @@ pub async fn create_site(
|
||||||
|
|
||||||
Site::update(&mut context.pool(), site_id, &site_form).await?;
|
Site::update(&mut context.pool(), site_id, &site_form).await?;
|
||||||
|
|
||||||
let application_question = sanitize_html_api_opt(&data.application_question);
|
|
||||||
let default_theme = sanitize_html_api_opt(&data.default_theme);
|
|
||||||
let legal_information = sanitize_html_api_opt(&data.legal_information);
|
|
||||||
|
|
||||||
let local_site_form = LocalSiteUpdateForm {
|
let local_site_form = LocalSiteUpdateForm {
|
||||||
// Set the site setup to true
|
// Set the site setup to true
|
||||||
site_setup: Some(true),
|
site_setup: Some(true),
|
||||||
|
@ -89,11 +76,11 @@ pub async fn create_site(
|
||||||
enable_nsfw: data.enable_nsfw,
|
enable_nsfw: data.enable_nsfw,
|
||||||
community_creation_admin_only: data.community_creation_admin_only,
|
community_creation_admin_only: data.community_creation_admin_only,
|
||||||
require_email_verification: data.require_email_verification,
|
require_email_verification: data.require_email_verification,
|
||||||
application_question: diesel_option_overwrite(application_question),
|
application_question: diesel_option_overwrite(data.application_question.clone()),
|
||||||
private_instance: data.private_instance,
|
private_instance: data.private_instance,
|
||||||
default_theme,
|
default_theme: data.default_theme.clone(),
|
||||||
default_post_listing_type: data.default_post_listing_type,
|
default_post_listing_type: data.default_post_listing_type,
|
||||||
legal_information: diesel_option_overwrite(legal_information),
|
legal_information: diesel_option_overwrite(data.legal_information.clone()),
|
||||||
application_email_admins: data.application_email_admins,
|
application_email_admins: data.application_email_admins,
|
||||||
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
||||||
updated: Some(Some(naive_now())),
|
updated: Some(Some(naive_now())),
|
||||||
|
@ -132,10 +119,7 @@ pub async fn create_site(
|
||||||
|
|
||||||
let rate_limit_config =
|
let rate_limit_config =
|
||||||
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
|
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
|
||||||
context
|
context.rate_limit_cell().set_config(rate_limit_config);
|
||||||
.settings_updated_channel()
|
|
||||||
.send(rate_limit_config)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SiteResponse {
|
Ok(Json(SiteResponse {
|
||||||
site_view,
|
site_view,
|
||||||
|
|
|
@ -3,7 +3,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{EditSite, SiteResponse},
|
site::{EditSite, SiteResponse},
|
||||||
utils::{is_admin, local_site_rate_limit_to_rate_limit_config, sanitize_html_api_opt},
|
utils::{is_admin, local_site_rate_limit_to_rate_limit_config},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -54,14 +54,10 @@ pub async fn update_site(
|
||||||
SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?;
|
SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = sanitize_html_api_opt(&data.name);
|
|
||||||
let sidebar = sanitize_html_api_opt(&data.sidebar);
|
|
||||||
let description = sanitize_html_api_opt(&data.description);
|
|
||||||
|
|
||||||
let site_form = SiteUpdateForm {
|
let site_form = SiteUpdateForm {
|
||||||
name,
|
name: data.name.clone(),
|
||||||
sidebar: diesel_option_overwrite(sidebar),
|
sidebar: diesel_option_overwrite(data.sidebar.clone()),
|
||||||
description: diesel_option_overwrite(description),
|
description: diesel_option_overwrite(data.description.clone()),
|
||||||
icon: diesel_option_overwrite_to_url(&data.icon)?,
|
icon: diesel_option_overwrite_to_url(&data.icon)?,
|
||||||
banner: diesel_option_overwrite_to_url(&data.banner)?,
|
banner: diesel_option_overwrite_to_url(&data.banner)?,
|
||||||
updated: Some(Some(naive_now())),
|
updated: Some(Some(naive_now())),
|
||||||
|
@ -74,21 +70,17 @@ pub async fn update_site(
|
||||||
// Diesel will throw an error for empty update forms
|
// Diesel will throw an error for empty update forms
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
let application_question = sanitize_html_api_opt(&data.application_question);
|
|
||||||
let default_theme = sanitize_html_api_opt(&data.default_theme);
|
|
||||||
let legal_information = sanitize_html_api_opt(&data.legal_information);
|
|
||||||
|
|
||||||
let local_site_form = LocalSiteUpdateForm {
|
let local_site_form = LocalSiteUpdateForm {
|
||||||
enable_downvotes: data.enable_downvotes,
|
enable_downvotes: data.enable_downvotes,
|
||||||
registration_mode: data.registration_mode,
|
registration_mode: data.registration_mode,
|
||||||
enable_nsfw: data.enable_nsfw,
|
enable_nsfw: data.enable_nsfw,
|
||||||
community_creation_admin_only: data.community_creation_admin_only,
|
community_creation_admin_only: data.community_creation_admin_only,
|
||||||
require_email_verification: data.require_email_verification,
|
require_email_verification: data.require_email_verification,
|
||||||
application_question: diesel_option_overwrite(application_question),
|
application_question: diesel_option_overwrite(data.application_question.clone()),
|
||||||
private_instance: data.private_instance,
|
private_instance: data.private_instance,
|
||||||
default_theme,
|
default_theme: data.default_theme.clone(),
|
||||||
default_post_listing_type: data.default_post_listing_type,
|
default_post_listing_type: data.default_post_listing_type,
|
||||||
legal_information: diesel_option_overwrite(legal_information),
|
legal_information: diesel_option_overwrite(data.legal_information.clone()),
|
||||||
application_email_admins: data.application_email_admins,
|
application_email_admins: data.application_email_admins,
|
||||||
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
||||||
updated: Some(Some(naive_now())),
|
updated: Some(Some(naive_now())),
|
||||||
|
@ -165,10 +157,7 @@ pub async fn update_site(
|
||||||
|
|
||||||
let rate_limit_config =
|
let rate_limit_config =
|
||||||
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
|
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
|
||||||
context
|
context.rate_limit_cell().set_config(rate_limit_config);
|
||||||
.settings_updated_channel()
|
|
||||||
.send(rate_limit_config)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SiteResponse {
|
Ok(Json(SiteResponse {
|
||||||
site_view,
|
site_view,
|
||||||
|
|
|
@ -12,8 +12,6 @@ use lemmy_api_common::{
|
||||||
honeypot_check,
|
honeypot_check,
|
||||||
local_site_to_slur_regex,
|
local_site_to_slur_regex,
|
||||||
password_length_check,
|
password_length_check,
|
||||||
sanitize_html_api,
|
|
||||||
sanitize_html_api_opt,
|
|
||||||
send_new_applicant_email_to_admins,
|
send_new_applicant_email_to_admins,
|
||||||
send_verification_email,
|
send_verification_email,
|
||||||
EndpointType,
|
EndpointType,
|
||||||
|
@ -93,12 +91,6 @@ pub async fn register(
|
||||||
check_slurs(&data.username, &slur_regex)?;
|
check_slurs(&data.username, &slur_regex)?;
|
||||||
check_slurs_opt(&data.answer, &slur_regex)?;
|
check_slurs_opt(&data.answer, &slur_regex)?;
|
||||||
|
|
||||||
if sanitize_html_api(&data.username) != data.username {
|
|
||||||
Err(LemmyErrorType::InvalidName)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let answer = sanitize_html_api_opt(&data.answer);
|
|
||||||
|
|
||||||
let actor_keypair = generate_actor_keypair()?;
|
let actor_keypair = generate_actor_keypair()?;
|
||||||
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
|
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
|
||||||
let actor_id = generate_local_apub_endpoint(
|
let actor_id = generate_local_apub_endpoint(
|
||||||
|
@ -154,7 +146,7 @@ pub async fn register(
|
||||||
let form = RegistrationApplicationInsertForm {
|
let form = RegistrationApplicationInsertForm {
|
||||||
local_user_id: inserted_local_user.id,
|
local_user_id: inserted_local_user.id,
|
||||||
// We already made sure answer was not null above
|
// We already made sure answer was not null above
|
||||||
answer: answer.expect("must have an answer"),
|
answer: data.answer.clone().expect("must have an answer"),
|
||||||
};
|
};
|
||||||
|
|
||||||
RegistrationApplication::create(&mut context.pool(), &form).await?;
|
RegistrationApplication::create(&mut context.pool(), &form).await?;
|
||||||
|
|
|
@ -3,20 +3,21 @@ use actix_web::web::Json;
|
||||||
use bcrypt::verify;
|
use bcrypt::verify;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{DeleteAccount, DeleteAccountResponse},
|
person::DeleteAccount,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::purge_user_account,
|
utils::purge_user_account,
|
||||||
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{login_token::LoginToken, person::Person};
|
use lemmy_db_schema::source::{login_token::LoginToken, person::Person};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn delete_account(
|
pub async fn delete_account(
|
||||||
data: Json<DeleteAccount>,
|
data: Json<DeleteAccount>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<DeleteAccountResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Verify the password
|
// Verify the password
|
||||||
let valid: bool = verify(
|
let valid: bool = verify(
|
||||||
&data.password,
|
&data.password,
|
||||||
|
@ -41,5 +42,5 @@ pub async fn delete_account(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Json(DeleteAccountResponse {}))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ anyhow = { workspace = true }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
html2md = "0.2.14"
|
html2md = "0.2.14"
|
||||||
|
html2text = "0.6.0"
|
||||||
|
stringreader = "0.1.1"
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
enum_delegate = "0.2.0"
|
enum_delegate = "0.2.0"
|
||||||
moka = { version = "0.11", features = ["future"] }
|
moka = { version = "0.11", features = ["future"] }
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "https://enterprise.lemmy.ml/c/tenforward",
|
"id": "https://enterprise.lemmy.ml/c/tenforward",
|
||||||
"type": "Group",
|
"type": "Group",
|
||||||
"preferredUsername": "main",
|
"preferredUsername": "tenforward",
|
||||||
"name": "Ten Forward",
|
"name": "Ten Forward",
|
||||||
"summary": "<p>Lounge and recreation facility</p>\n<hr />\n<p>Welcome to the <a href=\"https://memory-alpha.fandom.com/wiki/USS_Enterprise_(NCC-1701-D)\">Enterprise</a>!.</p>\n",
|
"summary": "<p>Lounge and recreation facility</p>\n<hr />\n<p>Welcome to the <a href=\"https://memory-alpha.fandom.com/wiki/USS_Enterprise_(NCC-1701-D)\">Enterprise</a>!.</p>\n",
|
||||||
"source": {
|
"source": {
|
||||||
|
|
|
@ -11,40 +11,42 @@
|
||||||
"votersCount": "toot:votersCount"
|
"votersCount": "toot:votersCount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "https://mastodon.madrid/users/felix/statuses/107224289116410645",
|
"id": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519",
|
||||||
"type": "Note",
|
"type": "Note",
|
||||||
"summary": null,
|
"summary": null,
|
||||||
"published": "2021-11-05T11:46:50Z",
|
"inReplyTo": null,
|
||||||
"url": "https://mastodon.madrid/@felix/107224289116410645",
|
"published": "2023-08-04T09:55:39Z",
|
||||||
"attributedTo": "https://mastodon.madrid/users/felix",
|
"url": "https://dice.camp/@thekernelinyellow/110830743680706519",
|
||||||
"to": ["https://mastodon.madrid/users/felix/followers"],
|
"attributedTo": "https://dice.camp/users/thekernelinyellow",
|
||||||
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"cc": [
|
"cc": [
|
||||||
"https://www.w3.org/ns/activitystreams#Public",
|
"https://dice.camp/users/thekernelinyellow/followers",
|
||||||
"https://mamot.fr/users/retiolus"
|
"https://enterprise.lemmy.ml/c/tenforward",
|
||||||
|
"https://enterprise.lemmy.ml/c/tenforward/followers"
|
||||||
],
|
],
|
||||||
"sensitive": false,
|
"sensitive": false,
|
||||||
"atomUri": "https://mastodon.madrid/users/felix/statuses/107224289116410645",
|
"atomUri": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519",
|
||||||
"inReplyToAtomUri": "https://mamot.fr/users/retiolus/statuses/107224244380204526",
|
"inReplyToAtomUri": null,
|
||||||
"conversation": "tag:mamot.fr,2021-11-05:objectId=64635960:objectType=Conversation",
|
"conversation": "tag:dice.camp,2023-08-04:objectId=29969291:objectType=Conversation",
|
||||||
"content": "<p><span class=\"h-card\"><a href=\"https://mamot.fr/@retiolus\" class=\"u-url mention\">@<span>retiolus</span></a></span> i have never been disappointed by a thinkpad. if you want to save money, get a model from a few years ago, there isnt a huge difference anyway.</p>",
|
"content": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://enterprise.lemmy.ml/c/tenforward\" class=\"u-url mention\">@<span>tenforward</span></a></span> Variable never resetting at refresh</p><p>Hi! I'm using a variable to count elements in my generator but every time I generate a new character, the counter's value carries on from the previous one. Is there a function to reset it (I set it to 0 at the beginning of the file)</p>",
|
||||||
"contentMap": {
|
"contentMap": {
|
||||||
"en": "<p><span class=\"h-card\"><a href=\"https://mamot.fr/@retiolus\" class=\"u-url mention\">@<span>retiolus</span></a></span> i have never been disappointed by a thinkpad. if you want to save money, get a model from a few years ago, there isnt a huge difference anyway.</p>"
|
"it": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://enterprise.lemmy.ml/c/tenforward\" class=\"u-url mention\">@<span>tenforward</span></a></span>Variable never resetting at refresh</p><p>Hi! I'm using a variable to count elements in my generator but every time I generate a new character, the counter's value carries on from the previous one. Is there a function to reset it (I set it to 0 at the beginning of the file)</p>"
|
||||||
},
|
},
|
||||||
"attachment": [],
|
"attachment": [],
|
||||||
"tag": [
|
"tag": [
|
||||||
{
|
{
|
||||||
"type": "Mention",
|
"type": "Mention",
|
||||||
"href": "https://mamot.fr/users/retiolus",
|
"href": "https://enterprise.lemmy.ml/c/tenforward",
|
||||||
"name": "@retiolus@mamot.fr"
|
"name": "@tenforward@enterprise.lemmy.ml"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"replies": {
|
"replies": {
|
||||||
"id": "https://mastodon.madrid/users/felix/statuses/107224289116410645/replies",
|
"id": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519/replies",
|
||||||
"type": "Collection",
|
"type": "Collection",
|
||||||
"first": {
|
"first": {
|
||||||
"type": "CollectionPage",
|
"type": "CollectionPage",
|
||||||
"next": "https://mastodon.madrid/users/felix/statuses/107224289116410645/replies?only_other_accounts=true&page=true",
|
"next": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519/replies?only_other_accounts=true&page=true",
|
||||||
"partOf": "https://mastodon.madrid/users/felix/statuses/107224289116410645/replies",
|
"partOf": "https://dice.camp/users/thekernelinyellow/statuses/110830743680706519/replies",
|
||||||
"items": []
|
"items": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use anyhow::anyhow;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{remove_user_data, remove_user_data_in_community, sanitize_html_federation_opt},
|
utils::{remove_user_data, remove_user_data_in_community},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -173,7 +173,7 @@ impl ActivityHandler for BlockUser {
|
||||||
let form = ModBanForm {
|
let form = ModBanForm {
|
||||||
mod_person_id: mod_person.id,
|
mod_person_id: mod_person.id,
|
||||||
other_person_id: blocked_person.id,
|
other_person_id: blocked_person.id,
|
||||||
reason: sanitize_html_federation_opt(&self.summary),
|
reason: self.summary,
|
||||||
banned: Some(true),
|
banned: Some(true),
|
||||||
expires,
|
expires,
|
||||||
};
|
};
|
||||||
|
@ -207,7 +207,7 @@ impl ActivityHandler for BlockUser {
|
||||||
mod_person_id: mod_person.id,
|
mod_person_id: mod_person.id,
|
||||||
other_person_id: blocked_person.id,
|
other_person_id: blocked_person.id,
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
reason: sanitize_html_federation_opt(&self.summary),
|
reason: self.summary,
|
||||||
banned: Some(true),
|
banned: Some(true),
|
||||||
expires,
|
expires,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,12 @@ use activitypub_federation::{
|
||||||
traits::{Actor, Object},
|
traits::{Actor, Object},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{community::BanFromCommunity, context::LemmyContext, person::BanPerson};
|
use lemmy_api_common::{
|
||||||
|
community::BanFromCommunity,
|
||||||
|
context::LemmyContext,
|
||||||
|
person::BanPerson,
|
||||||
|
utils::check_expire_time,
|
||||||
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::CommunityId,
|
newtypes::CommunityId,
|
||||||
source::{community::Community, person::Person, site::Site},
|
source::{community::Community, person::Person, site::Site},
|
||||||
|
@ -19,10 +24,7 @@ use lemmy_db_schema::{
|
||||||
utils::DbPool,
|
utils::DbPool,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::SiteView;
|
use lemmy_db_views::structs::SiteView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
error::{LemmyError, LemmyResult},
|
|
||||||
utils::time::naive_from_unix,
|
|
||||||
};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -137,7 +139,7 @@ pub(crate) async fn send_ban_from_site(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into());
|
let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into());
|
||||||
let expires = data.expires.map(naive_from_unix);
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
|
||||||
// if the action affects a local user, federate to other instances
|
// if the action affects a local user, federate to other instances
|
||||||
if banned_user.local {
|
if banned_user.local {
|
||||||
|
@ -177,7 +179,7 @@ pub(crate) async fn send_ban_from_community(
|
||||||
let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
|
let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
|
||||||
.await?
|
.await?
|
||||||
.into();
|
.into();
|
||||||
let expires = data.expires.map(naive_from_unix);
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
|
||||||
if data.ban {
|
if data.ban {
|
||||||
BlockUser::send(
|
BlockUser::send(
|
||||||
|
|
|
@ -17,7 +17,7 @@ use activitypub_federation::{
|
||||||
protocol::verification::verify_domains_match,
|
protocol::verification::verify_domains_match,
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
};
|
};
|
||||||
use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_federation_opt};
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
|
@ -118,7 +118,7 @@ impl ActivityHandler for UndoBlockUser {
|
||||||
let form = ModBanForm {
|
let form = ModBanForm {
|
||||||
mod_person_id: mod_person.id,
|
mod_person_id: mod_person.id,
|
||||||
other_person_id: blocked_person.id,
|
other_person_id: blocked_person.id,
|
||||||
reason: sanitize_html_federation_opt(&self.object.summary),
|
reason: self.object.summary,
|
||||||
banned: Some(false),
|
banned: Some(false),
|
||||||
expires,
|
expires,
|
||||||
};
|
};
|
||||||
|
@ -137,7 +137,7 @@ impl ActivityHandler for UndoBlockUser {
|
||||||
mod_person_id: mod_person.id,
|
mod_person_id: mod_person.id,
|
||||||
other_person_id: blocked_person.id,
|
other_person_id: blocked_person.id,
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
reason: sanitize_html_federation_opt(&self.object.summary),
|
reason: self.object.summary,
|
||||||
banned: Some(false),
|
banned: Some(false),
|
||||||
expires,
|
expires,
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,8 +22,8 @@ use activitypub_federation::{
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
};
|
};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::source::activity::ActivitySendTargets;
|
use lemmy_db_schema::source::{activity::ActivitySendTargets, community::CommunityFollower};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -46,24 +46,28 @@ impl ActivityHandler for RawAnnouncableActivities {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
let activity: AnnouncableActivities = self.clone().try_into()?;
|
let activity: AnnouncableActivities = self.clone().try_into()?;
|
||||||
|
|
||||||
// This is only for sending, not receiving so we reject it.
|
// This is only for sending, not receiving so we reject it.
|
||||||
if let AnnouncableActivities::Page(_) = activity {
|
if let AnnouncableActivities::Page(_) = activity {
|
||||||
Err(LemmyErrorType::CannotReceivePage)?
|
Err(LemmyErrorType::CannotReceivePage)?
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify and receive activity
|
// Need to treat community as optional here because `Delete/PrivateMessage` gets routed through
|
||||||
activity.verify(data).await?;
|
let community = activity.community(context).await.ok();
|
||||||
activity.clone().receive(data).await?;
|
can_accept_activity_in_community(&community, context).await?;
|
||||||
|
|
||||||
// if activity is in a community, send to followers
|
// verify and receive activity
|
||||||
let community = activity.community(data).await;
|
activity.verify(context).await?;
|
||||||
if let Ok(community) = community {
|
activity.clone().receive(context).await?;
|
||||||
|
|
||||||
|
// if community is local, send activity to followers
|
||||||
|
if let Some(community) = community {
|
||||||
if community.local {
|
if community.local {
|
||||||
let actor_id = activity.actor().clone().into();
|
let actor_id = activity.actor().clone().into();
|
||||||
verify_person_in_community(&actor_id, &community, data).await?;
|
verify_person_in_community(&actor_id, &community, context).await?;
|
||||||
AnnounceActivity::send(self, &community, data).await?;
|
AnnounceActivity::send(self, &community, context).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -150,11 +154,15 @@ impl ActivityHandler for AnnounceActivity {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
async fn receive(self, context: &Data<Self::DataType>) -> Result<(), LemmyError> {
|
||||||
let object: AnnouncableActivities = self.object.object(context).await?.try_into()?;
|
let object: AnnouncableActivities = self.object.object(context).await?.try_into()?;
|
||||||
|
|
||||||
// This is only for sending, not receiving so we reject it.
|
// This is only for sending, not receiving so we reject it.
|
||||||
if let AnnouncableActivities::Page(_) = object {
|
if let AnnouncableActivities::Page(_) = object {
|
||||||
Err(LemmyErrorType::CannotReceivePage)?
|
Err(LemmyErrorType::CannotReceivePage)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let community = object.community(context).await?;
|
||||||
|
can_accept_activity_in_community(&Some(community), context).await?;
|
||||||
|
|
||||||
// verify here in order to avoid fetching the object twice over http
|
// verify here in order to avoid fetching the object twice over http
|
||||||
object.verify(context).await?;
|
object.verify(context).await?;
|
||||||
object.receive(context).await
|
object.receive(context).await
|
||||||
|
@ -185,3 +193,23 @@ impl TryFrom<AnnouncableActivities> for RawAnnouncableActivities {
|
||||||
serde_json::from_value(serde_json::to_value(value)?)
|
serde_json::from_value(serde_json::to_value(value)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if an activity in the given community can be accepted. To return true, the community must
|
||||||
|
/// either be local to this instance, or it must have at least one local follower.
|
||||||
|
///
|
||||||
|
/// TODO: This means mentions dont work if the community has no local followers. Can be fixed
|
||||||
|
/// by checking if any local user is in to/cc fields of activity. Anyway this is a minor
|
||||||
|
/// problem compared to receiving unsolicited posts.
|
||||||
|
async fn can_accept_activity_in_community(
|
||||||
|
community: &Option<ApubCommunity>,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if let Some(community) = community {
|
||||||
|
if !community.local
|
||||||
|
&& !CommunityFollower::has_local_followers(&mut context.pool(), community.id).await?
|
||||||
|
{
|
||||||
|
Err(LemmyErrorType::CommunityHasNoFollowers)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use activitypub_federation::{
|
||||||
kinds::activity::FlagType,
|
kinds::activity::FlagType,
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
};
|
};
|
||||||
use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_federation};
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
|
@ -90,7 +90,7 @@ impl ActivityHandler for Report {
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
original_post_name: post.name.clone(),
|
original_post_name: post.name.clone(),
|
||||||
original_post_url: post.url.clone(),
|
original_post_url: post.url.clone(),
|
||||||
reason: sanitize_html_federation(&self.summary),
|
reason: self.summary.clone(),
|
||||||
original_post_body: post.body.clone(),
|
original_post_body: post.body.clone(),
|
||||||
};
|
};
|
||||||
PostReport::report(&mut context.pool(), &report_form).await?;
|
PostReport::report(&mut context.pool(), &report_form).await?;
|
||||||
|
@ -100,7 +100,7 @@ impl ActivityHandler for Report {
|
||||||
creator_id: actor.id,
|
creator_id: actor.id,
|
||||||
comment_id: comment.id,
|
comment_id: comment.id,
|
||||||
original_comment_text: comment.content.clone(),
|
original_comment_text: comment.content.clone(),
|
||||||
reason: sanitize_html_federation(&self.summary),
|
reason: self.summary.clone(),
|
||||||
};
|
};
|
||||||
CommentReport::report(&mut context.pool(), &report_form).await?;
|
CommentReport::report(&mut context.pool(), &report_form).await?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@ impl ActivityHandler for CreateOrUpdateNote {
|
||||||
if distinguished != existing_comment.distinguished {
|
if distinguished != existing_comment.distinguished {
|
||||||
let creator = self.actor.dereference(context).await?;
|
let creator = self.actor.dereference(context).await?;
|
||||||
let (post, _) = self.object.get_parents(context).await?;
|
let (post, _) = self.object.get_parents(context).await?;
|
||||||
is_mod_or_admin(&mut context.pool(), creator.id, post.community_id).await?;
|
is_mod_or_admin(&mut context.pool(), &creator, post.community_id).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
|
protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
|
||||||
};
|
};
|
||||||
use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler};
|
use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler};
|
||||||
use lemmy_api_common::{context::LemmyContext, utils::sanitize_html_federation_opt};
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
|
@ -105,8 +105,6 @@ pub(in crate::activities) async fn receive_remove_action(
|
||||||
reason: Option<String>,
|
reason: Option<String>,
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
) -> Result<(), LemmyError> {
|
) -> Result<(), LemmyError> {
|
||||||
let reason = sanitize_html_federation_opt(&reason);
|
|
||||||
|
|
||||||
match DeletableObjects::read_from_db(object, context).await? {
|
match DeletableObjects::read_from_db(object, context).await? {
|
||||||
DeletableObjects::Community(community) => {
|
DeletableObjects::Community(community) => {
|
||||||
if community.local {
|
if community.local {
|
||||||
|
@ -117,7 +115,6 @@ pub(in crate::activities) async fn receive_remove_action(
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
removed: Some(true),
|
removed: Some(true),
|
||||||
reason,
|
reason,
|
||||||
expires: None,
|
|
||||||
};
|
};
|
||||||
ModRemoveCommunity::create(&mut context.pool(), &form).await?;
|
ModRemoveCommunity::create(&mut context.pool(), &form).await?;
|
||||||
Community::update(
|
Community::update(
|
||||||
|
|
|
@ -107,7 +107,6 @@ impl UndoDelete {
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
removed: Some(false),
|
removed: Some(false),
|
||||||
reason: None,
|
reason: None,
|
||||||
expires: None,
|
|
||||||
};
|
};
|
||||||
ModRemoveCommunity::create(&mut context.pool(), &form).await?;
|
ModRemoveCommunity::create(&mut context.pool(), &form).await?;
|
||||||
Community::update(
|
Community::update(
|
||||||
|
|
|
@ -44,7 +44,7 @@ use lemmy_db_schema::source::{
|
||||||
use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
|
use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{ops::Deref, time::Duration};
|
use std::ops::Deref;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -56,10 +56,6 @@ pub mod deletion;
|
||||||
pub mod following;
|
pub mod following;
|
||||||
pub mod voting;
|
pub mod voting;
|
||||||
|
|
||||||
/// Amount of time that the list of dead instances is cached. This is only updated once a day,
|
|
||||||
/// so there is no harm in caching it for a longer time.
|
|
||||||
pub static DEAD_INSTANCE_LIST_CACHE_DURATION: Duration = Duration::from_secs(30 * 60);
|
|
||||||
|
|
||||||
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
|
/// Checks that the specified Url actually identifies a Person (by fetching it), and that the person
|
||||||
/// doesn't have a site ban.
|
/// doesn't have a site ban.
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
|
@ -92,9 +88,7 @@ pub(crate) async fn verify_person_in_community(
|
||||||
}
|
}
|
||||||
let person_id = person.id;
|
let person_id = person.id;
|
||||||
let community_id = community.id;
|
let community_id = community.id;
|
||||||
let is_banned = CommunityPersonBanView::get(&mut context.pool(), person_id, community_id)
|
let is_banned = CommunityPersonBanView::get(&mut context.pool(), person_id, community_id).await?;
|
||||||
.await
|
|
||||||
.is_ok();
|
|
||||||
if is_banned {
|
if is_banned {
|
||||||
Err(LemmyErrorType::PersonIsBannedFromCommunity)?
|
Err(LemmyErrorType::PersonIsBannedFromCommunity)?
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub mod read_community;
|
||||||
pub mod read_person;
|
pub mod read_person;
|
||||||
pub mod resolve_object;
|
pub mod resolve_object;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
pub mod user_settings_backup;
|
||||||
|
|
||||||
/// Returns default listing type, depending if the query is for frontpage or community.
|
/// Returns default listing type, depending if the query is for frontpage or community.
|
||||||
fn listing_type_with_default(
|
fn listing_type_with_default(
|
||||||
|
|
|
@ -8,7 +8,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{community::Community, local_site::LocalSite},
|
source::{community::Community, local_site::LocalSite},
|
||||||
utils::{post_to_comment_sort_type, post_to_person_sort_type},
|
utils::post_to_comment_sort_type,
|
||||||
SearchType,
|
SearchType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery, structs::LocalUserView};
|
use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery, structs::LocalUserView};
|
||||||
|
@ -101,7 +101,7 @@ pub async fn search(
|
||||||
}
|
}
|
||||||
SearchType::Users => {
|
SearchType::Users => {
|
||||||
users = PersonQuery {
|
users = PersonQuery {
|
||||||
sort: (sort.map(post_to_person_sort_type)),
|
sort,
|
||||||
search_term: (Some(q)),
|
search_term: (Some(q)),
|
||||||
page: (page),
|
page: (page),
|
||||||
limit: (limit),
|
limit: (limit),
|
||||||
|
@ -171,7 +171,7 @@ pub async fn search(
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
PersonQuery {
|
PersonQuery {
|
||||||
sort: (sort.map(post_to_person_sort_type)),
|
sort,
|
||||||
search_term: (Some(q)),
|
search_term: (Some(q)),
|
||||||
page: (page),
|
page: (page),
|
||||||
limit: (limit),
|
limit: (limit),
|
||||||
|
|
|
@ -0,0 +1,441 @@
|
||||||
|
use crate::objects::{
|
||||||
|
comment::ApubComment,
|
||||||
|
community::ApubCommunity,
|
||||||
|
person::ApubPerson,
|
||||||
|
post::ApubPost,
|
||||||
|
};
|
||||||
|
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use futures::{future::try_join_all, StreamExt};
|
||||||
|
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
newtypes::DbUrl,
|
||||||
|
source::{
|
||||||
|
comment::{CommentSaved, CommentSavedForm},
|
||||||
|
community::{CommunityFollower, CommunityFollowerForm},
|
||||||
|
community_block::{CommunityBlock, CommunityBlockForm},
|
||||||
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
|
person::{Person, PersonUpdateForm},
|
||||||
|
person_block::{PersonBlock, PersonBlockForm},
|
||||||
|
post::{PostSaved, PostSavedForm},
|
||||||
|
},
|
||||||
|
traits::{Blockable, Crud, Followable, Saveable},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
error::{LemmyError, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS},
|
||||||
|
spawn_try_task,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
/// Backup of user data. This struct should never be changed so that the data can be used as a
|
||||||
|
/// long-term backup in case the instance goes down unexpectedly. All fields are optional to allow
|
||||||
|
/// importing partial backups.
|
||||||
|
///
|
||||||
|
/// This data should not be parsed by apps/clients, but directly downloaded as a file.
|
||||||
|
///
|
||||||
|
/// Be careful with any changes to this struct, to avoid breaking changes which could prevent
|
||||||
|
/// importing older backups.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct UserSettingsBackup {
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
pub bio: Option<String>,
|
||||||
|
pub avatar: Option<DbUrl>,
|
||||||
|
pub banner: Option<DbUrl>,
|
||||||
|
pub matrix_id: Option<String>,
|
||||||
|
pub bot_account: Option<bool>,
|
||||||
|
// TODO: might be worth making a separate struct for settings backup, to avoid breakage in case
|
||||||
|
// fields are renamed, and to avoid storing unnecessary fields like person_id or email
|
||||||
|
pub settings: Option<LocalUser>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub followed_communities: Vec<ObjectId<ApubCommunity>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub saved_posts: Vec<ObjectId<ApubPost>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub saved_comments: Vec<ObjectId<ApubComment>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub blocked_communities: Vec<ObjectId<ApubCommunity>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub blocked_users: Vec<ObjectId<ApubPerson>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn export_settings(
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> Result<Json<UserSettingsBackup>, LemmyError> {
|
||||||
|
let lists = LocalUser::export_backup(&mut context.pool(), local_user_view.person.id).await?;
|
||||||
|
|
||||||
|
let vec_into = |vec: Vec<_>| vec.into_iter().map(Into::into).collect();
|
||||||
|
Ok(Json(UserSettingsBackup {
|
||||||
|
display_name: local_user_view.person.display_name,
|
||||||
|
bio: local_user_view.person.bio,
|
||||||
|
avatar: local_user_view.person.avatar,
|
||||||
|
banner: local_user_view.person.banner,
|
||||||
|
matrix_id: local_user_view.person.matrix_user_id,
|
||||||
|
bot_account: local_user_view.person.bot_account.into(),
|
||||||
|
settings: Some(local_user_view.local_user),
|
||||||
|
followed_communities: vec_into(lists.followed_communities),
|
||||||
|
blocked_communities: vec_into(lists.blocked_communities),
|
||||||
|
blocked_users: lists.blocked_users.into_iter().map(Into::into).collect(),
|
||||||
|
saved_posts: lists.saved_posts.into_iter().map(Into::into).collect(),
|
||||||
|
saved_comments: lists.saved_comments.into_iter().map(Into::into).collect(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn import_settings(
|
||||||
|
data: Json<UserSettingsBackup>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
|
let person_form = PersonUpdateForm {
|
||||||
|
display_name: Some(data.display_name.clone()),
|
||||||
|
bio: Some(data.bio.clone()),
|
||||||
|
matrix_user_id: Some(data.matrix_id.clone()),
|
||||||
|
bot_account: data.bot_account,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Person::update(&mut context.pool(), local_user_view.person.id, &person_form).await?;
|
||||||
|
|
||||||
|
let local_user_form = LocalUserUpdateForm {
|
||||||
|
show_nsfw: data.settings.as_ref().map(|s| s.show_nsfw),
|
||||||
|
theme: data.settings.as_ref().map(|s| s.theme.clone()),
|
||||||
|
default_sort_type: data.settings.as_ref().map(|s| s.default_sort_type),
|
||||||
|
default_listing_type: data.settings.as_ref().map(|s| s.default_listing_type),
|
||||||
|
interface_language: data.settings.as_ref().map(|s| s.interface_language.clone()),
|
||||||
|
show_avatars: data.settings.as_ref().map(|s| s.show_avatars),
|
||||||
|
send_notifications_to_email: data
|
||||||
|
.settings
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.send_notifications_to_email),
|
||||||
|
show_scores: data.settings.as_ref().map(|s| s.show_scores),
|
||||||
|
show_bot_accounts: data.settings.as_ref().map(|s| s.show_bot_accounts),
|
||||||
|
show_read_posts: data.settings.as_ref().map(|s| s.show_read_posts),
|
||||||
|
open_links_in_new_tab: data.settings.as_ref().map(|s| s.open_links_in_new_tab),
|
||||||
|
blur_nsfw: data.settings.as_ref().map(|s| s.blur_nsfw),
|
||||||
|
auto_expand: data.settings.as_ref().map(|s| s.auto_expand),
|
||||||
|
infinite_scroll_enabled: data.settings.as_ref().map(|s| s.infinite_scroll_enabled),
|
||||||
|
post_listing_mode: data.settings.as_ref().map(|s| s.post_listing_mode),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
LocalUser::update(
|
||||||
|
&mut context.pool(),
|
||||||
|
local_user_view.local_user.id,
|
||||||
|
&local_user_form,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let url_count = data.followed_communities.len()
|
||||||
|
+ data.blocked_communities.len()
|
||||||
|
+ data.blocked_users.len()
|
||||||
|
+ data.saved_posts.len()
|
||||||
|
+ data.saved_comments.len();
|
||||||
|
if url_count > MAX_API_PARAM_ELEMENTS {
|
||||||
|
Err(LemmyErrorType::TooManyItems)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn_try_task(async move {
|
||||||
|
const PARALLELISM: usize = 10;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
|
||||||
|
// These tasks fetch objects from remote instances which might be down.
|
||||||
|
// TODO: Would be nice if we could send a list of failed items with api response, but then
|
||||||
|
// the request would likely timeout.
|
||||||
|
let mut failed_items = vec![];
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Starting settings backup for {}",
|
||||||
|
local_user_view.person.name
|
||||||
|
);
|
||||||
|
|
||||||
|
futures::stream::iter(
|
||||||
|
data
|
||||||
|
.followed_communities
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
// reset_request_count works like clone, and is necessary to avoid running into request limit
|
||||||
|
.map(|f| (f, context.reset_request_count()))
|
||||||
|
.map(|(followed, context)| async move {
|
||||||
|
// need to reset outgoing request count to avoid running into limit
|
||||||
|
let community = followed.dereference(&context).await?;
|
||||||
|
let form = CommunityFollowerForm {
|
||||||
|
person_id,
|
||||||
|
community_id: community.id,
|
||||||
|
pending: true,
|
||||||
|
};
|
||||||
|
CommunityFollower::follow(&mut context.pool(), &form).await?;
|
||||||
|
LemmyResult::Ok(())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.buffer_unordered(PARALLELISM)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, r)| {
|
||||||
|
if let Err(e) = r {
|
||||||
|
failed_items.push(data.followed_communities.get(i).map(|u| u.inner().clone()));
|
||||||
|
info!("Failed to import followed community: {e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
futures::stream::iter(
|
||||||
|
data
|
||||||
|
.saved_posts
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| (s, context.reset_request_count()))
|
||||||
|
.map(|(saved, context)| async move {
|
||||||
|
let post = saved.dereference(&context).await?;
|
||||||
|
let form = PostSavedForm {
|
||||||
|
person_id,
|
||||||
|
post_id: post.id,
|
||||||
|
};
|
||||||
|
PostSaved::save(&mut context.pool(), &form).await?;
|
||||||
|
LemmyResult::Ok(())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.buffer_unordered(PARALLELISM)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, r)| {
|
||||||
|
if let Err(e) = r {
|
||||||
|
failed_items.push(data.followed_communities.get(i).map(|u| u.inner().clone()));
|
||||||
|
info!("Failed to import saved post community: {e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
futures::stream::iter(
|
||||||
|
data
|
||||||
|
.saved_comments
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| (s, context.reset_request_count()))
|
||||||
|
.map(|(saved, context)| async move {
|
||||||
|
let comment = saved.dereference(&context).await?;
|
||||||
|
let form = CommentSavedForm {
|
||||||
|
person_id,
|
||||||
|
comment_id: comment.id,
|
||||||
|
};
|
||||||
|
CommentSaved::save(&mut context.pool(), &form).await?;
|
||||||
|
LemmyResult::Ok(())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.buffer_unordered(PARALLELISM)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, r)| {
|
||||||
|
if let Err(e) = r {
|
||||||
|
failed_items.push(data.followed_communities.get(i).map(|u| u.inner().clone()));
|
||||||
|
info!("Failed to import saved comment community: {e}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let failed_items: Vec<_> = failed_items.into_iter().flatten().collect();
|
||||||
|
info!(
|
||||||
|
"Finished settings backup for {}, failed items: {:#?}",
|
||||||
|
local_user_view.person.name, failed_items
|
||||||
|
);
|
||||||
|
|
||||||
|
// These tasks don't connect to any remote instances but only insert directly in the database.
|
||||||
|
// That means the only error condition are db connection failures, so no extra error handling is
|
||||||
|
// needed.
|
||||||
|
try_join_all(data.blocked_communities.iter().map(|blocked| async {
|
||||||
|
// dont fetch unknown blocked objects from home server
|
||||||
|
let community = blocked.dereference_local(&context).await?;
|
||||||
|
let form = CommunityBlockForm {
|
||||||
|
person_id,
|
||||||
|
community_id: community.id,
|
||||||
|
};
|
||||||
|
CommunityBlock::block(&mut context.pool(), &form).await?;
|
||||||
|
LemmyResult::Ok(())
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
try_join_all(data.blocked_users.iter().map(|blocked| async {
|
||||||
|
// dont fetch unknown blocked objects from home server
|
||||||
|
let target = blocked.dereference_local(&context).await?;
|
||||||
|
let form = PersonBlockForm {
|
||||||
|
person_id,
|
||||||
|
target_id: target.id,
|
||||||
|
};
|
||||||
|
PersonBlock::block(&mut context.pool(), &form).await?;
|
||||||
|
LemmyResult::Ok(())
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Json(Default::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#![allow(clippy::unwrap_used)]
|
||||||
|
#![allow(clippy::indexing_slicing)]
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::user_settings_backup::{export_settings, import_settings},
|
||||||
|
objects::tests::init_context,
|
||||||
|
};
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use lemmy_api_common::context::LemmyContext;
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm},
|
||||||
|
instance::Instance,
|
||||||
|
local_user::{LocalUser, LocalUserInsertForm},
|
||||||
|
person::{Person, PersonInsertForm},
|
||||||
|
},
|
||||||
|
traits::{Crud, Followable},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
|
use lemmy_utils::error::LemmyErrorType;
|
||||||
|
use serial_test::serial;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
async fn create_user(
|
||||||
|
name: String,
|
||||||
|
bio: Option<String>,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
) -> LocalUserView {
|
||||||
|
let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let person_form = PersonInsertForm::builder()
|
||||||
|
.name(name.clone())
|
||||||
|
.display_name(Some(name.clone()))
|
||||||
|
.bio(bio)
|
||||||
|
.public_key("asd".to_string())
|
||||||
|
.instance_id(instance.id)
|
||||||
|
.build();
|
||||||
|
let person = Person::create(&mut context.pool(), &person_form)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let user_form = LocalUserInsertForm::builder()
|
||||||
|
.person_id(person.id)
|
||||||
|
.password_encrypted("pass".to_string())
|
||||||
|
.build();
|
||||||
|
let local_user = LocalUser::create(&mut context.pool(), &user_form)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
LocalUserView::read(&mut context.pool(), local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_settings_export_import() {
|
||||||
|
let context = init_context().await;
|
||||||
|
|
||||||
|
let export_user = create_user("hanna".to_string(), Some("my bio".to_string()), &context).await;
|
||||||
|
|
||||||
|
let community_form = CommunityInsertForm::builder()
|
||||||
|
.name("testcom".to_string())
|
||||||
|
.title("testcom".to_string())
|
||||||
|
.instance_id(export_user.person.instance_id)
|
||||||
|
.build();
|
||||||
|
let community = Community::create(&mut context.pool(), &community_form)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let follower_form = CommunityFollowerForm {
|
||||||
|
community_id: community.id,
|
||||||
|
person_id: export_user.person.id,
|
||||||
|
pending: false,
|
||||||
|
};
|
||||||
|
CommunityFollower::follow(&mut context.pool(), &follower_form)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let backup = export_settings(export_user.clone(), context.reset_request_count())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let import_user = create_user("charles".to_string(), None, &context).await;
|
||||||
|
|
||||||
|
import_settings(backup, import_user.clone(), context.reset_request_count())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// wait for background task to finish
|
||||||
|
sleep(Duration::from_millis(1000)).await;
|
||||||
|
|
||||||
|
let import_user_updated = LocalUserView::read(&mut context.pool(), import_user.local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
export_user.person.display_name,
|
||||||
|
import_user_updated.person.display_name
|
||||||
|
);
|
||||||
|
assert_eq!(export_user.person.bio, import_user_updated.person.bio);
|
||||||
|
|
||||||
|
let follows = CommunityFollowerView::for_person(&mut context.pool(), import_user.person.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(follows.len(), 1);
|
||||||
|
assert_eq!(follows[0].community.actor_id, community.actor_id);
|
||||||
|
|
||||||
|
LocalUser::delete(&mut context.pool(), export_user.local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
LocalUser::delete(&mut context.pool(), import_user.local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn disallow_large_backup() {
|
||||||
|
let context = init_context().await;
|
||||||
|
|
||||||
|
let export_user = create_user("hanna".to_string(), Some("my bio".to_string()), &context).await;
|
||||||
|
|
||||||
|
let mut backup = export_settings(export_user.clone(), context.reset_request_count())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for _ in 0..251 {
|
||||||
|
backup
|
||||||
|
.followed_communities
|
||||||
|
.push("http://example.com".parse().unwrap());
|
||||||
|
backup
|
||||||
|
.blocked_communities
|
||||||
|
.push("http://example2.com".parse().unwrap());
|
||||||
|
backup
|
||||||
|
.saved_posts
|
||||||
|
.push("http://example3.com".parse().unwrap());
|
||||||
|
backup
|
||||||
|
.saved_comments
|
||||||
|
.push("http://example4.com".parse().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let import_user = create_user("charles".to_string(), None, &context).await;
|
||||||
|
|
||||||
|
let imported =
|
||||||
|
import_settings(backup, import_user.clone(), context.reset_request_count()).await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
imported.err().unwrap().error_type,
|
||||||
|
LemmyErrorType::TooManyItems
|
||||||
|
);
|
||||||
|
|
||||||
|
LocalUser::delete(&mut context.pool(), export_user.local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
LocalUser::delete(&mut context.pool(), import_user.local_user.id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::{
|
||||||
|
objects::community::ApubCommunity,
|
||||||
|
protocol::collections::group_followers::GroupFollowers,
|
||||||
|
};
|
||||||
|
use activitypub_federation::{
|
||||||
|
config::Data,
|
||||||
|
kinds::collection::CollectionType,
|
||||||
|
protocol::verification::verify_domains_match,
|
||||||
|
traits::Collection,
|
||||||
|
};
|
||||||
|
use lemmy_api_common::{context::LemmyContext, utils::generate_followers_url};
|
||||||
|
use lemmy_db_schema::aggregates::structs::CommunityAggregates;
|
||||||
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
|
use lemmy_utils::error::LemmyError;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct ApubCommunityFollower(Vec<()>);
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Collection for ApubCommunityFollower {
|
||||||
|
type Owner = ApubCommunity;
|
||||||
|
type DataType = LemmyContext;
|
||||||
|
type Kind = GroupFollowers;
|
||||||
|
type Error = LemmyError;
|
||||||
|
|
||||||
|
async fn read_local(
|
||||||
|
community: &Self::Owner,
|
||||||
|
context: &Data<Self::DataType>,
|
||||||
|
) -> Result<Self::Kind, Self::Error> {
|
||||||
|
let community_id = community.id;
|
||||||
|
let community_followers =
|
||||||
|
CommunityFollowerView::count_community_followers(&mut context.pool(), community_id).await?;
|
||||||
|
|
||||||
|
Ok(GroupFollowers {
|
||||||
|
id: generate_followers_url(&community.actor_id)?.into(),
|
||||||
|
r#type: CollectionType::Collection,
|
||||||
|
total_items: community_followers as i32,
|
||||||
|
items: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify(
|
||||||
|
json: &Self::Kind,
|
||||||
|
expected_domain: &Url,
|
||||||
|
_data: &Data<Self::DataType>,
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
verify_domains_match(expected_domain, &json.id)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn from_json(
|
||||||
|
json: Self::Kind,
|
||||||
|
community: &Self::Owner,
|
||||||
|
context: &Data<Self::DataType>,
|
||||||
|
) -> Result<Self, Self::Error> {
|
||||||
|
CommunityAggregates::update_federated_followers(
|
||||||
|
&mut context.pool(),
|
||||||
|
community.id,
|
||||||
|
json.total_items,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(ApubCommunityFollower(Vec::new()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub(crate) mod community_featured;
|
pub(crate) mod community_featured;
|
||||||
|
pub(crate) mod community_follower;
|
||||||
pub(crate) mod community_moderators;
|
pub(crate) mod community_moderators;
|
||||||
pub(crate) mod community_outbox;
|
pub(crate) mod community_outbox;
|
||||||
|
|
|
@ -2,12 +2,12 @@ use crate::{
|
||||||
activity_lists::GroupInboxActivities,
|
activity_lists::GroupInboxActivities,
|
||||||
collections::{
|
collections::{
|
||||||
community_featured::ApubCommunityFeatured,
|
community_featured::ApubCommunityFeatured,
|
||||||
|
community_follower::ApubCommunityFollower,
|
||||||
community_moderators::ApubCommunityModerators,
|
community_moderators::ApubCommunityModerators,
|
||||||
community_outbox::ApubCommunityOutbox,
|
community_outbox::ApubCommunityOutbox,
|
||||||
},
|
},
|
||||||
http::{create_apub_response, create_apub_tombstone_response},
|
http::{create_apub_response, create_apub_tombstone_response},
|
||||||
objects::{community::ApubCommunity, person::ApubPerson},
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
protocol::collections::group_followers::GroupFollowers,
|
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
actix_web::inbox::receive_activity,
|
actix_web::inbox::receive_activity,
|
||||||
|
@ -66,7 +66,7 @@ pub(crate) async fn get_apub_community_followers(
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
let community =
|
let community =
|
||||||
Community::read_from_name(&mut context.pool(), &info.community_name, false).await?;
|
Community::read_from_name(&mut context.pool(), &info.community_name, false).await?;
|
||||||
let followers = GroupFollowers::new(community, &context).await?;
|
let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?;
|
||||||
create_apub_response(&followers)
|
create_apub_response(&followers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,7 @@ use activitypub_federation::{
|
||||||
traits::Object,
|
traits::Object,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex};
|
||||||
context::LemmyContext,
|
|
||||||
utils::{local_site_opt_to_slur_regex, sanitize_html_federation},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentInsertForm, CommentUpdateForm},
|
comment::{Comment, CommentInsertForm, CommentUpdateForm},
|
||||||
|
@ -32,7 +29,7 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorType},
|
error::{LemmyError, LemmyErrorType},
|
||||||
utils::{markdown::markdown_to_html, slurs::remove_slurs, time::convert_datetime},
|
utils::{markdown::markdown_to_html, slurs::remove_slurs},
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -116,8 +113,8 @@ impl Object for ApubComment {
|
||||||
media_type: Some(MediaTypeMarkdownOrHtml::Html),
|
media_type: Some(MediaTypeMarkdownOrHtml::Html),
|
||||||
source: Some(Source::new(self.content.clone())),
|
source: Some(Source::new(self.content.clone())),
|
||||||
in_reply_to,
|
in_reply_to,
|
||||||
published: Some(convert_datetime(self.published)),
|
published: Some(self.published),
|
||||||
updated: self.updated.map(convert_datetime),
|
updated: self.updated,
|
||||||
tag: maa.tags,
|
tag: maa.tags,
|
||||||
distinguished: Some(self.distinguished),
|
distinguished: Some(self.distinguished),
|
||||||
language,
|
language,
|
||||||
|
@ -162,7 +159,6 @@ impl Object for ApubComment {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
||||||
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
||||||
let content = remove_slurs(&content, slur_regex);
|
let content = remove_slurs(&content, slur_regex);
|
||||||
let content = sanitize_html_federation(&content);
|
|
||||||
let language_id =
|
let language_id =
|
||||||
LanguageTag::to_language_id_single(note.language, &mut context.pool()).await?;
|
LanguageTag::to_language_id_single(note.language, &mut context.pool()).await?;
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,7 @@ use lemmy_db_schema::{
|
||||||
traits::{ApubActor, Crud},
|
traits::{ApubActor, Crud},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{error::LemmyError, utils::markdown::markdown_to_html};
|
||||||
error::LemmyError,
|
|
||||||
utils::{markdown::markdown_to_html, time::convert_datetime},
|
|
||||||
};
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -109,8 +106,8 @@ impl Object for ApubCommunity {
|
||||||
}),
|
}),
|
||||||
public_key: self.public_key(),
|
public_key: self.public_key(),
|
||||||
language,
|
language,
|
||||||
published: Some(convert_datetime(self.published)),
|
published: Some(self.published),
|
||||||
updated: self.updated.map(convert_datetime),
|
updated: self.updated,
|
||||||
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
||||||
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
|
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
|
||||||
};
|
};
|
||||||
|
@ -146,15 +143,19 @@ impl Object for ApubCommunity {
|
||||||
// Fetching mods and outbox is not necessary for Lemmy to work, so ignore errors. Besides,
|
// Fetching mods and outbox is not necessary for Lemmy to work, so ignore errors. Besides,
|
||||||
// we need to ignore these errors so that tests can work entirely offline.
|
// we need to ignore these errors so that tests can work entirely offline.
|
||||||
let fetch_outbox = group.outbox.dereference(&community, context);
|
let fetch_outbox = group.outbox.dereference(&community, context);
|
||||||
|
let fetch_followers = group.followers.dereference(&community, context);
|
||||||
|
|
||||||
if let Some(moderators) = group.attributed_to {
|
if let Some(moderators) = group.attributed_to {
|
||||||
let fetch_moderators = moderators.dereference(&community, context);
|
let fetch_moderators = moderators.dereference(&community, context);
|
||||||
// Fetch mods and outbox in parallel
|
// Fetch mods, outbox and followers in parallel
|
||||||
let res = tokio::join!(fetch_outbox, fetch_moderators);
|
let res = tokio::join!(fetch_outbox, fetch_moderators, fetch_followers);
|
||||||
res.0.map_err(|e| debug!("{}", e)).ok();
|
res.0.map_err(|e| debug!("{}", e)).ok();
|
||||||
res.1.map_err(|e| debug!("{}", e)).ok();
|
res.1.map_err(|e| debug!("{}", e)).ok();
|
||||||
|
res.2.map_err(|e| debug!("{}", e)).ok();
|
||||||
} else {
|
} else {
|
||||||
fetch_outbox.await.map_err(|e| debug!("{}", e)).ok();
|
let res = tokio::join!(fetch_outbox, fetch_followers);
|
||||||
|
res.0.map_err(|e| debug!("{}", e)).ok();
|
||||||
|
res.1.map_err(|e| debug!("{}", e)).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(community)
|
Ok(community)
|
||||||
|
@ -235,12 +236,14 @@ pub(crate) mod tests {
|
||||||
json.attributed_to = None;
|
json.attributed_to = None;
|
||||||
json.outbox =
|
json.outbox =
|
||||||
CollectionId::parse("https://enterprise.lemmy.ml/c/tenforward/not_outbox").unwrap();
|
CollectionId::parse("https://enterprise.lemmy.ml/c/tenforward/not_outbox").unwrap();
|
||||||
|
json.followers =
|
||||||
|
CollectionId::parse("https://enterprise.lemmy.ml/c/tenforward/not_followers").unwrap();
|
||||||
|
|
||||||
let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
|
let url = Url::parse("https://enterprise.lemmy.ml/c/tenforward").unwrap();
|
||||||
ApubCommunity::verify(&json, &url, &context2).await.unwrap();
|
ApubCommunity::verify(&json, &url, &context2).await.unwrap();
|
||||||
let community = ApubCommunity::from_json(json, &context2).await.unwrap();
|
let community = ApubCommunity::from_json(json, &context2).await.unwrap();
|
||||||
// this makes one requests to the (intentionally broken) outbox collection
|
// this makes requests to the (intentionally broken) outbox and followers collections
|
||||||
assert_eq!(context2.request_count(), 1);
|
assert_eq!(context2.request_count(), 2);
|
||||||
community
|
community
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,7 @@ use activitypub_federation::{
|
||||||
traits::{Actor, Object},
|
traits::{Actor, Object},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex};
|
||||||
context::LemmyContext,
|
|
||||||
utils::{local_site_opt_to_slur_regex, sanitize_html_federation_opt},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::InstanceId,
|
newtypes::InstanceId,
|
||||||
source::{
|
source::{
|
||||||
|
@ -37,7 +34,6 @@ use lemmy_utils::{
|
||||||
utils::{
|
utils::{
|
||||||
markdown::markdown_to_html,
|
markdown::markdown_to_html,
|
||||||
slurs::{check_slurs, check_slurs_opt},
|
slurs::{check_slurs, check_slurs_opt},
|
||||||
time::convert_datetime,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -106,8 +102,8 @@ impl Object for ApubSite {
|
||||||
outbox: Url::parse(&format!("{}/site_outbox", self.actor_id))?,
|
outbox: Url::parse(&format!("{}/site_outbox", self.actor_id))?,
|
||||||
public_key: self.public_key(),
|
public_key: self.public_key(),
|
||||||
language,
|
language,
|
||||||
published: convert_datetime(self.published),
|
published: self.published,
|
||||||
updated: self.updated.map(convert_datetime),
|
updated: self.updated,
|
||||||
};
|
};
|
||||||
Ok(instance)
|
Ok(instance)
|
||||||
}
|
}
|
||||||
|
@ -135,8 +131,6 @@ impl Object for ApubSite {
|
||||||
let instance = DbInstance::read_or_create(&mut data.pool(), domain.to_string()).await?;
|
let instance = DbInstance::read_or_create(&mut data.pool(), domain.to_string()).await?;
|
||||||
|
|
||||||
let sidebar = read_from_string_or_source_opt(&apub.content, &None, &apub.source);
|
let sidebar = read_from_string_or_source_opt(&apub.content, &None, &apub.source);
|
||||||
let sidebar = sanitize_html_federation_opt(&sidebar);
|
|
||||||
let description = sanitize_html_federation_opt(&apub.summary);
|
|
||||||
|
|
||||||
let site_form = SiteInsertForm {
|
let site_form = SiteInsertForm {
|
||||||
name: apub.name.clone(),
|
name: apub.name.clone(),
|
||||||
|
@ -144,7 +138,7 @@ impl Object for ApubSite {
|
||||||
updated: apub.updated,
|
updated: apub.updated,
|
||||||
icon: apub.icon.clone().map(|i| i.url.into()),
|
icon: apub.icon.clone().map(|i| i.url.into()),
|
||||||
banner: apub.image.clone().map(|i| i.url.into()),
|
banner: apub.image.clone().map(|i| i.url.into()),
|
||||||
description,
|
description: apub.summary,
|
||||||
actor_id: Some(apub.id.clone().into()),
|
actor_id: Some(apub.id.clone().into()),
|
||||||
last_refreshed_at: Some(naive_now()),
|
last_refreshed_at: Some(naive_now()),
|
||||||
inbox_url: Some(apub.inbox.clone().into()),
|
inbox_url: Some(apub.inbox.clone().into()),
|
||||||
|
|
|
@ -61,10 +61,7 @@ pub(crate) mod tests {
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use lemmy_api_common::{context::LemmyContext, request::build_user_agent};
|
use lemmy_api_common::{context::LemmyContext, request::build_user_agent};
|
||||||
use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool_for_tests};
|
use lemmy_db_schema::{source::secret::Secret, utils::build_db_pool_for_tests};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{rate_limit::RateLimitCell, settings::SETTINGS};
|
||||||
rate_limit::{RateLimitCell, RateLimitConfig},
|
|
||||||
settings::SETTINGS,
|
|
||||||
};
|
|
||||||
use reqwest::{Client, Request, Response};
|
use reqwest::{Client, Request, Response};
|
||||||
use reqwest_middleware::{ClientBuilder, Middleware, Next};
|
use reqwest_middleware::{ClientBuilder, Middleware, Next};
|
||||||
use task_local_extensions::Extensions;
|
use task_local_extensions::Extensions;
|
||||||
|
@ -101,8 +98,7 @@ pub(crate) mod tests {
|
||||||
jwt_secret: String::new(),
|
jwt_secret: String::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let rate_limit_config = RateLimitConfig::builder().build();
|
let rate_limit_cell = RateLimitCell::with_test_config();
|
||||||
let rate_limit_cell = RateLimitCell::new(rate_limit_config).await;
|
|
||||||
|
|
||||||
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
|
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
|
||||||
let config = FederationConfig::builder()
|
let config = FederationConfig::builder()
|
||||||
|
|
|
@ -20,12 +20,7 @@ use activitypub_federation::{
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{
|
utils::{generate_outbox_url, local_site_opt_to_slur_regex},
|
||||||
generate_outbox_url,
|
|
||||||
local_site_opt_to_slur_regex,
|
|
||||||
sanitize_html_federation,
|
|
||||||
sanitize_html_federation_opt,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -40,7 +35,6 @@ use lemmy_utils::{
|
||||||
utils::{
|
utils::{
|
||||||
markdown::markdown_to_html,
|
markdown::markdown_to_html,
|
||||||
slurs::{check_slurs, check_slurs_opt},
|
slurs::{check_slurs, check_slurs_opt},
|
||||||
time::convert_datetime,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -112,13 +106,13 @@ impl Object for ApubPerson {
|
||||||
icon: self.avatar.clone().map(ImageObject::new),
|
icon: self.avatar.clone().map(ImageObject::new),
|
||||||
image: self.banner.clone().map(ImageObject::new),
|
image: self.banner.clone().map(ImageObject::new),
|
||||||
matrix_user_id: self.matrix_user_id.clone(),
|
matrix_user_id: self.matrix_user_id.clone(),
|
||||||
published: Some(convert_datetime(self.published)),
|
published: Some(self.published),
|
||||||
outbox: generate_outbox_url(&self.actor_id)?.into(),
|
outbox: generate_outbox_url(&self.actor_id)?.into(),
|
||||||
endpoints: self.shared_inbox_url.clone().map(|s| Endpoints {
|
endpoints: self.shared_inbox_url.clone().map(|s| Endpoints {
|
||||||
shared_inbox: s.into(),
|
shared_inbox: s.into(),
|
||||||
}),
|
}),
|
||||||
public_key: self.public_key(),
|
public_key: self.public_key(),
|
||||||
updated: self.updated.map(convert_datetime),
|
updated: self.updated,
|
||||||
inbox: self.inbox_url.clone().into(),
|
inbox: self.inbox_url.clone().into(),
|
||||||
};
|
};
|
||||||
Ok(person)
|
Ok(person)
|
||||||
|
@ -150,17 +144,14 @@ impl Object for ApubPerson {
|
||||||
) -> Result<ApubPerson, LemmyError> {
|
) -> Result<ApubPerson, LemmyError> {
|
||||||
let instance_id = fetch_instance_actor_for_object(&person.id, context).await?;
|
let instance_id = fetch_instance_actor_for_object(&person.id, context).await?;
|
||||||
|
|
||||||
let name = sanitize_html_federation(&person.preferred_username);
|
|
||||||
let display_name = sanitize_html_federation_opt(&person.name);
|
|
||||||
let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source);
|
let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source);
|
||||||
let bio = sanitize_html_federation_opt(&bio);
|
|
||||||
|
|
||||||
// Some Mastodon users have `name: ""` (empty string), need to convert that to `None`
|
// Some Mastodon users have `name: ""` (empty string), need to convert that to `None`
|
||||||
// https://github.com/mastodon/mastodon/issues/25233
|
// https://github.com/mastodon/mastodon/issues/25233
|
||||||
let display_name = display_name.filter(|n| !n.is_empty());
|
let display_name = person.name.filter(|n| !n.is_empty());
|
||||||
|
|
||||||
let person_form = PersonInsertForm {
|
let person_form = PersonInsertForm {
|
||||||
name,
|
name: person.preferred_username,
|
||||||
display_name,
|
display_name,
|
||||||
banned: None,
|
banned: None,
|
||||||
ban_expires: None,
|
ban_expires: None,
|
||||||
|
@ -275,7 +266,7 @@ pub(crate) mod tests {
|
||||||
assert_eq!(person.name, "lanodan");
|
assert_eq!(person.name, "lanodan");
|
||||||
assert!(!person.local);
|
assert!(!person.local);
|
||||||
assert_eq!(context.request_count(), 0);
|
assert_eq!(context.request_count(), 0);
|
||||||
assert_eq!(person.bio.as_ref().unwrap().len(), 878);
|
assert_eq!(person.bio.as_ref().unwrap().len(), 873);
|
||||||
|
|
||||||
cleanup((person, site), &context).await;
|
cleanup((person, site), &context).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,17 +21,11 @@ use activitypub_federation::{
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use html2md::parse_html;
|
use html2text::{from_read_with_decorator, render::text_renderer::TrivialDecorator};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
request::fetch_site_data,
|
request::fetch_site_data,
|
||||||
utils::{
|
utils::{is_mod_or_admin, local_site_opt_to_sensitive, local_site_opt_to_slur_regex},
|
||||||
is_mod_or_admin,
|
|
||||||
local_site_opt_to_sensitive,
|
|
||||||
local_site_opt_to_slur_regex,
|
|
||||||
sanitize_html_federation,
|
|
||||||
sanitize_html_federation_opt,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
self,
|
self,
|
||||||
|
@ -49,11 +43,11 @@ use lemmy_utils::{
|
||||||
utils::{
|
utils::{
|
||||||
markdown::markdown_to_html,
|
markdown::markdown_to_html,
|
||||||
slurs::{check_slurs_opt, remove_slurs},
|
slurs::{check_slurs_opt, remove_slurs},
|
||||||
time::convert_datetime,
|
|
||||||
validation::check_url_scheme,
|
validation::check_url_scheme,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
use stringreader::StringReader;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const MAX_TITLE_LENGTH: usize = 200;
|
const MAX_TITLE_LENGTH: usize = 200;
|
||||||
|
@ -132,8 +126,8 @@ impl Object for ApubPost {
|
||||||
comments_enabled: Some(!self.locked),
|
comments_enabled: Some(!self.locked),
|
||||||
sensitive: Some(self.nsfw),
|
sensitive: Some(self.nsfw),
|
||||||
language,
|
language,
|
||||||
published: Some(convert_datetime(self.published)),
|
published: Some(self.published),
|
||||||
updated: self.updated.map(convert_datetime),
|
updated: self.updated,
|
||||||
audience: Some(community.actor_id.into()),
|
audience: Some(community.actor_id.into()),
|
||||||
in_reply_to: None,
|
in_reply_to: None,
|
||||||
};
|
};
|
||||||
|
@ -171,17 +165,27 @@ impl Object for ApubPost {
|
||||||
let creator = page.creator()?.dereference(context).await?;
|
let creator = page.creator()?.dereference(context).await?;
|
||||||
let community = page.community(context).await?;
|
let community = page.community(context).await?;
|
||||||
if community.posting_restricted_to_mods {
|
if community.posting_restricted_to_mods {
|
||||||
is_mod_or_admin(&mut context.pool(), creator.id, community.id).await?;
|
is_mod_or_admin(&mut context.pool(), &creator, community.id).await?;
|
||||||
}
|
}
|
||||||
let mut name = page
|
let mut name = page
|
||||||
.name
|
.name
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
|
// Posts coming from Mastodon or similar platforms don't have a title. Instead we take the
|
||||||
|
// first line of the content and convert it from HTML to plaintext. We also remove mentions
|
||||||
|
// of the community name.
|
||||||
page
|
page
|
||||||
.content
|
.content
|
||||||
.clone()
|
.as_deref()
|
||||||
.as_ref()
|
.map(StringReader::new)
|
||||||
.and_then(|c| parse_html(c).lines().next().map(ToString::to_string))
|
.map(|c| from_read_with_decorator(c, MAX_TITLE_LENGTH, TrivialDecorator::new()))
|
||||||
|
.and_then(|c| {
|
||||||
|
c.lines().next().map(|s| {
|
||||||
|
s.replace(&format!("@{}", community.name), "")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.ok_or_else(|| anyhow!("Object must have name or content"))?;
|
.ok_or_else(|| anyhow!("Object must have name or content"))?;
|
||||||
if name.chars().count() > MAX_TITLE_LENGTH {
|
if name.chars().count() > MAX_TITLE_LENGTH {
|
||||||
|
@ -231,17 +235,11 @@ impl Object for ApubPost {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
||||||
|
|
||||||
let body_slurs_removed =
|
let body = read_from_string_or_source_opt(&page.content, &page.media_type, &page.source)
|
||||||
read_from_string_or_source_opt(&page.content, &page.media_type, &page.source)
|
|
||||||
.map(|s| remove_slurs(&s, slur_regex));
|
.map(|s| remove_slurs(&s, slur_regex));
|
||||||
let language_id =
|
let language_id =
|
||||||
LanguageTag::to_language_id_single(page.language, &mut context.pool()).await?;
|
LanguageTag::to_language_id_single(page.language, &mut context.pool()).await?;
|
||||||
|
|
||||||
let name = sanitize_html_federation(&name);
|
|
||||||
let body = sanitize_html_federation_opt(&body_slurs_removed);
|
|
||||||
let embed_title = sanitize_html_federation_opt(&embed_title);
|
|
||||||
let embed_description = sanitize_html_federation_opt(&embed_description);
|
|
||||||
|
|
||||||
PostInsertForm {
|
PostInsertForm {
|
||||||
name,
|
name,
|
||||||
url: url.map(Into::into),
|
url: url.map(Into::into),
|
||||||
|
@ -300,8 +298,9 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::{
|
objects::{
|
||||||
community::tests::parse_lemmy_community,
|
community::{tests::parse_lemmy_community, ApubCommunity},
|
||||||
person::tests::parse_lemmy_person,
|
instance::ApubSite,
|
||||||
|
person::{tests::parse_lemmy_person, ApubPerson},
|
||||||
post::ApubPost,
|
post::ApubPost,
|
||||||
tests::init_context,
|
tests::init_context,
|
||||||
},
|
},
|
||||||
|
@ -330,6 +329,31 @@ mod tests {
|
||||||
assert!(!post.featured_community);
|
assert!(!post.featured_community);
|
||||||
assert_eq!(context.request_count(), 0);
|
assert_eq!(context.request_count(), 0);
|
||||||
|
|
||||||
|
cleanup(&context, person, site, community, post).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_convert_mastodon_post_title() {
|
||||||
|
let context = init_context().await;
|
||||||
|
let (person, site) = parse_lemmy_person(&context).await;
|
||||||
|
let community = parse_lemmy_community(&context).await;
|
||||||
|
|
||||||
|
let json = file_to_json_object("assets/mastodon/objects/page.json").unwrap();
|
||||||
|
let post = ApubPost::from_json(json, &context).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(post.name, "Variable never resetting at refresh");
|
||||||
|
|
||||||
|
cleanup(&context, person, site, community, post).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cleanup(
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
person: ApubPerson,
|
||||||
|
site: ApubSite,
|
||||||
|
community: ApubCommunity,
|
||||||
|
post: ApubPost,
|
||||||
|
) {
|
||||||
Post::delete(&mut context.pool(), post.id).await.unwrap();
|
Post::delete(&mut context.pool(), post.id).await.unwrap();
|
||||||
Person::delete(&mut context.pool(), person.id)
|
Person::delete(&mut context.pool(), person.id)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -12,10 +12,7 @@ use activitypub_federation::{
|
||||||
traits::Object,
|
traits::Object,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, utils::check_person_block};
|
||||||
context::LemmyContext,
|
|
||||||
utils::{check_person_block, sanitize_html_federation},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
person::Person,
|
person::Person,
|
||||||
|
@ -25,7 +22,7 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorType},
|
error::{LemmyError, LemmyErrorType},
|
||||||
utils::{markdown::markdown_to_html, time::convert_datetime},
|
utils::markdown::markdown_to_html,
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -89,8 +86,8 @@ impl Object for ApubPrivateMessage {
|
||||||
content: markdown_to_html(&self.content),
|
content: markdown_to_html(&self.content),
|
||||||
media_type: Some(MediaTypeHtml::Html),
|
media_type: Some(MediaTypeHtml::Html),
|
||||||
source: Some(Source::new(self.content.clone())),
|
source: Some(Source::new(self.content.clone())),
|
||||||
published: Some(convert_datetime(self.published)),
|
published: Some(self.published),
|
||||||
updated: self.updated.map(convert_datetime),
|
updated: self.updated,
|
||||||
};
|
};
|
||||||
Ok(note)
|
Ok(note)
|
||||||
}
|
}
|
||||||
|
@ -125,7 +122,6 @@ impl Object for ApubPrivateMessage {
|
||||||
check_person_block(creator.id, recipient.id, &mut context.pool()).await?;
|
check_person_block(creator.id, recipient.id, &mut context.pool()).await?;
|
||||||
|
|
||||||
let content = read_from_string_or_source(¬e.content, &None, ¬e.source);
|
let content = read_from_string_or_source(¬e.content, &None, ¬e.source);
|
||||||
let content = sanitize_html_federation(&content);
|
|
||||||
|
|
||||||
let form = PrivateMessageInsertForm {
|
let form = PrivateMessageInsertForm {
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
|
|
|
@ -1,34 +1,12 @@
|
||||||
use activitypub_federation::kinds::collection::CollectionType;
|
use activitypub_federation::kinds::collection::CollectionType;
|
||||||
use lemmy_api_common::{context::LemmyContext, utils::generate_followers_url};
|
|
||||||
use lemmy_db_schema::source::community::Community;
|
|
||||||
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
|
||||||
use lemmy_utils::error::LemmyError;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct GroupFollowers {
|
pub(crate) struct GroupFollowers {
|
||||||
id: Url,
|
pub(crate) id: Url,
|
||||||
r#type: CollectionType,
|
pub(crate) r#type: CollectionType,
|
||||||
total_items: i32,
|
pub(crate) total_items: i32,
|
||||||
items: Vec<()>,
|
pub(crate) items: Vec<()>,
|
||||||
}
|
|
||||||
|
|
||||||
impl GroupFollowers {
|
|
||||||
pub(crate) async fn new(
|
|
||||||
community: Community,
|
|
||||||
context: &LemmyContext,
|
|
||||||
) -> Result<GroupFollowers, LemmyError> {
|
|
||||||
let community_id = community.id;
|
|
||||||
let community_followers =
|
|
||||||
CommunityFollowerView::count_community_followers(&mut context.pool(), community_id).await?;
|
|
||||||
|
|
||||||
Ok(GroupFollowers {
|
|
||||||
id: generate_followers_url(&community.actor_id)?.into(),
|
|
||||||
r#type: CollectionType::Collection,
|
|
||||||
total_items: community_followers as i32,
|
|
||||||
items: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::{
|
||||||
check_apub_id_valid_with_strictness,
|
check_apub_id_valid_with_strictness,
|
||||||
collections::{
|
collections::{
|
||||||
community_featured::ApubCommunityFeatured,
|
community_featured::ApubCommunityFeatured,
|
||||||
|
community_follower::ApubCommunityFollower,
|
||||||
community_moderators::ApubCommunityModerators,
|
community_moderators::ApubCommunityModerators,
|
||||||
community_outbox::ApubCommunityOutbox,
|
community_outbox::ApubCommunityOutbox,
|
||||||
},
|
},
|
||||||
|
@ -23,10 +24,7 @@ use activitypub_federation::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, utils::local_site_opt_to_slur_regex};
|
||||||
context::LemmyContext,
|
|
||||||
utils::{local_site_opt_to_slur_regex, sanitize_html_federation, sanitize_html_federation_opt},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::InstanceId,
|
newtypes::InstanceId,
|
||||||
source::community::{CommunityInsertForm, CommunityUpdateForm},
|
source::community::{CommunityInsertForm, CommunityUpdateForm},
|
||||||
|
@ -51,7 +49,7 @@ pub struct Group {
|
||||||
/// username, set at account creation and usually fixed after that
|
/// username, set at account creation and usually fixed after that
|
||||||
pub(crate) preferred_username: String,
|
pub(crate) preferred_username: String,
|
||||||
pub(crate) inbox: Url,
|
pub(crate) inbox: Url,
|
||||||
pub(crate) followers: Url,
|
pub(crate) followers: CollectionId<ApubCommunityFollower>,
|
||||||
pub(crate) public_key: PublicKey,
|
pub(crate) public_key: PublicKey,
|
||||||
|
|
||||||
/// title
|
/// title
|
||||||
|
@ -97,14 +95,11 @@ impl Group {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn into_insert_form(self, instance_id: InstanceId) -> CommunityInsertForm {
|
pub(crate) fn into_insert_form(self, instance_id: InstanceId) -> CommunityInsertForm {
|
||||||
let name = sanitize_html_federation(&self.preferred_username);
|
|
||||||
let title = sanitize_html_federation(&self.name.unwrap_or(self.preferred_username));
|
|
||||||
let description = read_from_string_or_source_opt(&self.summary, &None, &self.source);
|
let description = read_from_string_or_source_opt(&self.summary, &None, &self.source);
|
||||||
let description = sanitize_html_federation_opt(&description);
|
|
||||||
|
|
||||||
CommunityInsertForm {
|
CommunityInsertForm {
|
||||||
name,
|
name: self.preferred_username.clone(),
|
||||||
title,
|
title: self.name.unwrap_or(self.preferred_username.clone()),
|
||||||
description,
|
description,
|
||||||
removed: None,
|
removed: None,
|
||||||
published: self.published,
|
published: self.published,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue