mirror of
https://github.com/LemmyNet/lemmy
synced 2024-11-10 06:54:12 +00:00
Merge branch 'main' into no-redundant-post-id
This commit is contained in:
commit
a017142daa
228 changed files with 6488 additions and 3922 deletions
|
@ -2,9 +2,14 @@
|
|||
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
||||
|
||||
variables:
|
||||
- &rust_image "rust:1.77"
|
||||
- &rust_image "rust:1.80"
|
||||
- &rust_nightly_image "rustlang/rust:nightly"
|
||||
- &install_pnpm "corepack enable pnpm"
|
||||
- &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin"
|
||||
- install_diesel_cli: &install_diesel_cli
|
||||
- apt-get update && apt-get install -y postgresql-client
|
||||
- cargo install diesel_cli --no-default-features --features postgres
|
||||
- export PATH="$CARGO_HOME/bin:$PATH"
|
||||
- &slow_check_paths
|
||||
- event: pull_request
|
||||
path:
|
||||
|
@ -25,17 +30,6 @@ variables:
|
|||
"diesel.toml",
|
||||
".gitmodules",
|
||||
]
|
||||
- install_binstall: &install_binstall
|
||||
- wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
|
||||
- cp cargo-binstall /usr/local/cargo/bin
|
||||
- install_diesel_cli: &install_diesel_cli
|
||||
- apt update && apt install -y lsb-release build-essential
|
||||
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
- apt update && apt install -y postgresql-client-16
|
||||
- cargo install diesel_cli --no-default-features --features postgres
|
||||
- export PATH="$CARGO_HOME/bin:$PATH"
|
||||
|
||||
steps:
|
||||
prepare_repo:
|
||||
|
@ -82,7 +76,7 @@ steps:
|
|||
cargo_machete:
|
||||
image: *rust_nightly_image
|
||||
commands:
|
||||
- <<: *install_binstall
|
||||
- *install_binstall
|
||||
- cargo binstall -y cargo-machete
|
||||
- cargo machete
|
||||
when:
|
||||
|
@ -163,7 +157,7 @@ steps:
|
|||
CARGO_HOME: .cargo_home
|
||||
commands:
|
||||
- rustup component add clippy
|
||||
- cargo clippy --workspace --tests --all-targets --features console -- -D warnings
|
||||
- cargo clippy --workspace --tests --all-targets -- -D warnings
|
||||
when: *slow_check_paths
|
||||
|
||||
cargo_build:
|
||||
|
@ -181,6 +175,7 @@ steps:
|
|||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||
RUST_BACKTRACE: "1"
|
||||
CARGO_HOME: .cargo_home
|
||||
LEMMY_TEST_FAST_FEDERATION: "1"
|
||||
commands:
|
||||
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
||||
- cargo test --workspace --no-fail-fast
|
||||
|
@ -203,11 +198,6 @@ steps:
|
|||
- <<: *install_diesel_cli
|
||||
# Run all migrations
|
||||
- diesel migration run
|
||||
# Dump schema to before.sqldump (PostgreSQL apt repo is used to prevent pg_dump version mismatch error)
|
||||
- apt update && apt install -y lsb-release
|
||||
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
|
||||
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
- apt update && apt install -y postgresql-client-16
|
||||
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
|
||||
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f before.sqldump
|
||||
# Make sure that the newest migration is revertable without the `r` schema
|
||||
|
@ -231,7 +221,7 @@ steps:
|
|||
DO_WRITE_HOSTS_FILE: "1"
|
||||
commands:
|
||||
- *install_pnpm
|
||||
- apt update && apt install -y bash curl postgresql-client
|
||||
- apt-get update && apt-get install -y bash curl postgresql-client
|
||||
- bash api_tests/prepare-drone-federation-test.sh
|
||||
- cd api_tests/
|
||||
- pnpm i
|
||||
|
@ -250,10 +240,13 @@ steps:
|
|||
|
||||
publish_release_docker:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
secrets: [docker_username, docker_password]
|
||||
settings:
|
||||
repo: dessalines/lemmy
|
||||
dockerfile: docker/Dockerfile
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
platforms: linux/amd64, linux/arm64
|
||||
build_args:
|
||||
- RUST_RELEASE_MODE=release
|
||||
|
@ -263,10 +256,13 @@ steps:
|
|||
|
||||
nightly_build:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
secrets: [docker_username, docker_password]
|
||||
settings:
|
||||
repo: dessalines/lemmy
|
||||
dockerfile: docker/Dockerfile
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build_args:
|
||||
- RUST_RELEASE_MODE=release
|
||||
|
@ -278,7 +274,7 @@ steps:
|
|||
publish_to_crates_io:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- <<: *install_binstall
|
||||
- *install_binstall
|
||||
# Install cargo-workspaces
|
||||
- cargo binstall -y cargo-workspaces
|
||||
- cp -r migrations crates/db_schema/
|
||||
|
@ -306,7 +302,8 @@ steps:
|
|||
|
||||
services:
|
||||
database:
|
||||
image: postgres:16-alpine
|
||||
# 15-alpine image necessary because of diesel tests
|
||||
image: pgautoupgrade/pgautoupgrade:15-alpine
|
||||
environment:
|
||||
POSTGRES_USER: lemmy
|
||||
POSTGRES_PASSWORD: password
|
||||
|
|
1883
Cargo.lock
generated
1883
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
97
Cargo.toml
97
Cargo.toml
|
@ -1,5 +1,5 @@
|
|||
[workspace.package]
|
||||
version = "0.19.4-rc.3"
|
||||
version = "0.19.6-beta.7"
|
||||
edition = "2021"
|
||||
description = "A link aggregator for the fediverse"
|
||||
license = "AGPL-3.0"
|
||||
|
@ -37,15 +37,6 @@ debug = 0
|
|||
|
||||
[features]
|
||||
embed-pictrs = ["pict-rs"]
|
||||
# This feature requires building with `tokio_unstable` flag, see documentation:
|
||||
# https://docs.rs/tokio/latest/tokio/#unstable-features
|
||||
console = [
|
||||
"console-subscriber",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"tracing-opentelemetry",
|
||||
"reqwest-tracing/opentelemetry_0_16",
|
||||
]
|
||||
json-log = ["tracing-subscriber/json"]
|
||||
default = []
|
||||
|
||||
|
@ -86,30 +77,31 @@ suspicious = { level = "deny", priority = -1 }
|
|||
uninlined_format_args = "allow"
|
||||
unused_self = "deny"
|
||||
unwrap_used = "deny"
|
||||
unimplemented = "deny"
|
||||
|
||||
[workspace.dependencies]
|
||||
lemmy_api = { version = "=0.19.4-rc.3", path = "./crates/api" }
|
||||
lemmy_api_crud = { version = "=0.19.4-rc.3", path = "./crates/api_crud" }
|
||||
lemmy_apub = { version = "=0.19.4-rc.3", path = "./crates/apub" }
|
||||
lemmy_utils = { version = "=0.19.4-rc.3", path = "./crates/utils", default-features = false }
|
||||
lemmy_db_schema = { version = "=0.19.4-rc.3", path = "./crates/db_schema" }
|
||||
lemmy_api_common = { version = "=0.19.4-rc.3", path = "./crates/api_common" }
|
||||
lemmy_routes = { version = "=0.19.4-rc.3", path = "./crates/routes" }
|
||||
lemmy_db_views = { version = "=0.19.4-rc.3", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.19.4-rc.3", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.19.4-rc.3", path = "./crates/db_views_moderator" }
|
||||
lemmy_federate = { version = "=0.19.4-rc.3", path = "./crates/federate" }
|
||||
activitypub_federation = { version = "0.5.6", default-features = false, features = [
|
||||
lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" }
|
||||
lemmy_api_crud = { version = "=0.19.6-beta.7", path = "./crates/api_crud" }
|
||||
lemmy_apub = { version = "=0.19.6-beta.7", path = "./crates/apub" }
|
||||
lemmy_utils = { version = "=0.19.6-beta.7", path = "./crates/utils", default-features = false }
|
||||
lemmy_db_schema = { version = "=0.19.6-beta.7", path = "./crates/db_schema" }
|
||||
lemmy_api_common = { version = "=0.19.6-beta.7", path = "./crates/api_common" }
|
||||
lemmy_routes = { version = "=0.19.6-beta.7", path = "./crates/routes" }
|
||||
lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
|
||||
lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
|
||||
lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
|
||||
lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
|
||||
activitypub_federation = { version = "0.6.0-alpha1", default-features = false, features = [
|
||||
"actix-web",
|
||||
] }
|
||||
diesel = "2.1.6"
|
||||
diesel_migrations = "2.1.0"
|
||||
diesel-async = "0.4.1"
|
||||
serde = { version = "1.0.202", features = ["derive"] }
|
||||
serde_with = "3.8.1"
|
||||
actix-web = { version = "4.6.0", default-features = false, features = [
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_with = "3.9.0"
|
||||
actix-web = { version = "4.9.0", default-features = false, features = [
|
||||
"macros",
|
||||
"rustls",
|
||||
"rustls-0_23",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
|
@ -117,56 +109,56 @@ actix-web = { version = "4.6.0", default-features = false, features = [
|
|||
] }
|
||||
tracing = "0.1.40"
|
||||
tracing-actix-web = { version = "0.7.10", default-features = false }
|
||||
tracing-error = "0.2.0"
|
||||
tracing-log = "0.2.0"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
reqwest = { version = "0.11.27", features = ["json", "blocking", "gzip"] }
|
||||
reqwest-middleware = "0.2.5"
|
||||
reqwest-tracing = "0.4.8"
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
reqwest = { version = "0.12.7", default-features = false, features = [
|
||||
"json",
|
||||
"blocking",
|
||||
"gzip",
|
||||
"rustls-tls",
|
||||
] }
|
||||
reqwest-middleware = "0.3.3"
|
||||
reqwest-tracing = "0.5.3"
|
||||
clokwerk = "0.4.0"
|
||||
doku = { version = "0.21.1", features = ["url-2"] }
|
||||
bcrypt = "0.15.1"
|
||||
chrono = { version = "0.4.38", features = ["serde"], default-features = false }
|
||||
serde_json = { version = "1.0.117", features = ["preserve_order"] }
|
||||
serde_json = { version = "1.0.121", features = ["preserve_order"] }
|
||||
base64 = "0.22.1"
|
||||
uuid = { version = "1.8.0", features = ["serde", "v4"] }
|
||||
async-trait = "0.1.80"
|
||||
uuid = { version = "1.10.0", features = ["serde", "v4"] }
|
||||
async-trait = "0.1.81"
|
||||
captcha = "0.0.9"
|
||||
anyhow = { version = "1.0.86", features = [
|
||||
"backtrace",
|
||||
] } # backtrace is on by default on nightly, but not stable rust
|
||||
diesel_ltree = "0.3.1"
|
||||
typed-builder = "0.18.2"
|
||||
typed-builder = "0.19.1"
|
||||
serial_test = "3.1.1"
|
||||
tokio = { version = "1.37.0", features = ["full"] }
|
||||
regex = "1.10.4"
|
||||
once_cell = "1.19.0"
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
regex = "1.10.5"
|
||||
diesel-derive-newtype = "2.1.2"
|
||||
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||
strum = "0.26.2"
|
||||
strum_macros = "0.26.2"
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
itertools = "0.13.0"
|
||||
futures = "0.3.30"
|
||||
http = "0.2.12"
|
||||
http = "1.1"
|
||||
rosetta-i18n = "0.1.3"
|
||||
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
|
||||
tracing-opentelemetry = { version = "0.19.0" }
|
||||
ts-rs = { version = "7.1.1", features = [
|
||||
"serde-compat",
|
||||
"chrono-impl",
|
||||
"no-serde-warnings",
|
||||
] }
|
||||
rustls = { version = "0.23.8", features = ["ring"] }
|
||||
rustls = { version = "0.23.12", features = ["ring"] }
|
||||
futures-util = "0.3.30"
|
||||
tokio-postgres = "0.7.10"
|
||||
tokio-postgres = "0.7.11"
|
||||
tokio-postgres-rustls = "0.12.0"
|
||||
urlencoding = "2.1.3"
|
||||
enum-map = "2.7"
|
||||
moka = { version = "0.12.7", features = ["future"] }
|
||||
moka = { version = "0.12.8", features = ["future"] }
|
||||
i-love-jesus = { version = "0.1.0" }
|
||||
clap = { version = "4.5.4", features = ["derive", "env"] }
|
||||
clap = { version = "4.5.13", features = ["derive", "env"] }
|
||||
pretty_assertions = "1.4.0"
|
||||
derive-new = "0.7.0"
|
||||
|
||||
[dependencies]
|
||||
lemmy_api = { workspace = true }
|
||||
|
@ -183,8 +175,6 @@ diesel-async = { workspace = true }
|
|||
actix-web = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-actix-web = { workspace = true }
|
||||
tracing-error = { workspace = true }
|
||||
tracing-log = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
url = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
|
@ -192,11 +182,8 @@ reqwest-middleware = { workspace = true }
|
|||
reqwest-tracing = { workspace = true }
|
||||
clokwerk = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tracing-opentelemetry = { workspace = true, optional = true }
|
||||
opentelemetry = { workspace = true, optional = true }
|
||||
console-subscriber = { version = "0.1.10", optional = true }
|
||||
opentelemetry-otlp = { version = "0.12.0", optional = true }
|
||||
pict-rs = { version = "0.5.14", optional = true }
|
||||
pict-rs = { version = "0.5.16", optional = true }
|
||||
rustls = { workspace = true }
|
||||
tokio.workspace = true
|
||||
actix-cors = "0.7.0"
|
||||
futures-util = { workspace = true }
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json",
|
||||
"warnOnUnsupportedTypeScriptVersion": false
|
||||
},
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"arrow-body-style": 0,
|
||||
"curly": 0,
|
||||
"eol-last": 0,
|
||||
"eqeqeq": 0,
|
||||
"func-style": 0,
|
||||
"import/no-duplicates": 0,
|
||||
"max-statements": 0,
|
||||
"max-params": 0,
|
||||
"new-cap": 0,
|
||||
"no-console": 0,
|
||||
"no-duplicate-imports": 0,
|
||||
"no-extra-parens": 0,
|
||||
"no-return-assign": 0,
|
||||
"no-throw-literal": 0,
|
||||
"no-trailing-spaces": 0,
|
||||
"no-unused-expressions": 0,
|
||||
"no-useless-constructor": 0,
|
||||
"no-useless-escape": 0,
|
||||
"no-var": 0,
|
||||
"prefer-const": 0,
|
||||
"prefer-rest-params": 0,
|
||||
"quote-props": 0,
|
||||
"unicorn/filename-case": 0
|
||||
}
|
||||
}
|
56
api_tests/eslint.config.mjs
Normal file
56
api_tests/eslint.config.mjs
Normal file
|
@ -0,0 +1,56 @@
|
|||
import pluginJs from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default [
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
},
|
||||
},
|
||||
// For some reason this has to be in its own block
|
||||
{
|
||||
ignores: [
|
||||
"putTypesInIndex.js",
|
||||
"dist/*",
|
||||
"docs/*",
|
||||
".yalc",
|
||||
"jest.config.js",
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ["src/**/*"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-empty-interface": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-var-requires": 0,
|
||||
"arrow-body-style": 0,
|
||||
curly: 0,
|
||||
"eol-last": 0,
|
||||
eqeqeq: 0,
|
||||
"func-style": 0,
|
||||
"import/no-duplicates": 0,
|
||||
"max-statements": 0,
|
||||
"max-params": 0,
|
||||
"new-cap": 0,
|
||||
"no-console": 0,
|
||||
"no-duplicate-imports": 0,
|
||||
"no-extra-parens": 0,
|
||||
"no-return-assign": 0,
|
||||
"no-throw-literal": 0,
|
||||
"no-trailing-spaces": 0,
|
||||
"no-unused-expressions": 0,
|
||||
"no-useless-constructor": 0,
|
||||
"no-useless-escape": 0,
|
||||
"no-var": 0,
|
||||
"prefer-const": 0,
|
||||
"prefer-rest-params": 0,
|
||||
"quote-props": 0,
|
||||
"unicorn/filename-case": 0,
|
||||
},
|
||||
},
|
||||
];
|
|
@ -6,9 +6,9 @@
|
|||
"repository": "https://github.com/LemmyNet/lemmy",
|
||||
"author": "Dessalines",
|
||||
"license": "AGPL-3.0",
|
||||
"packageManager": "pnpm@9.1.1+sha256.9551e803dcb7a1839fdf5416153a844060c7bce013218ce823410532504ac10b",
|
||||
"packageManager": "pnpm@9.9.0",
|
||||
"scripts": {
|
||||
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
|
||||
"lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'",
|
||||
"fix": "prettier --write src && eslint --fix src",
|
||||
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
|
||||
"api-test-follow": "jest -i follow.spec.ts",
|
||||
|
@ -21,16 +21,16 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.12.4",
|
||||
"@typescript-eslint/eslint-plugin": "^7.5.0",
|
||||
"@typescript-eslint/parser": "^7.5.0",
|
||||
"download-file-sync": "^1.0.4",
|
||||
"eslint": "^8.57.0",
|
||||
"@types/node": "^22.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||
"@typescript-eslint/parser": "^8.0.0",
|
||||
"eslint": "^9.8.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.5.0",
|
||||
"lemmy-js-client": "0.19.4-alpha.18",
|
||||
"lemmy-js-client": "0.19.5-alpha.1",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.4.4"
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.0.0"
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,7 @@ export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queu
|
|||
|
||||
# pictrs setup
|
||||
if [ ! -f "api_tests/pict-rs" ]; then
|
||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.13/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.16/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||
chmod +x api_tests/pict-rs
|
||||
fi
|
||||
./api_tests/pict-rs \
|
||||
|
|
|
@ -37,8 +37,9 @@ import {
|
|||
followCommunity,
|
||||
blockCommunity,
|
||||
delay,
|
||||
saveUserSettings,
|
||||
} from "./shared";
|
||||
import { CommentView, CommunityView } from "lemmy-js-client";
|
||||
import { CommentView, CommunityView, SaveUserSettings } from "lemmy-js-client";
|
||||
|
||||
let betaCommunity: CommunityView | undefined;
|
||||
let postOnAlphaRes: PostResponse;
|
||||
|
@ -443,6 +444,59 @@ test("Reply to a comment from another instance, get notification", async () => {
|
|||
assertCommentFederation(alphaReply, replyRes.comment_view);
|
||||
});
|
||||
|
||||
test("Bot reply notifications are filtered when bots are hidden", async () => {
|
||||
const newAlphaBot = await registerUser(alpha, alphaUrl);
|
||||
let form: SaveUserSettings = {
|
||||
bot_account: true,
|
||||
};
|
||||
await saveUserSettings(newAlphaBot, form);
|
||||
|
||||
const alphaCommunity = (
|
||||
await resolveCommunity(alpha, "!main@lemmy-alpha:8541")
|
||||
).community;
|
||||
|
||||
if (!alphaCommunity) {
|
||||
throw "Missing alpha community";
|
||||
}
|
||||
|
||||
await alpha.markAllAsRead();
|
||||
form = {
|
||||
show_bot_accounts: false,
|
||||
};
|
||||
await saveUserSettings(alpha, form);
|
||||
const postOnAlphaRes = await createPost(alpha, alphaCommunity.community.id);
|
||||
|
||||
// Bot reply to alpha's post
|
||||
let commentRes = await createComment(
|
||||
newAlphaBot,
|
||||
postOnAlphaRes.post_view.post.id,
|
||||
);
|
||||
expect(commentRes).toBeDefined();
|
||||
|
||||
let alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||
expect(alphaUnreadCountRes.replies).toBe(0);
|
||||
|
||||
let alphaUnreadRepliesRes = await getReplies(alpha, true);
|
||||
expect(alphaUnreadRepliesRes.replies.length).toBe(0);
|
||||
|
||||
// This both restores the original state that may be expected by other tests
|
||||
// implicitly and is used by the next steps to ensure replies are still
|
||||
// returned when a user later decides to show bot accounts again.
|
||||
form = {
|
||||
show_bot_accounts: true,
|
||||
};
|
||||
await saveUserSettings(alpha, form);
|
||||
|
||||
alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||
expect(alphaUnreadCountRes.replies).toBe(1);
|
||||
|
||||
alphaUnreadRepliesRes = await getReplies(alpha, true);
|
||||
expect(alphaUnreadRepliesRes.replies.length).toBe(1);
|
||||
expect(alphaUnreadRepliesRes.replies[0].comment.id).toBe(
|
||||
commentRes.comment_view.comment.id,
|
||||
);
|
||||
});
|
||||
|
||||
test("Mention beta from alpha", async () => {
|
||||
if (!betaCommunity) throw Error("no community");
|
||||
const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
jest.setTimeout(120000);
|
||||
|
||||
import { AddModToCommunity } from "lemmy-js-client/dist/types/AddModToCommunity";
|
||||
import { CommunityView } from "lemmy-js-client/dist/types/CommunityView";
|
||||
import {
|
||||
alpha,
|
||||
|
@ -9,6 +10,7 @@ import {
|
|||
resolveCommunity,
|
||||
createCommunity,
|
||||
deleteCommunity,
|
||||
delay,
|
||||
removeCommunity,
|
||||
getCommunity,
|
||||
followCommunity,
|
||||
|
@ -533,3 +535,41 @@ test("Content in local-only community doesn't federate", async () => {
|
|||
Error("couldnt_find_object"),
|
||||
);
|
||||
});
|
||||
|
||||
test("Remote mods can edit communities", async () => {
|
||||
let communityRes = await createCommunity(alpha);
|
||||
|
||||
let betaCommunity = await resolveCommunity(
|
||||
beta,
|
||||
communityRes.community_view.community.actor_id,
|
||||
);
|
||||
if (!betaCommunity.community) {
|
||||
throw "Missing beta community";
|
||||
}
|
||||
let betaOnAlpha = await resolvePerson(alpha, "lemmy_beta@lemmy-beta:8551");
|
||||
|
||||
let form: AddModToCommunity = {
|
||||
community_id: communityRes.community_view.community.id,
|
||||
person_id: betaOnAlpha.person?.person.id as number,
|
||||
added: true,
|
||||
};
|
||||
alpha.addModToCommunity(form);
|
||||
|
||||
let form2: EditCommunity = {
|
||||
community_id: betaCommunity.community?.community.id as number,
|
||||
description: "Example description",
|
||||
};
|
||||
|
||||
await editCommunity(beta, form2);
|
||||
// give alpha time to get and process the edit
|
||||
await delay(1000);
|
||||
|
||||
let alphaCommunity = await getCommunity(
|
||||
alpha,
|
||||
communityRes.community_view.community.id,
|
||||
);
|
||||
|
||||
await expect(alphaCommunity.community_view.community.description).toBe(
|
||||
"Example description",
|
||||
);
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
betaUrl,
|
||||
registerUser,
|
||||
unfollows,
|
||||
delay,
|
||||
} from "./shared";
|
||||
|
||||
beforeAll(setupLogins);
|
||||
|
@ -21,39 +22,48 @@ test("Follow local community", async () => {
|
|||
let user = await registerUser(beta, betaUrl);
|
||||
|
||||
let community = (await resolveBetaCommunity(user)).community!;
|
||||
expect(community.counts.subscribers).toBe(1);
|
||||
expect(community.counts.subscribers_local).toBe(1);
|
||||
let follow = await followCommunity(user, true, community.community.id);
|
||||
|
||||
// Make sure the follow response went through
|
||||
expect(follow.community_view.community.local).toBe(true);
|
||||
expect(follow.community_view.subscribed).toBe("Subscribed");
|
||||
expect(follow.community_view.counts.subscribers).toBe(2);
|
||||
expect(follow.community_view.counts.subscribers_local).toBe(2);
|
||||
expect(follow.community_view.counts.subscribers).toBe(
|
||||
community.counts.subscribers + 1,
|
||||
);
|
||||
expect(follow.community_view.counts.subscribers_local).toBe(
|
||||
community.counts.subscribers_local + 1,
|
||||
);
|
||||
|
||||
// Test an unfollow
|
||||
let unfollow = await followCommunity(user, false, community.community.id);
|
||||
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
|
||||
expect(unfollow.community_view.counts.subscribers).toBe(1);
|
||||
expect(unfollow.community_view.counts.subscribers_local).toBe(1);
|
||||
expect(unfollow.community_view.counts.subscribers).toBe(
|
||||
community.counts.subscribers,
|
||||
);
|
||||
expect(unfollow.community_view.counts.subscribers_local).toBe(
|
||||
community.counts.subscribers_local,
|
||||
);
|
||||
});
|
||||
|
||||
test("Follow federated community", async () => {
|
||||
// It takes about 1 second for the community aggregates to federate
|
||||
let betaCommunity = (
|
||||
await delay(2000); // if this is the second test run, we don't have a way to wait for the correct number of subscribers
|
||||
const betaCommunityInitial = (
|
||||
await waitUntil(
|
||||
() => resolveBetaCommunity(alpha),
|
||||
c =>
|
||||
c.community?.counts.subscribers === 1 &&
|
||||
c.community.counts.subscribers_local === 0,
|
||||
c => !!c.community && c.community?.counts.subscribers >= 1,
|
||||
)
|
||||
).community;
|
||||
if (!betaCommunity) {
|
||||
if (!betaCommunityInitial) {
|
||||
throw "Missing beta community";
|
||||
}
|
||||
let follow = await followCommunity(alpha, true, betaCommunity.community.id);
|
||||
let follow = await followCommunity(
|
||||
alpha,
|
||||
true,
|
||||
betaCommunityInitial.community.id,
|
||||
);
|
||||
expect(follow.community_view.subscribed).toBe("Pending");
|
||||
betaCommunity = (
|
||||
const betaCommunity = (
|
||||
await waitUntil(
|
||||
() => resolveBetaCommunity(alpha),
|
||||
c => c.community?.subscribed === "Subscribed",
|
||||
|
@ -64,20 +74,24 @@ test("Follow federated community", async () => {
|
|||
expect(betaCommunity?.community.local).toBe(false);
|
||||
expect(betaCommunity?.community.name).toBe("main");
|
||||
expect(betaCommunity?.subscribed).toBe("Subscribed");
|
||||
expect(betaCommunity?.counts.subscribers_local).toBe(1);
|
||||
expect(betaCommunity?.counts.subscribers_local).toBe(
|
||||
betaCommunityInitial.counts.subscribers_local + 1,
|
||||
);
|
||||
|
||||
// check that unfollow was federated
|
||||
let communityOnBeta1 = await resolveBetaCommunity(beta);
|
||||
expect(communityOnBeta1.community?.counts.subscribers).toBe(2);
|
||||
expect(communityOnBeta1.community?.counts.subscribers_local).toBe(1);
|
||||
expect(communityOnBeta1.community?.counts.subscribers).toBe(
|
||||
betaCommunityInitial.counts.subscribers + 1,
|
||||
);
|
||||
|
||||
// Check it from local
|
||||
let site = await getSite(alpha);
|
||||
let remoteCommunityId = site.my_user?.follows.find(
|
||||
c => c.community.local == false,
|
||||
c =>
|
||||
c.community.local == false &&
|
||||
c.community.id === betaCommunityInitial.community.id,
|
||||
)?.community.id;
|
||||
expect(remoteCommunityId).toBeDefined();
|
||||
expect(site.my_user?.follows.length).toBe(2);
|
||||
|
||||
if (!remoteCommunityId) {
|
||||
throw "Missing remote community id";
|
||||
|
@ -89,10 +103,21 @@ test("Follow federated community", async () => {
|
|||
|
||||
// Make sure you are unsubbed locally
|
||||
let siteUnfollowCheck = await getSite(alpha);
|
||||
expect(siteUnfollowCheck.my_user?.follows.length).toBe(1);
|
||||
expect(
|
||||
siteUnfollowCheck.my_user?.follows.find(
|
||||
c => c.community.id === betaCommunityInitial.community.id,
|
||||
),
|
||||
).toBe(undefined);
|
||||
|
||||
// check that unfollow was federated
|
||||
let communityOnBeta2 = await resolveBetaCommunity(beta);
|
||||
expect(communityOnBeta2.community?.counts.subscribers).toBe(1);
|
||||
let communityOnBeta2 = await waitUntil(
|
||||
() => resolveBetaCommunity(beta),
|
||||
c =>
|
||||
c.community?.counts.subscribers ===
|
||||
betaCommunityInitial.counts.subscribers,
|
||||
);
|
||||
expect(communityOnBeta2.community?.counts.subscribers).toBe(
|
||||
betaCommunityInitial.counts.subscribers,
|
||||
);
|
||||
expect(communityOnBeta2.community?.counts.subscribers_local).toBe(1);
|
||||
});
|
||||
|
|
|
@ -33,7 +33,6 @@ import {
|
|||
sampleImage,
|
||||
sampleSite,
|
||||
} from "./shared";
|
||||
const downloadFileSync = require("download-file-sync");
|
||||
|
||||
beforeAll(setupLogins);
|
||||
|
||||
|
@ -57,7 +56,8 @@ test("Upload image and delete it", async () => {
|
|||
expect(upload.delete_url).toBeDefined();
|
||||
|
||||
// ensure that image download is working. theres probably a better way to do this
|
||||
const content = downloadFileSync(upload.url);
|
||||
const response = await fetch(upload.url ?? "");
|
||||
const content = await response.text();
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
|
||||
// Ensure that it comes back with the list_media endpoint
|
||||
|
@ -92,7 +92,8 @@ test("Upload image and delete it", async () => {
|
|||
expect(delete_).toBe(true);
|
||||
|
||||
// ensure that image is deleted
|
||||
const content2 = downloadFileSync(upload.url);
|
||||
const response2 = await fetch(upload.url ?? "");
|
||||
const content2 = await response2.text();
|
||||
expect(content2).toBe("");
|
||||
|
||||
// Ensure that it shows the image is deleted
|
||||
|
@ -120,7 +121,8 @@ test("Purge user, uploaded image removed", async () => {
|
|||
expect(upload.delete_url).toBeDefined();
|
||||
|
||||
// ensure that image download is working. theres probably a better way to do this
|
||||
const content = downloadFileSync(upload.url);
|
||||
const response = await fetch(upload.url ?? "");
|
||||
const content = await response.text();
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
|
||||
// purge user
|
||||
|
@ -132,7 +134,8 @@ test("Purge user, uploaded image removed", async () => {
|
|||
expect(delete_.success).toBe(true);
|
||||
|
||||
// ensure that image is deleted
|
||||
const content2 = downloadFileSync(upload.url);
|
||||
const response2 = await fetch(upload.url ?? "");
|
||||
const content2 = await response2.text();
|
||||
expect(content2).toBe("");
|
||||
});
|
||||
|
||||
|
@ -150,7 +153,8 @@ test("Purge post, linked image removed", async () => {
|
|||
expect(upload.delete_url).toBeDefined();
|
||||
|
||||
// ensure that image download is working. theres probably a better way to do this
|
||||
const content = downloadFileSync(upload.url);
|
||||
const response = await fetch(upload.url ?? "");
|
||||
const content = await response.text();
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
|
||||
let community = await resolveBetaCommunity(user);
|
||||
|
@ -160,6 +164,7 @@ test("Purge post, linked image removed", async () => {
|
|||
upload.url,
|
||||
);
|
||||
expect(post.post_view.post.url).toBe(upload.url);
|
||||
expect(post.post_view.image_details).toBeDefined();
|
||||
|
||||
// purge post
|
||||
const purgeForm: PurgePost = {
|
||||
|
@ -169,7 +174,8 @@ test("Purge post, linked image removed", async () => {
|
|||
expect(delete_.success).toBe(true);
|
||||
|
||||
// ensure that image is deleted
|
||||
const content2 = downloadFileSync(upload.url);
|
||||
const response2 = await fetch(upload.url ?? "");
|
||||
const content2 = await response2.text();
|
||||
expect(content2).toBe("");
|
||||
});
|
||||
|
||||
|
@ -184,6 +190,9 @@ test("Images in remote image post are proxied if setting enabled", async () => {
|
|||
const post = postRes.post_view.post;
|
||||
expect(post).toBeDefined();
|
||||
|
||||
// Make sure it fetched the image details
|
||||
expect(postRes.post_view.image_details).toBeDefined();
|
||||
|
||||
// remote image gets proxied after upload
|
||||
expect(
|
||||
post.thumbnail_url?.startsWith(
|
||||
|
|
|
@ -52,17 +52,23 @@ beforeAll(async () => {
|
|||
|
||||
afterAll(unfollows);
|
||||
|
||||
async function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
||||
async function assertPostFederation(
|
||||
postOne: PostView,
|
||||
postTwo: PostView,
|
||||
waitForMeta = true,
|
||||
) {
|
||||
// Link metadata is generated in background task and may not be ready yet at this time,
|
||||
// so wait for it explicitly. For removed posts we cant refetch anything.
|
||||
postOne = await waitForPost(beta, postOne.post, res => {
|
||||
return res === null || res?.post.embed_title !== null;
|
||||
});
|
||||
postTwo = await waitForPost(
|
||||
beta,
|
||||
postTwo.post,
|
||||
res => res === null || res?.post.embed_title !== null,
|
||||
);
|
||||
if (waitForMeta) {
|
||||
postOne = await waitForPost(beta, postOne.post, res => {
|
||||
return res === null || !!res?.post.embed_title;
|
||||
});
|
||||
postTwo = await waitForPost(
|
||||
beta,
|
||||
postTwo.post,
|
||||
res => res === null || !!res?.post.embed_title,
|
||||
);
|
||||
}
|
||||
|
||||
expect(postOne?.post.ap_id).toBe(postTwo?.post.ap_id);
|
||||
expect(postOne?.post.name).toBe(postTwo?.post.name);
|
||||
|
@ -408,7 +414,11 @@ test("Remove a post from admin and community on same instance", async () => {
|
|||
p => p?.post_view.post.removed ?? false,
|
||||
);
|
||||
expect(alphaPost?.post_view.post.removed).toBe(true);
|
||||
await assertPostFederation(alphaPost.post_view, removePostRes.post_view);
|
||||
await assertPostFederation(
|
||||
alphaPost.post_view,
|
||||
removePostRes.post_view,
|
||||
false,
|
||||
);
|
||||
|
||||
// Undelete
|
||||
let undeletedPost = await removePost(beta, false, betaPost.post);
|
||||
|
@ -502,7 +512,7 @@ test("Enforce site ban federation for local user", async () => {
|
|||
}
|
||||
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
|
||||
alphaUserHttp.setHeaders({
|
||||
Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "",
|
||||
Authorization: "Bearer " + newAlphaUserJwt.jwt,
|
||||
});
|
||||
// alpha makes new post in beta community, it federates
|
||||
let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id);
|
||||
|
|
|
@ -197,7 +197,7 @@ export async function setupLogins() {
|
|||
// (because last_successful_id is set to current id when federation to an instance is first started)
|
||||
// only needed the first time so do in this try
|
||||
await delay(10_000);
|
||||
} catch (_) {
|
||||
} catch {
|
||||
console.log("Communities already exist");
|
||||
}
|
||||
}
|
||||
|
@ -364,10 +364,13 @@ export async function getUnreadCount(
|
|||
return api.getUnreadCount();
|
||||
}
|
||||
|
||||
export async function getReplies(api: LemmyHttp): Promise<GetRepliesResponse> {
|
||||
export async function getReplies(
|
||||
api: LemmyHttp,
|
||||
unread_only: boolean = false,
|
||||
): Promise<GetRepliesResponse> {
|
||||
let form: GetReplies = {
|
||||
sort: "New",
|
||||
unread_only: false,
|
||||
unread_only,
|
||||
};
|
||||
return api.getReplies(form);
|
||||
}
|
||||
|
@ -896,7 +899,6 @@ export async function deleteAllImages(api: LemmyHttp) {
|
|||
const imagesRes = await api.listAllMedia({
|
||||
limit: imageFetchLimit,
|
||||
});
|
||||
imagesRes.images;
|
||||
Promise.all(
|
||||
imagesRes.images
|
||||
.map(image => {
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
fetchFunction,
|
||||
alphaImage,
|
||||
unfollows,
|
||||
saveUserSettingsBio,
|
||||
} from "./shared";
|
||||
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||
|
@ -130,7 +131,11 @@ test("Requests with invalid auth should be treated as unauthenticated", async ()
|
|||
});
|
||||
|
||||
test("Create user with Arabic name", async () => {
|
||||
let user = await registerUser(alpha, alphaUrl, "تجريب");
|
||||
let user = await registerUser(
|
||||
alpha,
|
||||
alphaUrl,
|
||||
"تجريب" + Math.random().toString().slice(2, 10), // less than actor_name_max_length
|
||||
);
|
||||
|
||||
let site = await getSite(user);
|
||||
expect(site.my_user).toBeDefined();
|
||||
|
@ -186,10 +191,26 @@ test("Set a new avatar, old avatar is deleted", async () => {
|
|||
expect(upload2.url).toBeDefined();
|
||||
|
||||
let form2 = {
|
||||
avatar: upload1.url,
|
||||
avatar: upload2.url,
|
||||
};
|
||||
await saveUserSettings(alpha, form2);
|
||||
// make sure only the new avatar is kept
|
||||
const listMediaRes2 = await alphaImage.listMedia();
|
||||
expect(listMediaRes2.images.length).toBe(1);
|
||||
|
||||
// Upload that same form2 avatar, make sure it isn't replaced / deleted
|
||||
await saveUserSettings(alpha, form2);
|
||||
// make sure only the new avatar is kept
|
||||
const listMediaRes3 = await alphaImage.listMedia();
|
||||
expect(listMediaRes3.images.length).toBe(1);
|
||||
|
||||
// Now try to save a user settings, with the icon missing,
|
||||
// and make sure it doesn't clear the data, or delete the image
|
||||
await saveUserSettingsBio(alpha);
|
||||
let site = await getSite(alpha);
|
||||
expect(site.my_user?.local_user_view.person.avatar).toBe(upload2.url);
|
||||
|
||||
// make sure only the new avatar is kept
|
||||
const listMediaRes4 = await alphaImage.listMedia();
|
||||
expect(listMediaRes4.images.length).toBe(1);
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ body = """
|
|||
{%- endif %}
|
||||
{%- endfor -%}
|
||||
|
||||
{%- if github -%}
|
||||
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||
{% raw %}\n{% endraw -%}
|
||||
## New Contributors
|
||||
|
@ -36,6 +37,7 @@ body = """
|
|||
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||
{%- endif %}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
{% if version %}
|
||||
{% if previous.version %}
|
||||
|
@ -70,6 +72,7 @@ commit_preprocessors = [
|
|||
# remove issue numbers from commits
|
||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
||||
]
|
||||
commit_parsers = [{ field = "author.name", pattern = "renovate", skip = true }]
|
||||
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
||||
protect_breaking_commits = false
|
||||
# filter out the commits that are not matched by commit parsers
|
||||
|
|
|
@ -108,10 +108,12 @@
|
|||
port: 8536
|
||||
# Whether the site is available over TLS. Needs to be true for federation to work.
|
||||
tls_enabled: true
|
||||
# The number of activitypub federation workers that can be in-flight concurrently
|
||||
worker_count: 0
|
||||
# The number of activitypub federation retry workers that can be in-flight concurrently
|
||||
retry_count: 0
|
||||
federation: {
|
||||
# Limit to the number of concurrent outgoing federation requests per target instance.
|
||||
# Set this to a higher value than 1 (e.g. 6) only if you have a huge instance (>10 activities
|
||||
# per second) and if a receiving instance is not keeping up.
|
||||
concurrent_sends_per_instance: 1
|
||||
}
|
||||
prometheus: {
|
||||
bind: "127.0.0.1"
|
||||
port: 10002
|
||||
|
|
|
@ -33,13 +33,14 @@ anyhow = { workspace = true }
|
|||
tracing = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
url = { workspace = true }
|
||||
wav = "1.0.1"
|
||||
hound = "3.5.1"
|
||||
sitemap-rs = "0.2.1"
|
||||
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
|
||||
actix-web-httpauth = "0.8.1"
|
||||
totp-rs = { version = "5.6.0", features = ["gen_secret", "otpauth"] }
|
||||
actix-web-httpauth = "0.8.2"
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
elementtree = "1.2.3"
|
||||
pretty_assertions = { workspace = true }
|
||||
lemmy_api_crud = { workspace = true }
|
||||
|
|
|
@ -17,9 +17,13 @@ pub async fn distinguish_comment(
|
|||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let orig_comment = CommentView::read(
|
||||
&mut context.pool(),
|
||||
data.comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -54,7 +58,7 @@ pub async fn distinguish_comment(
|
|||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
data.comment_id,
|
||||
Some(local_user_view.person.id),
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
|
|
@ -35,9 +35,13 @@ pub async fn like_comment(
|
|||
check_bot_account(&local_user_view.person)?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let orig_comment = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
|
|
@ -17,7 +17,7 @@ pub async fn list_comment_likes(
|
|||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
data.comment_id,
|
||||
Some(local_user_view.person.id),
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
|
|
@ -32,10 +32,13 @@ pub async fn save_comment(
|
|||
}
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, Some(person_id))
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
Ok(Json(CommentResponse {
|
||||
comment_view,
|
||||
|
|
|
@ -35,9 +35,13 @@ pub async fn create_comment_report(
|
|||
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_id = data.comment_id;
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
|
|
@ -9,6 +9,7 @@ use lemmy_api_common::{
|
|||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||
local_user::LocalUser,
|
||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||
},
|
||||
traits::{Crud, Joinable},
|
||||
|
@ -33,6 +34,18 @@ pub async fn add_mod_to_community(
|
|||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// If its a mod removal, also check that you're a higher mod.
|
||||
if !data.added {
|
||||
LocalUser::is_higher_mod_or_admin_check(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
local_user_view.person.id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let community = Community::read(&mut context.pool(), community_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
|
|
@ -14,6 +14,7 @@ use lemmy_db_schema::{
|
|||
CommunityPersonBan,
|
||||
CommunityPersonBanForm,
|
||||
},
|
||||
local_user::LocalUser,
|
||||
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
||||
},
|
||||
traits::{Bannable, Crud, Followable},
|
||||
|
@ -43,7 +44,18 @@ pub async fn ban_from_community(
|
|||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
is_valid_body_field(&data.reason, false)?;
|
||||
|
||||
LocalUser::is_higher_mod_or_admin_check(
|
||||
&mut context.pool(),
|
||||
data.community_id,
|
||||
local_user_view.person.id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(reason) = &data.reason {
|
||||
is_valid_body_field(reason, false)?;
|
||||
}
|
||||
|
||||
let community_user_ban_form = CommunityPersonBanForm {
|
||||
community_id: data.community_id,
|
||||
|
|
|
@ -50,10 +50,14 @@ pub async fn block_community(
|
|||
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
|
||||
}
|
||||
|
||||
let community_view =
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
let community_view = CommunityView::read(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
Some(&local_user_view.local_user),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
SendActivityData::FollowCommunity(
|
||||
|
|
|
@ -62,11 +62,14 @@ pub async fn follow_community(
|
|||
}
|
||||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view =
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
let community_view = CommunityView::read(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
Some(&local_user_view.local_user),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
||||
|
||||
|
|
|
@ -76,11 +76,14 @@ pub async fn transfer_community(
|
|||
ModTransferCommunity::create(&mut context.pool(), &form).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let community_view =
|
||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
let community_view = CommunityView::read(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
Some(&local_user_view.local_user),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id)
|
||||
|
|
|
@ -44,33 +44,38 @@ pub mod site;
|
|||
pub mod sitemap;
|
||||
|
||||
/// Converts the captcha to a base64 encoded wav audio file
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
|
||||
let letters = captcha.as_wav();
|
||||
|
||||
// Decode each wav file, concatenate the samples
|
||||
let mut concat_samples: Vec<i16> = Vec::new();
|
||||
let mut any_header: Option<wav::Header> = None;
|
||||
let mut any_header: Option<hound::WavSpec> = None;
|
||||
for letter in letters {
|
||||
let mut cursor = Cursor::new(letter.unwrap_or_default());
|
||||
let (header, samples) = wav::read(&mut cursor)?;
|
||||
any_header = Some(header);
|
||||
if let Some(samples16) = samples.as_sixteen() {
|
||||
concat_samples.extend(samples16);
|
||||
} else {
|
||||
Err(LemmyErrorType::CouldntCreateAudioCaptcha)?
|
||||
}
|
||||
let reader = hound::WavReader::new(&mut cursor)?;
|
||||
any_header = Some(reader.spec());
|
||||
let samples16 = reader
|
||||
.into_samples::<i16>()
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
concat_samples.extend(samples16);
|
||||
}
|
||||
|
||||
// Encode the concatenated result as a wav file
|
||||
let mut output_buffer = Cursor::new(vec![]);
|
||||
if let Some(header) = any_header {
|
||||
wav::write(
|
||||
header,
|
||||
&wav::BitDepth::Sixteen(concat_samples),
|
||||
&mut output_buffer,
|
||||
)
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
let mut writer = hound::WavWriter::new(&mut output_buffer, header)
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
let mut writer16 = writer.get_i16_writer(concat_samples.len() as u32);
|
||||
for sample in concat_samples {
|
||||
writer16.write_sample(sample);
|
||||
}
|
||||
writer16
|
||||
.flush()
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
writer
|
||||
.finalize()
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||
|
||||
Ok(base64.encode(output_buffer.into_inner()))
|
||||
} else {
|
||||
|
|
|
@ -24,6 +24,16 @@ pub async fn add_admin(
|
|||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// If its an admin removal, also check that you're a higher admin
|
||||
if !data.added {
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Make sure that the person_id added is local
|
||||
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
|
||||
.await?
|
||||
|
|
|
@ -9,6 +9,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
local_user::LocalUser,
|
||||
login_token::LoginToken,
|
||||
moderator::{ModBan, ModBanForm},
|
||||
person::{Person, PersonUpdateForm},
|
||||
|
@ -31,7 +32,17 @@ pub async fn ban_from_site(
|
|||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
is_valid_body_field(&data.reason, false)?;
|
||||
// Also make sure you're a higher admin than the target
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(reason) = &data.reason {
|
||||
is_valid_body_field(reason, false)?;
|
||||
}
|
||||
|
||||
let expires = check_expire_time(data.expires)?;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_api_common::{context::LemmyContext, person::ListLoginsResponse};
|
||||
use lemmy_db_schema::source::login_token::LoginToken;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -7,8 +7,8 @@ use lemmy_utils::error::LemmyResult;
|
|||
pub async fn list_logins(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<Vec<LoginToken>>> {
|
||||
) -> LemmyResult<Json<ListLoginsResponse>> {
|
||||
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
|
||||
|
||||
Ok(Json(logins))
|
||||
Ok(Json(ListLoginsResponse { logins }))
|
||||
}
|
||||
|
|
|
@ -11,9 +11,12 @@ pub async fn unread_count(
|
|||
) -> LemmyResult<Json<GetUnreadCountResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
let replies = CommentReplyView::get_unread_replies(&mut context.pool(), person_id).await?;
|
||||
let replies =
|
||||
CommentReplyView::get_unread_replies(&mut context.pool(), &local_user_view.local_user).await?;
|
||||
|
||||
let mentions = PersonMentionView::get_unread_mentions(&mut context.pool(), person_id).await?;
|
||||
let mentions =
|
||||
PersonMentionView::get_unread_mentions(&mut context.pool(), &local_user_view.local_user)
|
||||
.await?;
|
||||
|
||||
let private_messages =
|
||||
PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?;
|
||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
|||
person::{Person, PersonUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::diesel_option_overwrite,
|
||||
utils::{diesel_string_update, diesel_url_update},
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::{
|
||||
|
@ -42,18 +42,24 @@ pub async fn save_user_settings(
|
|||
|
||||
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let bio = diesel_option_overwrite(
|
||||
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?,
|
||||
let bio = diesel_string_update(
|
||||
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context)
|
||||
.await?
|
||||
.as_deref(),
|
||||
);
|
||||
replace_image(&data.avatar, &local_user_view.person.avatar, &context).await?;
|
||||
replace_image(&data.banner, &local_user_view.person.banner, &context).await?;
|
||||
|
||||
let avatar = proxy_image_link_opt_api(&data.avatar, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
||||
let display_name = diesel_option_overwrite(data.display_name.clone());
|
||||
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
|
||||
let avatar = diesel_url_update(data.avatar.as_deref())?;
|
||||
replace_image(&avatar, &local_user_view.person.avatar, &context).await?;
|
||||
let avatar = proxy_image_link_opt_api(avatar, &context).await?;
|
||||
|
||||
let banner = diesel_url_update(data.banner.as_deref())?;
|
||||
replace_image(&banner, &local_user_view.person.banner, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(banner, &context).await?;
|
||||
|
||||
let display_name = diesel_string_update(data.display_name.as_deref());
|
||||
let matrix_user_id = diesel_string_update(data.matrix_user_id.as_deref());
|
||||
let email_deref = data.email.as_deref().map(str::to_lowercase);
|
||||
let email = diesel_option_overwrite(email_deref.clone());
|
||||
let email = diesel_string_update(email_deref.as_deref());
|
||||
|
||||
if let Some(Some(email)) = &email {
|
||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
||||
|
@ -129,7 +135,6 @@ pub async fn save_user_settings(
|
|||
blur_nsfw: data.blur_nsfw,
|
||||
auto_expand: data.auto_expand,
|
||||
show_bot_accounts: data.show_bot_accounts,
|
||||
show_scores: data.show_scores,
|
||||
default_sort_type,
|
||||
default_listing_type,
|
||||
theme: data.theme.clone(),
|
||||
|
|
|
@ -5,12 +5,9 @@ use lemmy_api_common::{
|
|||
utils::send_new_applicant_email_to_admins,
|
||||
SuccessResponse,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
email_verification::EmailVerification,
|
||||
local_user::{LocalUser, LocalUserUpdateForm},
|
||||
},
|
||||
RegistrationMode,
|
||||
use lemmy_db_schema::source::{
|
||||
email_verification::EmailVerification,
|
||||
local_user::{LocalUser, LocalUserUpdateForm},
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
@ -41,9 +38,7 @@ pub async fn verify_email(
|
|||
EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?;
|
||||
|
||||
// send out notification about registration application to admins if enabled
|
||||
if site_view.local_site.registration_mode == RegistrationMode::RequireApplication
|
||||
&& site_view.local_site.application_email_admins
|
||||
{
|
||||
if site_view.local_site.application_email_admins {
|
||||
let local_user = LocalUserView::read(&mut context.pool(), local_user_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
|
|
|
@ -72,11 +72,5 @@ pub async fn feature_post(
|
|||
)
|
||||
.await?;
|
||||
|
||||
build_post_response(
|
||||
&context,
|
||||
orig_post.community_id,
|
||||
&local_user_view.person,
|
||||
post_id,
|
||||
)
|
||||
.await
|
||||
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
||||
}
|
||||
|
|
|
@ -4,14 +4,22 @@ use lemmy_api_common::{
|
|||
post::{GetSiteMetadata, GetSiteMetadataResponse},
|
||||
request::fetch_link_metadata,
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyResult},
|
||||
LemmyErrorType,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_link_metadata(
|
||||
data: Query<GetSiteMetadata>,
|
||||
context: Data<LemmyContext>,
|
||||
// Require an account for this API
|
||||
_local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<GetSiteMetadataResponse>> {
|
||||
let metadata = fetch_link_metadata(&data.url, &context).await?;
|
||||
let url = Url::parse(&data.url).with_lemmy_type(LemmyErrorType::InvalidUrl)?;
|
||||
let metadata = fetch_link_metadata(&url, &context).await?;
|
||||
|
||||
Ok(Json(GetSiteMetadataResponse { metadata }))
|
||||
}
|
||||
|
|
|
@ -85,11 +85,5 @@ pub async fn like_post(
|
|||
)
|
||||
.await?;
|
||||
|
||||
build_post_response(
|
||||
context.deref(),
|
||||
post.community_id,
|
||||
&local_user_view.person,
|
||||
post_id,
|
||||
)
|
||||
.await
|
||||
build_post_response(context.deref(), post.community_id, local_user_view, post_id).await
|
||||
}
|
||||
|
|
|
@ -63,11 +63,5 @@ pub async fn lock_post(
|
|||
)
|
||||
.await?;
|
||||
|
||||
build_post_response(
|
||||
&context,
|
||||
orig_post.community_id,
|
||||
&local_user_view.person,
|
||||
post_id,
|
||||
)
|
||||
.await
|
||||
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
||||
}
|
||||
|
|
|
@ -34,9 +34,14 @@ pub async fn save_post(
|
|||
|
||||
let post_id = data.post_id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
let post_view = PostView::read(
|
||||
&mut context.pool(),
|
||||
post_id,
|
||||
Some(&local_user_view.local_user),
|
||||
false,
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use lemmy_api_common::{
|
|||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::Comment,
|
||||
local_user::LocalUser,
|
||||
moderator::{AdminPurgeComment, AdminPurgeCommentForm},
|
||||
},
|
||||
traits::Crud,
|
||||
|
@ -29,9 +30,21 @@ pub async fn purge_comment(
|
|||
let comment_id = data.comment_id;
|
||||
|
||||
// Read the comment to get the post_id and community
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
// Also check that you're a higher admin
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
vec![comment_view.creator.id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let post_id = comment_view.comment.post_id;
|
||||
|
||||
|
|
|
@ -9,13 +9,16 @@ use lemmy_api_common::{
|
|||
SuccessResponse,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
source::{
|
||||
community::Community,
|
||||
local_user::LocalUser,
|
||||
moderator::{AdminPurgeCommunity, AdminPurgeCommunityForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -32,6 +35,21 @@ pub async fn purge_community(
|
|||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
|
||||
// Also check that you're a higher admin than all the mods
|
||||
let community_mod_person_ids =
|
||||
CommunityModeratorView::for_community(&mut context.pool(), community.id)
|
||||
.await?
|
||||
.iter()
|
||||
.map(|cmv| cmv.moderator.id)
|
||||
.collect::<Vec<PersonId>>();
|
||||
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
community_mod_person_ids,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(banner) = &community.banner {
|
||||
purge_image_from_pictrs(banner, &context).await.ok();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
local_user::LocalUser,
|
||||
moderator::{AdminPurgePerson, AdminPurgePersonForm},
|
||||
person::{Person, PersonUpdateForm},
|
||||
},
|
||||
|
@ -27,9 +28,18 @@ pub async fn purge_person(
|
|||
// Only let admin purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Also check that you're a higher admin
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
vec![data.person_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let person = Person::read(&mut context.pool(), data.person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPerson)?;
|
||||
|
||||
ban_nonlocal_user_from_local_communities(
|
||||
&local_user_view,
|
||||
&person,
|
||||
|
|
|
@ -10,6 +10,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
local_user::LocalUser,
|
||||
moderator::{AdminPurgePost, AdminPurgePostForm},
|
||||
post::Post,
|
||||
},
|
||||
|
@ -32,6 +33,14 @@ pub async fn purge_post(
|
|||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
// Also check that you're a higher admin
|
||||
LocalUser::is_higher_admin_check(
|
||||
&mut context.pool(),
|
||||
local_user_view.person.id,
|
||||
vec![post.creator_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Purge image
|
||||
if let Some(url) = &post.url {
|
||||
purge_image_from_pictrs(url, &context).await.ok();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
site::{ApproveRegistrationApplication, RegistrationApplicationResponse},
|
||||
|
@ -10,10 +11,13 @@ use lemmy_db_schema::{
|
|||
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::diesel_option_overwrite,
|
||||
utils::{diesel_string_update, get_conn},
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyResult},
|
||||
LemmyErrorType,
|
||||
};
|
||||
|
||||
pub async fn approve_registration_application(
|
||||
data: Json<ApproveRegistrationApplication>,
|
||||
|
@ -25,34 +29,46 @@ pub async fn approve_registration_application(
|
|||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Update the registration with reason, admin_id
|
||||
let deny_reason = diesel_option_overwrite(data.deny_reason.clone());
|
||||
let app_form = RegistrationApplicationUpdateForm {
|
||||
admin_id: Some(Some(local_user_view.person.id)),
|
||||
deny_reason,
|
||||
};
|
||||
let pool = &mut context.pool();
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let tx_data = data.clone();
|
||||
let approved_user_id = conn
|
||||
.build_transaction()
|
||||
.run(|conn| {
|
||||
Box::pin(async move {
|
||||
// Update the registration with reason, admin_id
|
||||
let deny_reason = diesel_string_update(tx_data.deny_reason.as_deref());
|
||||
let app_form = RegistrationApplicationUpdateForm {
|
||||
admin_id: Some(Some(local_user_view.person.id)),
|
||||
deny_reason,
|
||||
};
|
||||
|
||||
let registration_application =
|
||||
RegistrationApplication::update(&mut context.pool(), app_id, &app_form).await?;
|
||||
let registration_application =
|
||||
RegistrationApplication::update(&mut conn.into(), app_id, &app_form).await?;
|
||||
|
||||
// Update the local_user row
|
||||
let local_user_form = LocalUserUpdateForm {
|
||||
accepted_application: Some(data.approve),
|
||||
..Default::default()
|
||||
};
|
||||
// Update the local_user row
|
||||
let local_user_form = LocalUserUpdateForm {
|
||||
accepted_application: Some(tx_data.approve),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let approved_user_id = registration_application.local_user_id;
|
||||
LocalUser::update(&mut context.pool(), approved_user_id, &local_user_form).await?;
|
||||
let approved_user_id = registration_application.local_user_id;
|
||||
LocalUser::update(&mut conn.into(), approved_user_id, &local_user_form).await?;
|
||||
|
||||
Ok::<_, LemmyError>(approved_user_id)
|
||||
}) as _
|
||||
})
|
||||
.await?;
|
||||
|
||||
if data.approve {
|
||||
let approved_local_user_view = LocalUserView::read(&mut context.pool(), approved_user_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
|
||||
|
||||
if approved_local_user_view.local_user.email.is_some() {
|
||||
// Email sending may fail, but this won't revert the application approval
|
||||
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Read the view
|
||||
let registration_application = RegistrationApplicationView::read(&mut context.pool(), app_id)
|
||||
|
|
28
crates/api/src/site/registration_applications/get.rs
Normal file
28
crates/api/src/site/registration_applications/get.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
site::{GetRegistrationApplication, RegistrationApplicationResponse},
|
||||
utils::is_admin,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
|
||||
/// Lists registration applications, filterable by undenied only.
|
||||
pub async fn get_registration_application(
|
||||
data: Query<GetRegistrationApplication>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<RegistrationApplicationResponse>> {
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Read the view
|
||||
let registration_application =
|
||||
RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
||||
|
||||
Ok(Json(RegistrationApplicationResponse {
|
||||
registration_application,
|
||||
}))
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::{Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
site::{ListRegistrationApplications, ListRegistrationApplicationsResponse},
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
pub mod approve;
|
||||
pub mod get;
|
||||
pub mod list;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod unread_count;
|
||||
|
|
428
crates/api/src/site/registration_applications/tests.rs
Normal file
428
crates/api/src/site/registration_applications/tests.rs
Normal file
|
@ -0,0 +1,428 @@
|
|||
use crate::site::registration_applications::{
|
||||
approve::approve_registration_application,
|
||||
list::list_registration_applications,
|
||||
unread_count::get_unread_registration_application_count,
|
||||
};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::{Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
site::{
|
||||
ApproveRegistrationApplication,
|
||||
EditSite,
|
||||
GetUnreadRegistrationApplicationCountResponse,
|
||||
ListRegistrationApplicationsResponse,
|
||||
},
|
||||
};
|
||||
use lemmy_api_crud::site::update::update_site;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::InstanceId,
|
||||
source::{
|
||||
instance::Instance,
|
||||
local_site::{LocalSite, LocalSiteInsertForm},
|
||||
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitInsertForm},
|
||||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||
person::{Person, PersonInsertForm},
|
||||
registration_application::{RegistrationApplication, RegistrationApplicationInsertForm},
|
||||
site::{Site, SiteInsertForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::DbPool,
|
||||
RegistrationMode,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API};
|
||||
use serial_test::serial;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> {
|
||||
let pool = &mut context.pool();
|
||||
|
||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
|
||||
.await
|
||||
.expect("Create test instance");
|
||||
|
||||
let admin_person = Person::create(
|
||||
pool,
|
||||
&PersonInsertForm::test_form(inserted_instance.id, "admin"),
|
||||
)
|
||||
.await?;
|
||||
LocalUser::create(
|
||||
pool,
|
||||
&LocalUserInsertForm::test_form_admin(admin_person.id),
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id)
|
||||
.await?
|
||||
.unwrap();
|
||||
|
||||
let site_form = SiteInsertForm::builder()
|
||||
.name("test site".to_string())
|
||||
.instance_id(inserted_instance.id)
|
||||
.build();
|
||||
let site = Site::create(pool, &site_form).await.unwrap();
|
||||
|
||||
// Create a local site, since this is necessary for determining if email verification is
|
||||
// required
|
||||
let local_site_form = LocalSiteInsertForm::builder()
|
||||
.site_id(site.id)
|
||||
.require_email_verification(Some(true))
|
||||
.application_question(Some(".".to_string()))
|
||||
.registration_mode(Some(RegistrationMode::RequireApplication))
|
||||
.site_setup(Some(true))
|
||||
.build();
|
||||
let local_site = LocalSite::create(pool, &local_site_form).await.unwrap();
|
||||
|
||||
// Required to have a working local SiteView when updating the site to change email verification
|
||||
// requirement or registration mode
|
||||
let rate_limit_form = LocalSiteRateLimitInsertForm::builder()
|
||||
.local_site_id(local_site.id)
|
||||
.build();
|
||||
LocalSiteRateLimit::create(pool, &rate_limit_form)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok((inserted_instance, admin_local_user_view))
|
||||
}
|
||||
|
||||
async fn signup(
|
||||
pool: &mut DbPool<'_>,
|
||||
instance_id: InstanceId,
|
||||
name: &str,
|
||||
email: Option<&str>,
|
||||
) -> LemmyResult<(LocalUser, RegistrationApplication)> {
|
||||
let person_insert_form = PersonInsertForm::test_form(instance_id, name);
|
||||
let person = Person::create(pool, &person_insert_form).await?;
|
||||
|
||||
let local_user_insert_form = match email {
|
||||
Some(email) => LocalUserInsertForm {
|
||||
email: Some(email.to_string()),
|
||||
email_verified: Some(false),
|
||||
..LocalUserInsertForm::test_form(person.id)
|
||||
},
|
||||
None => LocalUserInsertForm::test_form(person.id),
|
||||
};
|
||||
|
||||
let local_user = LocalUser::create(pool, &local_user_insert_form, vec![]).await?;
|
||||
|
||||
let application_insert_form = RegistrationApplicationInsertForm {
|
||||
local_user_id: local_user.id,
|
||||
answer: "x".to_string(),
|
||||
};
|
||||
let application = RegistrationApplication::create(pool, &application_insert_form).await?;
|
||||
|
||||
Ok((local_user, application))
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
async fn get_application_statuses(
|
||||
context: &Data<LemmyContext>,
|
||||
admin: LocalUserView,
|
||||
) -> LemmyResult<(
|
||||
Json<GetUnreadRegistrationApplicationCountResponse>,
|
||||
Json<ListRegistrationApplicationsResponse>,
|
||||
Json<ListRegistrationApplicationsResponse>,
|
||||
)> {
|
||||
let application_count =
|
||||
get_unread_registration_application_count(context.reset_request_count(), admin.clone()).await?;
|
||||
|
||||
let unread_applications = list_registration_applications(
|
||||
Query::from_query("unread_only=true").unwrap(),
|
||||
context.reset_request_count(),
|
||||
admin.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let all_applications = list_registration_applications(
|
||||
Query::from_query("unread_only=false").unwrap(),
|
||||
context.reset_request_count(),
|
||||
admin,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((application_count, unread_applications, all_applications))
|
||||
}
|
||||
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_application_approval() -> LemmyResult<()> {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
let pool = &mut context.pool();
|
||||
|
||||
let (instance, admin_local_user_view) = create_test_site(&context).await?;
|
||||
|
||||
// Non-unread counts unfortunately are duplicated due to different types (i64 vs usize)
|
||||
let mut expected_total_applications = 0;
|
||||
let mut expected_unread_applications = 0u8;
|
||||
|
||||
let (local_user_with_email, app_with_email) =
|
||||
signup(pool, instance.id, "user_w_email", Some("lemmy@localhost")).await?;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// When email verification is required and the email is not verified the application should not
|
||||
// be visible to admins
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
LocalUser::update(
|
||||
pool,
|
||||
local_user_with_email.id,
|
||||
&LocalUserUpdateForm {
|
||||
email_verified: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
expected_total_applications += 1;
|
||||
expected_unread_applications += 1;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// When email verification is required and the email is verified the application should be
|
||||
// visible to admins
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert!(
|
||||
!unread_applications.registration_applications[0]
|
||||
.creator_local_user
|
||||
.accepted_application
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
let approval = approve_registration_application(
|
||||
Json(ApproveRegistrationApplication {
|
||||
id: app_with_email.id,
|
||||
approve: true,
|
||||
deny_reason: None,
|
||||
}),
|
||||
context.reset_request_count(),
|
||||
admin_local_user_view.clone(),
|
||||
)
|
||||
.await;
|
||||
// Approval should be processed up until email sending is attempted
|
||||
assert!(approval.is_err_and(|e| e.error_type == LemmyErrorType::NoEmailSetup));
|
||||
|
||||
expected_unread_applications -= 1;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// When the application is approved it should only be returned for unread queries
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
assert!(
|
||||
all_applications.registration_applications[0]
|
||||
.creator_local_user
|
||||
.accepted_application
|
||||
);
|
||||
|
||||
let (_local_user, app_with_email_2) = signup(
|
||||
pool,
|
||||
instance.id,
|
||||
"user_w_email_2",
|
||||
Some("lemmy2@localhost"),
|
||||
)
|
||||
.await?;
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// Email not verified, so application still not visible
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
update_site(
|
||||
Json(EditSite {
|
||||
require_email_verification: Some(false),
|
||||
..Default::default()
|
||||
}),
|
||||
context.reset_request_count(),
|
||||
admin_local_user_view.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// TODO: There is probably a better way to ensure cache invalidation
|
||||
tokio::time::sleep(CACHE_DURATION_API).await;
|
||||
|
||||
expected_total_applications += 1;
|
||||
expected_unread_applications += 1;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// After disabling email verification the application should now be visible
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
approve_registration_application(
|
||||
Json(ApproveRegistrationApplication {
|
||||
id: app_with_email_2.id,
|
||||
approve: false,
|
||||
deny_reason: None,
|
||||
}),
|
||||
context.reset_request_count(),
|
||||
admin_local_user_view.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
expected_unread_applications -= 1;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// Denied applications should not be marked as unread
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
signup(pool, instance.id, "user_wo_email", None).await?;
|
||||
|
||||
expected_total_applications += 1;
|
||||
expected_unread_applications += 1;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// New user without email should immediately be visible
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
signup(pool, instance.id, "user_w_email_3", None).await?;
|
||||
|
||||
expected_total_applications += 1;
|
||||
expected_unread_applications += 1;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// New user with email should immediately be visible
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
update_site(
|
||||
Json(EditSite {
|
||||
registration_mode: Some(RegistrationMode::Open),
|
||||
..Default::default()
|
||||
}),
|
||||
context.reset_request_count(),
|
||||
admin_local_user_view.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// TODO: There is probably a better way to ensure cache invalidation
|
||||
tokio::time::sleep(CACHE_DURATION_API).await;
|
||||
|
||||
let (application_count, unread_applications, all_applications) =
|
||||
get_application_statuses(&context, admin_local_user_view.clone()).await?;
|
||||
|
||||
// TODO: At this time applications do not get approved when switching to open registration, so the
|
||||
// numbers will not change. See https://github.com/LemmyNet/lemmy/issues/4969
|
||||
// expected_application_count = 0;
|
||||
// expected_unread_applications_len = 0;
|
||||
|
||||
// When applications are not required all previous applications should become approved but still
|
||||
// visible
|
||||
assert_eq!(
|
||||
application_count.registration_applications,
|
||||
i64::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
unread_applications.registration_applications.len(),
|
||||
usize::from(expected_unread_applications),
|
||||
);
|
||||
assert_eq!(
|
||||
all_applications.registration_applications.len(),
|
||||
expected_total_applications,
|
||||
);
|
||||
|
||||
LocalSite::delete(pool).await?;
|
||||
// Instance deletion cascades cleanup of all created persons
|
||||
Instance::delete(pool, instance.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
site::GetUnreadRegistrationApplicationCountResponse,
|
||||
|
|
|
@ -34,7 +34,6 @@ full = [
|
|||
"reqwest",
|
||||
"actix-web",
|
||||
"futures",
|
||||
"once_cell",
|
||||
"jsonwebtoken",
|
||||
"mime",
|
||||
]
|
||||
|
@ -61,7 +60,6 @@ reqwest = { workspace = true, optional = true }
|
|||
ts-rs = { workspace = true, optional = true }
|
||||
moka.workspace = true
|
||||
anyhow.workspace = true
|
||||
once_cell = { workspace = true, optional = true }
|
||||
actix-web = { workspace = true, optional = true }
|
||||
enum-map = { workspace = true }
|
||||
urlencoding = { workspace = true }
|
||||
|
|
|
@ -36,8 +36,8 @@ pub async fn build_comment_response(
|
|||
local_user_view: Option<LocalUserView>,
|
||||
recipient_ids: Vec<LocalUserId>,
|
||||
) -> LemmyResult<CommentResponse> {
|
||||
let person_id = local_user_view.map(|l| l.person.id);
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id)
|
||||
let local_user = local_user_view.map(|l| l.local_user);
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, local_user.as_ref())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
Ok(CommentResponse {
|
||||
|
@ -54,11 +54,11 @@ pub async fn build_community_response(
|
|||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
|
||||
.await
|
||||
.is_ok();
|
||||
let person_id = local_user_view.person.id;
|
||||
let local_user = local_user_view.local_user;
|
||||
let community_view = CommunityView::read(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
Some(person_id),
|
||||
Some(&local_user),
|
||||
is_mod_or_admin,
|
||||
)
|
||||
.await?
|
||||
|
@ -74,16 +74,17 @@ pub async fn build_community_response(
|
|||
pub async fn build_post_response(
|
||||
context: &LemmyContext,
|
||||
community_id: CommunityId,
|
||||
person: &Person,
|
||||
local_user_view: LocalUserView,
|
||||
post_id: PostId,
|
||||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id)
|
||||
let local_user = local_user_view.local_user;
|
||||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
|
||||
.await
|
||||
.is_ok();
|
||||
let post_view = PostView::read(
|
||||
&mut context.pool(),
|
||||
post_id,
|
||||
Some(person.id),
|
||||
Some(&local_user),
|
||||
is_mod_or_admin,
|
||||
)
|
||||
.await?
|
||||
|
@ -99,14 +100,20 @@ pub async fn send_local_notifs(
|
|||
person: &Person,
|
||||
do_send_email: bool,
|
||||
context: &LemmyContext,
|
||||
local_user_view: Option<&LocalUserView>,
|
||||
) -> LemmyResult<Vec<LocalUserId>> {
|
||||
let mut recipient_ids = Vec::new();
|
||||
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||
|
||||
// let person = my_local_user.person;
|
||||
// Read the comment view to get extra info
|
||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
local_user_view.map(|view| &view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let comment = comment_view.comment;
|
||||
let post = comment_view.post;
|
||||
let community = comment_view.community;
|
||||
|
|
|
@ -112,18 +112,11 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let new_person = PersonInsertForm::builder()
|
||||
.name("Gerry9812".into())
|
||||
.public_key("pubkey".to_string())
|
||||
.instance_id(inserted_instance.id)
|
||||
.build();
|
||||
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
|
||||
|
||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||
|
||||
let local_user_form = LocalUserInsertForm::builder()
|
||||
.person_id(inserted_person.id)
|
||||
.password_encrypted("123456".to_string())
|
||||
.build();
|
||||
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
|
||||
|
||||
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
||||
.await
|
||||
|
|
|
@ -55,7 +55,7 @@ impl LemmyContext {
|
|||
/// Initialize a context for use in tests which blocks federation network calls.
|
||||
///
|
||||
/// Do not use this in production code.
|
||||
pub async fn init_test_context() -> Data<LemmyContext> {
|
||||
pub async fn init_test_federation_config() -> FederationConfig<LemmyContext> {
|
||||
// call this to run migrations
|
||||
let pool = build_db_pool_for_tests().await;
|
||||
|
||||
|
@ -70,14 +70,19 @@ impl LemmyContext {
|
|||
let rate_limit_cell = RateLimitCell::with_test_config();
|
||||
|
||||
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
|
||||
let config = FederationConfig::builder()
|
||||
|
||||
FederationConfig::builder()
|
||||
.domain(context.settings().hostname.clone())
|
||||
.app_data(context)
|
||||
.debug(true)
|
||||
// Dont allow any network fetches
|
||||
.http_fetch_limit(0)
|
||||
.build()
|
||||
.await
|
||||
.expect("build federation config");
|
||||
.expect("build federation config")
|
||||
}
|
||||
pub async fn init_test_context() -> Data<LemmyContext> {
|
||||
let config = Self::init_test_federation_config().await;
|
||||
config.to_request_data()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use lemmy_db_schema::{
|
||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||
sensitive::SensitiveString,
|
||||
source::site::Site,
|
||||
source::{login_token::LoginToken, site::Site},
|
||||
CommentSortType,
|
||||
ListingType,
|
||||
PostListingMode,
|
||||
|
@ -441,3 +441,10 @@ pub struct ListMedia {
|
|||
pub struct ListMediaResponse {
|
||||
pub images: Vec<LocalImageView>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct ListLoginsResponse {
|
||||
pub logins: Vec<LoginToken>,
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
|
|||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
use ts_rs::TS;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -20,8 +19,7 @@ use url::Url;
|
|||
pub struct CreatePost {
|
||||
pub name: String,
|
||||
pub community_id: CommunityId,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub url: Option<Url>,
|
||||
pub url: Option<String>,
|
||||
/// An optional body for the post in markdown.
|
||||
pub body: Option<String>,
|
||||
/// An optional alt_text, usable for image posts.
|
||||
|
@ -30,9 +28,8 @@ pub struct CreatePost {
|
|||
pub honeypot: Option<String>,
|
||||
pub nsfw: Option<bool>,
|
||||
pub language_id: Option<LanguageId>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
/// Instead of fetching a thumbnail, use a custom one.
|
||||
pub custom_thumbnail: Option<Url>,
|
||||
pub custom_thumbnail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -82,6 +79,10 @@ pub struct GetPosts {
|
|||
pub liked_only: Option<bool>,
|
||||
pub disliked_only: Option<bool>,
|
||||
pub show_hidden: Option<bool>,
|
||||
/// If true, then show the read posts (even if your user setting is to hide them)
|
||||
pub show_read: Option<bool>,
|
||||
/// If true, then show the nsfw posts (even if your user setting is to hide them)
|
||||
pub show_nsfw: Option<bool>,
|
||||
pub page_cursor: Option<PaginationCursor>,
|
||||
}
|
||||
|
||||
|
@ -114,17 +115,15 @@ pub struct CreatePostLike {
|
|||
pub struct EditPost {
|
||||
pub post_id: PostId,
|
||||
pub name: Option<String>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub url: Option<Url>,
|
||||
pub url: Option<String>,
|
||||
/// An optional body for the post in markdown.
|
||||
pub body: Option<String>,
|
||||
/// An optional alt_text, usable for image posts.
|
||||
pub alt_text: Option<String>,
|
||||
pub nsfw: Option<bool>,
|
||||
pub language_id: Option<LanguageId>,
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
/// Instead of fetching a thumbnail, use a custom one.
|
||||
pub custom_thumbnail: Option<Url>,
|
||||
pub custom_thumbnail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -249,8 +248,7 @@ pub struct ListPostReportsResponse {
|
|||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Get metadata for a given site.
|
||||
pub struct GetSiteMetadata {
|
||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||
pub url: Url,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
|
|
@ -8,10 +8,11 @@ use crate::{
|
|||
use activitypub_federation::config::Data;
|
||||
use chrono::{DateTime, Utc};
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use futures::StreamExt;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::DbUrl,
|
||||
source::{
|
||||
images::{LocalImage, LocalImageForm},
|
||||
images::{ImageDetailsForm, LocalImage, LocalImageForm},
|
||||
local_site::LocalSite,
|
||||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
|
@ -23,7 +24,12 @@ use lemmy_utils::{
|
|||
VERSION,
|
||||
};
|
||||
use mime::Mime;
|
||||
use reqwest::{header::CONTENT_TYPE, Client, ClientBuilder};
|
||||
use reqwest::{
|
||||
header::{CONTENT_TYPE, RANGE},
|
||||
Client,
|
||||
ClientBuilder,
|
||||
Response,
|
||||
};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
|
@ -44,7 +50,17 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder {
|
|||
#[tracing::instrument(skip_all)]
|
||||
pub async fn fetch_link_metadata(url: &Url, context: &LemmyContext) -> LemmyResult<LinkMetadata> {
|
||||
info!("Fetching site metadata for url: {}", url);
|
||||
let response = context.client().get(url.as_str()).send().await?;
|
||||
// We only fetch the first 64kB of data in order to not waste bandwidth especially for large
|
||||
// binary files
|
||||
let bytes_to_fetch = 64 * 1024;
|
||||
let response = context
|
||||
.client()
|
||||
.get(url.as_str())
|
||||
// we only need the first chunk of data. Note that we do not check for Accept-Range so the
|
||||
// server may ignore this and still respond with the full response
|
||||
.header(RANGE, format!("bytes=0-{}", bytes_to_fetch - 1)) /* -1 because inclusive */
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let content_type: Option<Mime> = response
|
||||
.headers()
|
||||
|
@ -52,19 +68,57 @@ pub async fn fetch_link_metadata(url: &Url, context: &LemmyContext) -> LemmyResu
|
|||
.and_then(|h| h.to_str().ok())
|
||||
.and_then(|h| h.parse().ok());
|
||||
|
||||
// Can't use .text() here, because it only checks the content header, not the actual bytes
|
||||
// https://github.com/LemmyNet/lemmy/issues/1964
|
||||
let html_bytes = response.bytes().await.map_err(LemmyError::from)?.to_vec();
|
||||
let opengraph_data = {
|
||||
// if the content type is not text/html, we don't need to parse it
|
||||
let is_html = content_type
|
||||
.as_ref()
|
||||
.map(|c| {
|
||||
(c.type_() == mime::TEXT && c.subtype() == mime::HTML)
|
||||
||
|
||||
// application/xhtml+xml is a subset of HTML
|
||||
(c.type_() == mime::APPLICATION && c.subtype() == "xhtml")
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if !is_html {
|
||||
Default::default()
|
||||
} else {
|
||||
// Can't use .text() here, because it only checks the content header, not the actual bytes
|
||||
// https://github.com/LemmyNet/lemmy/issues/1964
|
||||
// So we want to do deep inspection of the actually returned bytes but need to be careful not
|
||||
// spend too much time parsing binary data as HTML
|
||||
|
||||
let opengraph_data = extract_opengraph_data(&html_bytes, url)
|
||||
.map_err(|e| info!("{e}"))
|
||||
.unwrap_or_default();
|
||||
// only take first bytes regardless of how many bytes the server returns
|
||||
let html_bytes = collect_bytes_until_limit(response, bytes_to_fetch).await?;
|
||||
extract_opengraph_data(&html_bytes, url)
|
||||
.map_err(|e| info!("{e}"))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
};
|
||||
Ok(LinkMetadata {
|
||||
opengraph_data,
|
||||
content_type: content_type.map(|c| c.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn collect_bytes_until_limit(
|
||||
response: Response,
|
||||
requested_bytes: usize,
|
||||
) -> Result<Vec<u8>, LemmyError> {
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut bytes = Vec::with_capacity(requested_bytes);
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk.map_err(LemmyError::from)?;
|
||||
// we may go over the requested size here but the important part is we don't keep aggregating
|
||||
// more chunks than needed
|
||||
bytes.extend_from_slice(&chunk);
|
||||
if bytes.len() >= requested_bytes {
|
||||
bytes.truncate(requested_bytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Generates and saves a post thumbnail and metadata.
|
||||
///
|
||||
/// Takes a callback to generate a send activity task, so that post can be federated with metadata.
|
||||
|
@ -209,6 +263,19 @@ pub struct PictrsFileDetails {
|
|||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl PictrsFileDetails {
|
||||
/// Builds the image form. This should always use the thumbnail_url,
|
||||
/// Because the post_view joins to it
|
||||
pub fn build_image_details_form(&self, thumbnail_url: &Url) -> ImageDetailsForm {
|
||||
ImageDetailsForm {
|
||||
link: thumbnail_url.clone().into(),
|
||||
width: self.width.into(),
|
||||
height: self.height.into(),
|
||||
content_type: self.content_type.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct PictrsPurgeResponse {
|
||||
msg: String,
|
||||
|
@ -316,11 +383,52 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L
|
|||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?;
|
||||
|
||||
LocalImage::create(&mut context.pool(), &form).await?;
|
||||
// Also store the details for the image
|
||||
let details_form = image.details.build_image_details_form(&thumbnail_url);
|
||||
LocalImage::create(&mut context.pool(), &form, &details_form).await?;
|
||||
|
||||
Ok(thumbnail_url)
|
||||
}
|
||||
|
||||
/// Fetches the image details for pictrs proxied images
|
||||
///
|
||||
/// We don't need to check for image mode, as that's already been done
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn fetch_pictrs_proxied_image_details(
|
||||
image_url: &Url,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<PictrsFileDetails> {
|
||||
let pictrs_url = context.settings().pictrs_config()?.url;
|
||||
let encoded_image_url = encode(image_url.as_str());
|
||||
|
||||
// Pictrs needs you to fetch the proxied image before you can fetch the details
|
||||
let proxy_url = format!("{pictrs_url}image/original?proxy={encoded_image_url}");
|
||||
|
||||
let res = context
|
||||
.client()
|
||||
.get(&proxy_url)
|
||||
.timeout(REQWEST_TIMEOUT)
|
||||
.send()
|
||||
.await?
|
||||
.status();
|
||||
if !res.is_success() {
|
||||
Err(LemmyErrorType::NotAnImageType)?
|
||||
}
|
||||
|
||||
let details_url = format!("{pictrs_url}image/details/original?proxy={encoded_image_url}");
|
||||
|
||||
let res = context
|
||||
.client()
|
||||
.get(&details_url)
|
||||
.timeout(REQWEST_TIMEOUT)
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
// TODO: get rid of this by reading content type from db
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> LemmyResult<()> {
|
||||
|
@ -338,16 +446,19 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Lemm
|
|||
}
|
||||
}
|
||||
|
||||
/// When adding a new avatar or similar image, delete the old one.
|
||||
/// When adding a new avatar, banner or similar image, delete the old one.
|
||||
pub async fn replace_image(
|
||||
new_image: &Option<String>,
|
||||
new_image: &Option<Option<DbUrl>>,
|
||||
old_image: &Option<DbUrl>,
|
||||
context: &Data<LemmyContext>,
|
||||
) -> LemmyResult<()> {
|
||||
if new_image.is_some() {
|
||||
// Ignore errors because image may be stored externally.
|
||||
if let Some(avatar) = &old_image {
|
||||
let image = LocalImage::delete_by_url(&mut context.pool(), avatar)
|
||||
if let (Some(Some(new_image)), Some(old_image)) = (new_image, old_image) {
|
||||
// Note: Oftentimes front ends will include the current image in the form.
|
||||
// In this case, deleting `old_image` would also be deletion of `new_image`,
|
||||
// so the deletion must be skipped for the image to be kept.
|
||||
if new_image != old_image {
|
||||
// Ignore errors because image may be stored externally.
|
||||
let image = LocalImage::delete_by_url(&mut context.pool(), old_image)
|
||||
.await
|
||||
.ok();
|
||||
if let Some(image) = image {
|
||||
|
|
|
@ -13,7 +13,7 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views::structs::PrivateMessageView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
|
@ -28,7 +28,7 @@ type MatchOutgoingActivitiesBoxed =
|
|||
Box<for<'a> fn(SendActivityData, &'a Data<LemmyContext>) -> BoxFuture<'a, LemmyResult<()>>>;
|
||||
|
||||
/// This static is necessary so that the api_common crates don't need to depend on lemmy_apub
|
||||
pub static MATCH_OUTGOING_ACTIVITIES: OnceCell<MatchOutgoingActivitiesBoxed> = OnceCell::new();
|
||||
pub static MATCH_OUTGOING_ACTIVITIES: OnceLock<MatchOutgoingActivitiesBoxed> = OnceLock::new();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SendActivityData {
|
||||
|
@ -101,7 +101,7 @@ pub enum SendActivityData {
|
|||
|
||||
// TODO: instead of static, move this into LemmyContext. make sure that stopping the process with
|
||||
// ctrl+c still works.
|
||||
static ACTIVITY_CHANNEL: Lazy<ActivityChannel> = Lazy::new(|| {
|
||||
static ACTIVITY_CHANNEL: LazyLock<ActivityChannel> = LazyLock::new(|| {
|
||||
let (sender, receiver) = mpsc::unbounded_channel();
|
||||
let weak_sender = sender.downgrade();
|
||||
ActivityChannel {
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
use crate::federate_retry_sleep_duration;
|
||||
use chrono::{DateTime, Utc};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommentId, CommunityId, InstanceId, LanguageId, PersonId, PostId},
|
||||
newtypes::{
|
||||
CommentId,
|
||||
CommunityId,
|
||||
InstanceId,
|
||||
LanguageId,
|
||||
PersonId,
|
||||
PostId,
|
||||
RegistrationApplicationId,
|
||||
},
|
||||
source::{
|
||||
community::Community,
|
||||
federation_queue_state::FederationQueueState,
|
||||
instance::Instance,
|
||||
language::Language,
|
||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||
person::Person,
|
||||
tagline::Tagline,
|
||||
},
|
||||
ListingType,
|
||||
|
@ -25,12 +35,9 @@ use lemmy_db_views::structs::{
|
|||
SiteView,
|
||||
};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommunityBlockView,
|
||||
CommunityFollowerView,
|
||||
CommunityModeratorView,
|
||||
CommunityView,
|
||||
InstanceBlockView,
|
||||
PersonBlockView,
|
||||
PersonView,
|
||||
};
|
||||
use lemmy_db_views_moderator::structs::{
|
||||
|
@ -329,9 +336,9 @@ pub struct MyUserInfo {
|
|||
pub local_user_view: LocalUserView,
|
||||
pub follows: Vec<CommunityFollowerView>,
|
||||
pub moderates: Vec<CommunityModeratorView>,
|
||||
pub community_blocks: Vec<CommunityBlockView>,
|
||||
pub instance_blocks: Vec<InstanceBlockView>,
|
||||
pub person_blocks: Vec<PersonBlockView>,
|
||||
pub community_blocks: Vec<Community>,
|
||||
pub instance_blocks: Vec<Instance>,
|
||||
pub person_blocks: Vec<Person>,
|
||||
pub discussion_languages: Vec<LanguageId>,
|
||||
}
|
||||
|
||||
|
@ -440,13 +447,22 @@ pub struct ListRegistrationApplicationsResponse {
|
|||
pub registration_applications: Vec<RegistrationApplicationView>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Gets a registration application for a person
|
||||
pub struct GetRegistrationApplication {
|
||||
pub person_id: PersonId,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Approves a registration application.
|
||||
pub struct ApproveRegistrationApplication {
|
||||
pub id: i32,
|
||||
pub id: RegistrationApplicationId,
|
||||
pub approve: bool,
|
||||
pub deny_reason: Option<String>,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use crate::{
|
||||
context::LemmyContext,
|
||||
request::{delete_image_from_pictrs, purge_image_from_pictrs},
|
||||
request::{
|
||||
delete_image_from_pictrs,
|
||||
fetch_pictrs_proxied_image_details,
|
||||
purge_image_from_pictrs,
|
||||
},
|
||||
site::{FederatedInstances, InstanceWithFederationState},
|
||||
};
|
||||
use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
||||
|
@ -49,10 +53,9 @@ use lemmy_utils::{
|
|||
CACHE_DURATION_FEDERATION,
|
||||
};
|
||||
use moka::future::Cache;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::{escape, Regex, RegexSet};
|
||||
use rosetta_i18n::{Language, LanguageId};
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, sync::LazyLock};
|
||||
use tracing::warn;
|
||||
use url::{ParseError, Url};
|
||||
use urlencoding::encode;
|
||||
|
@ -541,7 +544,7 @@ pub fn local_site_opt_to_sensitive(local_site: &Option<LocalSite>) -> bool {
|
|||
}
|
||||
|
||||
pub async fn get_url_blocklist(context: &LemmyContext) -> LemmyResult<RegexSet> {
|
||||
static URL_BLOCKLIST: Lazy<Cache<(), RegexSet>> = Lazy::new(|| {
|
||||
static URL_BLOCKLIST: LazyLock<Cache<(), RegexSet>> = LazyLock::new(|| {
|
||||
Cache::builder()
|
||||
.max_capacity(1)
|
||||
.time_to_live(CACHE_DURATION_FEDERATION)
|
||||
|
@ -948,7 +951,18 @@ pub async fn process_markdown(
|
|||
|
||||
if context.settings().pictrs_config()?.image_mode() == PictrsImageMode::ProxyAllImages {
|
||||
let (text, links) = markdown_rewrite_image_links(text);
|
||||
RemoteImage::create(&mut context.pool(), links).await?;
|
||||
|
||||
// Create images and image detail rows
|
||||
for link in links {
|
||||
// Insert image details for the remote image
|
||||
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
|
||||
if let Ok(details) = details_res {
|
||||
let proxied =
|
||||
build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
||||
let details_form = details.build_image_details_form(&proxied);
|
||||
RemoteImage::create(&mut context.pool(), &details_form).await?;
|
||||
}
|
||||
}
|
||||
Ok(text)
|
||||
} else {
|
||||
Ok(text)
|
||||
|
@ -983,8 +997,14 @@ async fn proxy_image_link_internal(
|
|||
Ok(link.into())
|
||||
} else if image_mode == PictrsImageMode::ProxyAllImages {
|
||||
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
|
||||
// This should fail softly, since pictrs might not even be running
|
||||
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
|
||||
|
||||
if let Ok(details) = details_res {
|
||||
let details_form = details.build_image_details_form(&proxied);
|
||||
RemoteImage::create(&mut context.pool(), &details_form).await?;
|
||||
};
|
||||
|
||||
RemoteImage::create(&mut context.pool(), vec![link]).await?;
|
||||
Ok(proxied.into())
|
||||
} else {
|
||||
Ok(link.into())
|
||||
|
@ -1003,26 +1023,25 @@ pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> Lemmy
|
|||
}
|
||||
|
||||
pub async fn proxy_image_link_opt_api(
|
||||
link: &Option<String>,
|
||||
link: Option<Option<DbUrl>>,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Option<Option<DbUrl>>> {
|
||||
proxy_image_link_api(link, context).await.map(Some)
|
||||
if let Some(Some(link)) = link {
|
||||
proxy_image_link(link.into(), context)
|
||||
.await
|
||||
.map(Some)
|
||||
.map(Some)
|
||||
} else {
|
||||
Ok(link)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn proxy_image_link_api(
|
||||
link: &Option<String>,
|
||||
link: Option<DbUrl>,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Option<DbUrl>> {
|
||||
let link: Option<DbUrl> = match link.as_ref().map(String::as_str) {
|
||||
// An empty string is an erase
|
||||
Some("") => None,
|
||||
Some(str_url) => Url::parse(str_url)
|
||||
.map(|u| Some(u.into()))
|
||||
.with_lemmy_type(LemmyErrorType::InvalidUrl)?,
|
||||
None => None,
|
||||
};
|
||||
if let Some(l) = link {
|
||||
proxy_image_link(l.into(), context).await.map(Some)
|
||||
if let Some(link) = link {
|
||||
proxy_image_link(link.into(), context).await.map(Some)
|
||||
} else {
|
||||
Ok(link)
|
||||
}
|
||||
|
@ -1123,35 +1142,13 @@ mod tests {
|
|||
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
|
||||
proxied.as_str()
|
||||
);
|
||||
|
||||
// This fails, because the details can't be fetched without pictrs running,
|
||||
// And a remote image won't be inserted.
|
||||
assert!(
|
||||
RemoteImage::validate(&mut context.pool(), remote_image.into())
|
||||
.await
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_diesel_option_overwrite_to_url() {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
|
||||
assert!(matches!(
|
||||
proxy_image_link_api(&None, &context).await,
|
||||
Ok(None)
|
||||
));
|
||||
assert!(matches!(
|
||||
proxy_image_link_opt_api(&Some(String::new()), &context).await,
|
||||
Ok(Some(None))
|
||||
));
|
||||
assert!(
|
||||
proxy_image_link_opt_api(&Some("invalid_url".to_string()), &context)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
let example_url = "https://lemmy-alpha/image.png";
|
||||
assert!(matches!(
|
||||
proxy_image_link_opt_api(&Some(example_url.to_string()), &context).await,
|
||||
Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ url = { workspace = true }
|
|||
futures.workspace = true
|
||||
uuid = { workspace = true }
|
||||
moka.workspace = true
|
||||
once_cell.workspace = true
|
||||
anyhow.workspace = true
|
||||
webmention = "0.5.0"
|
||||
accept-language = "3.1.0"
|
||||
|
|
|
@ -8,20 +8,18 @@ use lemmy_api_common::{
|
|||
utils::{
|
||||
check_community_user_action,
|
||||
check_post_deleted_or_removed,
|
||||
generate_local_apub_endpoint,
|
||||
get_url_blocklist,
|
||||
is_mod_or_admin,
|
||||
local_site_to_slur_regex,
|
||||
process_markdown,
|
||||
update_read_comments,
|
||||
EndpointType,
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::default_post_language,
|
||||
source::{
|
||||
actor_language::CommunityLanguage,
|
||||
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
|
||||
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
|
||||
comment_reply::{CommentReply, CommentReplyUpdateForm},
|
||||
local_site::LocalSite,
|
||||
person_mention::{PersonMention, PersonMentionUpdateForm},
|
||||
|
@ -47,7 +45,7 @@ pub async fn create_comment(
|
|||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&Some(content.clone()), false)?;
|
||||
is_valid_body_field(&content, false)?;
|
||||
|
||||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
|
@ -56,7 +54,7 @@ pub async fn create_comment(
|
|||
let post_view = PostView::read(
|
||||
&mut context.pool(),
|
||||
post_id,
|
||||
Some(local_user_view.person.id),
|
||||
Some(&local_user_view.local_user),
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
|
@ -126,25 +124,7 @@ pub async fn create_comment(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateComment)?;
|
||||
|
||||
// Necessary to update the ap_id
|
||||
let inserted_comment_id = inserted_comment.id;
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
|
||||
let apub_id = generate_local_apub_endpoint(
|
||||
EndpointType::Comment,
|
||||
&inserted_comment_id.to_string(),
|
||||
&protocol_and_hostname,
|
||||
)?;
|
||||
let updated_comment = Comment::update(
|
||||
&mut context.pool(),
|
||||
inserted_comment_id,
|
||||
&CommentUpdateForm {
|
||||
ap_id: Some(apub_id),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateComment)?;
|
||||
|
||||
// Scan the comment for user mentions, add those rows
|
||||
let mentions = scrape_text_for_mentions(&content);
|
||||
|
@ -154,6 +134,7 @@ pub async fn create_comment(
|
|||
&local_user_view.person,
|
||||
true,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
@ -169,7 +150,7 @@ pub async fn create_comment(
|
|||
.with_lemmy_type(LemmyErrorType::CouldntLikeComment)?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
SendActivityData::CreateComment(updated_comment.clone()),
|
||||
SendActivityData::CreateComment(inserted_comment.clone()),
|
||||
&context,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -21,9 +21,13 @@ pub async fn delete_comment(
|
|||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let orig_comment = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
// Dont delete it if its already been deleted.
|
||||
if orig_comment.comment.deleted == data.deleted {
|
||||
|
@ -55,8 +59,15 @@ pub async fn delete_comment(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
|
||||
|
||||
let recipient_ids =
|
||||
send_local_notifs(vec![], comment_id, &local_user_view.person, false, &context).await?;
|
||||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
comment_id,
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
)
|
||||
.await?;
|
||||
let updated_comment_id = updated_comment.id;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
|
|
|
@ -11,6 +11,7 @@ use lemmy_db_schema::{
|
|||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
comment_report::CommentReport,
|
||||
local_user::LocalUser,
|
||||
moderator::{ModRemoveComment, ModRemoveCommentForm},
|
||||
},
|
||||
traits::{Crud, Reportable},
|
||||
|
@ -25,9 +26,13 @@ pub async fn remove_comment(
|
|||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let orig_comment = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_mod_action(
|
||||
&local_user_view.person,
|
||||
|
@ -37,6 +42,14 @@ pub async fn remove_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
LocalUser::is_higher_mod_or_admin_check(
|
||||
&mut context.pool(),
|
||||
orig_comment.community.id,
|
||||
local_user_view.person.id,
|
||||
vec![orig_comment.creator.id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Don't allow removing or restoring comment which was deleted by user, as it would reveal
|
||||
// the comment text in mod log.
|
||||
if orig_comment.comment.deleted {
|
||||
|
@ -71,9 +84,10 @@ pub async fn remove_comment(
|
|||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
comment_id,
|
||||
&local_user_view.person.clone(),
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
)
|
||||
.await?;
|
||||
let updated_comment_id = updated_comment.id;
|
||||
|
|
|
@ -36,9 +36,13 @@ pub async fn update_comment(
|
|||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
let orig_comment = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -63,7 +67,9 @@ pub async fn update_comment(
|
|||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let content = process_markdown_opt(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&content, false)?;
|
||||
if let Some(content) = &content {
|
||||
is_valid_body_field(content, false)?;
|
||||
}
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let form = CommentUpdateForm {
|
||||
|
@ -85,6 +91,7 @@ pub async fn update_comment(
|
|||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ use lemmy_db_schema::{
|
|||
},
|
||||
},
|
||||
traits::{ApubActor, Crud, Followable, Joinable},
|
||||
utils::diesel_url_create,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::{
|
||||
|
@ -61,11 +62,18 @@ pub async fn create_community(
|
|||
check_slurs(&data.title, &slur_regex)?;
|
||||
let description =
|
||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
||||
let icon = proxy_image_link_api(&data.icon, &context).await?;
|
||||
let banner = proxy_image_link_api(&data.banner, &context).await?;
|
||||
|
||||
let icon = diesel_url_create(data.icon.as_deref())?;
|
||||
let icon = proxy_image_link_api(icon, &context).await?;
|
||||
|
||||
let banner = diesel_url_create(data.banner.as_deref())?;
|
||||
let banner = proxy_image_link_api(banner, &context).await?;
|
||||
|
||||
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
||||
is_valid_body_field(&data.description, false)?;
|
||||
|
||||
if let Some(desc) = &data.description {
|
||||
is_valid_body_field(desc, false)?;
|
||||
}
|
||||
|
||||
// Double check for duplicate community actor_ids
|
||||
let community_actor_id = generate_local_apub_endpoint(
|
||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
|||
local_site::LocalSite,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{diesel_option_overwrite, naive_now},
|
||||
utils::{diesel_string_update, diesel_url_update, naive_now},
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::{
|
||||
|
@ -40,18 +40,28 @@ pub async fn update_community(
|
|||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
check_slurs_opt(&data.title, &slur_regex)?;
|
||||
let description =
|
||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&data.description, false)?;
|
||||
|
||||
let description = diesel_string_update(
|
||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context)
|
||||
.await?
|
||||
.as_deref(),
|
||||
);
|
||||
|
||||
if let Some(Some(desc)) = &description {
|
||||
is_valid_body_field(desc, false)?;
|
||||
}
|
||||
|
||||
let old_community = Community::read(&mut context.pool(), data.community_id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
|
||||
replace_image(&data.icon, &old_community.icon, &context).await?;
|
||||
replace_image(&data.banner, &old_community.banner, &context).await?;
|
||||
|
||||
let description = diesel_option_overwrite(description);
|
||||
let icon = proxy_image_link_opt_api(&data.icon, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
||||
let icon = diesel_url_update(data.icon.as_deref())?;
|
||||
replace_image(&icon, &old_community.icon, &context).await?;
|
||||
let icon = proxy_image_link_opt_api(icon, &context).await?;
|
||||
|
||||
let banner = diesel_url_update(data.banner.as_deref())?;
|
||||
replace_image(&banner, &old_community.banner, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(banner, &context).await?;
|
||||
|
||||
// Verify its a mod (only mods can edit it)
|
||||
check_community_mod_action(
|
||||
|
|
|
@ -8,13 +8,11 @@ use lemmy_api_common::{
|
|||
send_activity::SendActivityData,
|
||||
utils::{
|
||||
check_community_user_action,
|
||||
generate_local_apub_endpoint,
|
||||
get_url_blocklist,
|
||||
honeypot_check,
|
||||
local_site_to_slur_regex,
|
||||
mark_post_as_read,
|
||||
process_markdown_opt,
|
||||
EndpointType,
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
|
@ -23,9 +21,10 @@ use lemmy_db_schema::{
|
|||
actor_language::CommunityLanguage,
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
|
||||
post::{Post, PostInsertForm, PostLike, PostLikeForm},
|
||||
},
|
||||
traits::{Crud, Likeable},
|
||||
utils::diesel_url_create,
|
||||
CommunityVisibility,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
|
@ -36,12 +35,11 @@ use lemmy_utils::{
|
|||
utils::{
|
||||
slurs::check_slurs,
|
||||
validation::{
|
||||
check_url_scheme,
|
||||
clean_url_params,
|
||||
is_url_blocked,
|
||||
is_valid_alt_text_field,
|
||||
is_valid_body_field,
|
||||
is_valid_post_title,
|
||||
is_valid_url,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -64,16 +62,27 @@ pub async fn create_post(
|
|||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
|
||||
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
|
||||
let data_url = data.url.as_ref();
|
||||
let url = data_url.map(clean_url_params); // TODO no good way to handle a "clear"
|
||||
let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params);
|
||||
let url = diesel_url_create(data.url.as_deref())?;
|
||||
let custom_thumbnail = diesel_url_create(data.custom_thumbnail.as_deref())?;
|
||||
|
||||
is_valid_post_title(&data.name)?;
|
||||
is_valid_body_field(&body, true)?;
|
||||
is_valid_alt_text_field(&data.alt_text)?;
|
||||
is_url_blocked(&url, &url_blocklist)?;
|
||||
check_url_scheme(&url)?;
|
||||
check_url_scheme(&custom_thumbnail)?;
|
||||
|
||||
if let Some(url) = &url {
|
||||
is_url_blocked(url, &url_blocklist)?;
|
||||
is_valid_url(url)?;
|
||||
}
|
||||
|
||||
if let Some(custom_thumbnail) = &custom_thumbnail {
|
||||
is_valid_url(custom_thumbnail)?;
|
||||
}
|
||||
|
||||
if let Some(alt_text) = &data.alt_text {
|
||||
is_valid_alt_text_field(alt_text)?;
|
||||
}
|
||||
|
||||
if let Some(body) = &body {
|
||||
is_valid_body_field(body, true)?;
|
||||
}
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -136,27 +145,9 @@ pub async fn create_post(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
|
||||
|
||||
let inserted_post_id = inserted_post.id;
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let apub_id = generate_local_apub_endpoint(
|
||||
EndpointType::Post,
|
||||
&inserted_post_id.to_string(),
|
||||
&protocol_and_hostname,
|
||||
)?;
|
||||
let updated_post = Post::update(
|
||||
&mut context.pool(),
|
||||
inserted_post_id,
|
||||
&PostUpdateForm {
|
||||
ap_id: Some(apub_id),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
|
||||
|
||||
generate_post_link_metadata(
|
||||
updated_post.clone(),
|
||||
custom_thumbnail,
|
||||
inserted_post.clone(),
|
||||
custom_thumbnail.map(Into::into),
|
||||
|post| Some(SendActivityData::CreatePost(post)),
|
||||
Some(local_site),
|
||||
context.reset_request_count(),
|
||||
|
@ -178,11 +169,11 @@ pub async fn create_post(
|
|||
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
|
||||
if let Some(url) = updated_post.url.clone() {
|
||||
if let Some(url) = inserted_post.url.clone() {
|
||||
if community.visibility == CommunityVisibility::Public {
|
||||
spawn_try_task(async move {
|
||||
let mut webmention =
|
||||
Webmention::new::<Url>(updated_post.ap_id.clone().into(), url.clone().into())?;
|
||||
Webmention::new::<Url>(inserted_post.ap_id.clone().into(), url.clone().into())?;
|
||||
webmention.set_checked(true);
|
||||
match webmention
|
||||
.send()
|
||||
|
@ -197,5 +188,5 @@ pub async fn create_post(
|
|||
}
|
||||
};
|
||||
|
||||
build_post_response(&context, community_id, &local_user_view.person, post_id).await
|
||||
build_post_response(&context, community_id, local_user_view, post_id).await
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ pub async fn delete_post(
|
|||
build_post_response(
|
||||
&context,
|
||||
orig_post.community_id,
|
||||
&local_user_view.person,
|
||||
local_user_view,
|
||||
data.post_id,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -55,9 +55,15 @@ pub async fn get_post(
|
|||
.await
|
||||
.is_ok();
|
||||
|
||||
let post_view = PostView::read(&mut context.pool(), post_id, person_id, is_mod_or_admin)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
let local_user = local_user_view.map(|l| l.local_user);
|
||||
let post_view = PostView::read(
|
||||
&mut context.pool(),
|
||||
post_id,
|
||||
local_user.as_ref(),
|
||||
is_mod_or_admin,
|
||||
)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?;
|
||||
|
||||
let post_id = post_view.post.id;
|
||||
if let Some(person_id) = person_id {
|
||||
|
@ -76,7 +82,7 @@ pub async fn get_post(
|
|||
let community_view = CommunityView::read(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
person_id,
|
||||
local_user.as_ref(),
|
||||
is_mod_or_admin,
|
||||
)
|
||||
.await?
|
||||
|
@ -88,6 +94,7 @@ pub async fn get_post(
|
|||
let cross_posts = if let Some(url) = &post_view.post.url {
|
||||
let mut x_posts = PostQuery {
|
||||
url_search: Some(url.inner().as_str().into()),
|
||||
local_user: local_user.as_ref(),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&local_site.site, &mut context.pool())
|
||||
|
|
|
@ -9,6 +9,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
local_user::LocalUser,
|
||||
moderator::{ModRemovePost, ModRemovePostForm},
|
||||
post::{Post, PostUpdateForm},
|
||||
post_report::PostReport,
|
||||
|
@ -37,6 +38,14 @@ pub async fn remove_post(
|
|||
)
|
||||
.await?;
|
||||
|
||||
LocalUser::is_higher_mod_or_admin_check(
|
||||
&mut context.pool(),
|
||||
orig_post.community_id,
|
||||
local_user_view.person.id,
|
||||
vec![orig_post.creator_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Update the post
|
||||
let post_id = data.post_id;
|
||||
let removed = data.removed;
|
||||
|
@ -73,11 +82,5 @@ pub async fn remove_post(
|
|||
)
|
||||
.await?;
|
||||
|
||||
build_post_response(
|
||||
&context,
|
||||
orig_post.community_id,
|
||||
&local_user_view.person,
|
||||
post_id,
|
||||
)
|
||||
.await
|
||||
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
||||
}
|
||||
|
|
|
@ -20,20 +20,19 @@ use lemmy_db_schema::{
|
|||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{diesel_option_overwrite, naive_now},
|
||||
utils::{diesel_string_update, diesel_url_update, naive_now},
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::{
|
||||
slurs::check_slurs_opt,
|
||||
slurs::check_slurs,
|
||||
validation::{
|
||||
check_url_scheme,
|
||||
clean_url_params,
|
||||
is_url_blocked,
|
||||
is_valid_alt_text_field,
|
||||
is_valid_body_field,
|
||||
is_valid_post_title,
|
||||
is_valid_url,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -47,26 +46,43 @@ pub async fn update_post(
|
|||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
|
||||
// TODO No good way to handle a clear.
|
||||
// Issue link: https://github.com/LemmyNet/lemmy/issues/2287
|
||||
let url = data.url.as_ref().map(clean_url_params);
|
||||
let custom_thumbnail = data.custom_thumbnail.as_ref().map(clean_url_params);
|
||||
let url = diesel_url_update(data.url.as_deref())?;
|
||||
|
||||
let custom_thumbnail = diesel_url_update(data.custom_thumbnail.as_deref())?;
|
||||
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
|
||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
check_slurs_opt(&data.name, &slur_regex)?;
|
||||
let body = process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context).await?;
|
||||
|
||||
let body = diesel_string_update(
|
||||
process_markdown_opt(&data.body, &slur_regex, &url_blocklist, &context)
|
||||
.await?
|
||||
.as_deref(),
|
||||
);
|
||||
|
||||
let alt_text = diesel_string_update(data.alt_text.as_deref());
|
||||
|
||||
if let Some(name) = &data.name {
|
||||
is_valid_post_title(name)?;
|
||||
check_slurs(name, &slur_regex)?;
|
||||
}
|
||||
|
||||
is_valid_body_field(&body, true)?;
|
||||
is_valid_alt_text_field(&data.alt_text)?;
|
||||
is_url_blocked(&url, &url_blocklist)?;
|
||||
check_url_scheme(&url)?;
|
||||
check_url_scheme(&custom_thumbnail)?;
|
||||
if let Some(Some(body)) = &body {
|
||||
is_valid_body_field(body, true)?;
|
||||
}
|
||||
|
||||
if let Some(Some(alt_text)) = &alt_text {
|
||||
is_valid_alt_text_field(alt_text)?;
|
||||
}
|
||||
|
||||
if let Some(Some(url)) = &url {
|
||||
is_url_blocked(url, &url_blocklist)?;
|
||||
is_valid_url(url)?;
|
||||
}
|
||||
|
||||
if let Some(Some(custom_thumbnail)) = &custom_thumbnail {
|
||||
is_valid_url(custom_thumbnail)?;
|
||||
}
|
||||
|
||||
let post_id = data.post_id;
|
||||
let orig_post = Post::read(&mut context.pool(), post_id)
|
||||
|
@ -95,9 +111,9 @@ pub async fn update_post(
|
|||
|
||||
let post_form = PostUpdateForm {
|
||||
name: data.name.clone(),
|
||||
url: Some(url.map(Into::into)),
|
||||
body: diesel_option_overwrite(body),
|
||||
alt_text: diesel_option_overwrite(data.alt_text.clone()),
|
||||
url,
|
||||
body,
|
||||
alt_text,
|
||||
nsfw: data.nsfw,
|
||||
language_id: data.language_id,
|
||||
updated: Some(Some(naive_now())),
|
||||
|
@ -111,7 +127,7 @@ pub async fn update_post(
|
|||
|
||||
generate_post_link_metadata(
|
||||
updated_post.clone(),
|
||||
custom_thumbnail,
|
||||
custom_thumbnail.flatten().map(Into::into),
|
||||
|post| Some(SendActivityData::UpdatePost(post)),
|
||||
Some(local_site),
|
||||
context.reset_request_count(),
|
||||
|
@ -121,7 +137,7 @@ pub async fn update_post(
|
|||
build_post_response(
|
||||
context.deref(),
|
||||
orig_post.community_id,
|
||||
&local_user_view.person,
|
||||
local_user_view,
|
||||
post_id,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -6,19 +6,17 @@ use lemmy_api_common::{
|
|||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{
|
||||
check_person_block,
|
||||
generate_local_apub_endpoint,
|
||||
get_interface_language,
|
||||
get_url_blocklist,
|
||||
local_site_to_slur_regex,
|
||||
process_markdown,
|
||||
send_email_to_user,
|
||||
EndpointType,
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
local_site::LocalSite,
|
||||
private_message::{PrivateMessage, PrivateMessageInsertForm, PrivateMessageUpdateForm},
|
||||
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
|
@ -39,7 +37,7 @@ pub async fn create_private_message(
|
|||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&Some(content.clone()), false)?;
|
||||
is_valid_body_field(&content, false)?;
|
||||
|
||||
check_person_block(
|
||||
local_user_view.person.id,
|
||||
|
@ -58,24 +56,6 @@ pub async fn create_private_message(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
|
||||
|
||||
let inserted_private_message_id = inserted_private_message.id;
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let apub_id = generate_local_apub_endpoint(
|
||||
EndpointType::PrivateMessage,
|
||||
&inserted_private_message_id.to_string(),
|
||||
&protocol_and_hostname,
|
||||
)?;
|
||||
PrivateMessage::update(
|
||||
&mut context.pool(),
|
||||
inserted_private_message.id,
|
||||
&PrivateMessageUpdateForm {
|
||||
ap_id: Some(apub_id),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
|
||||
|
||||
let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
|
||||
|
|
|
@ -41,7 +41,7 @@ pub async fn update_private_message(
|
|||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&Some(content.clone()), false)?;
|
||||
is_valid_body_field(&content, false)?;
|
||||
|
||||
let private_message_id = data.private_message_id;
|
||||
PrivateMessage::update(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::site::{application_question_check, site_default_post_listing_type_check};
|
||||
use activitypub_federation::http_signatures::generate_actor_keypair;
|
||||
use actix_web::web::{Data, Json};
|
||||
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
site::{CreateSite, SiteResponse},
|
||||
|
@ -11,7 +11,7 @@ use lemmy_api_common::{
|
|||
local_site_rate_limit_to_rate_limit_config,
|
||||
local_site_to_slur_regex,
|
||||
process_markdown_opt,
|
||||
proxy_image_link_opt_api,
|
||||
proxy_image_link_api,
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
|
@ -23,7 +23,7 @@ use lemmy_db_schema::{
|
|||
tagline::Tagline,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{diesel_option_overwrite, naive_now},
|
||||
utils::{diesel_string_update, diesel_url_create, naive_now},
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
use lemmy_utils::{
|
||||
|
@ -61,21 +61,25 @@ pub async fn create_site(
|
|||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
|
||||
let icon = proxy_image_link_opt_api(&data.icon, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
||||
|
||||
let icon = diesel_url_create(data.icon.as_deref())?;
|
||||
let icon = proxy_image_link_api(icon, &context).await?;
|
||||
|
||||
let banner = diesel_url_create(data.banner.as_deref())?;
|
||||
let banner = proxy_image_link_api(banner, &context).await?;
|
||||
|
||||
let site_form = SiteUpdateForm {
|
||||
name: Some(data.name.clone()),
|
||||
sidebar: diesel_option_overwrite(sidebar),
|
||||
description: diesel_option_overwrite(data.description.clone()),
|
||||
icon,
|
||||
banner,
|
||||
sidebar: diesel_string_update(sidebar.as_deref()),
|
||||
description: diesel_string_update(data.description.as_deref()),
|
||||
icon: Some(icon),
|
||||
banner: Some(banner),
|
||||
actor_id: Some(actor_id),
|
||||
last_refreshed_at: Some(naive_now()),
|
||||
inbox_url,
|
||||
private_key: Some(Some(keypair.private_key)),
|
||||
public_key: Some(keypair.public_key),
|
||||
content_warning: diesel_option_overwrite(data.content_warning.clone()),
|
||||
content_warning: diesel_string_update(data.content_warning.as_deref()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -91,16 +95,16 @@ pub async fn create_site(
|
|||
enable_nsfw: data.enable_nsfw,
|
||||
community_creation_admin_only: data.community_creation_admin_only,
|
||||
require_email_verification: data.require_email_verification,
|
||||
application_question: diesel_option_overwrite(data.application_question.clone()),
|
||||
application_question: diesel_string_update(data.application_question.as_deref()),
|
||||
private_instance: data.private_instance,
|
||||
default_theme: data.default_theme.clone(),
|
||||
default_post_listing_type: data.default_post_listing_type,
|
||||
default_sort_type: data.default_sort_type,
|
||||
legal_information: diesel_option_overwrite(data.legal_information.clone()),
|
||||
legal_information: diesel_string_update(data.legal_information.as_deref()),
|
||||
application_email_admins: data.application_email_admins,
|
||||
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
||||
updated: Some(Some(naive_now())),
|
||||
slur_filter_regex: diesel_option_overwrite(data.slur_filter_regex.clone()),
|
||||
slur_filter_regex: diesel_string_update(data.slur_filter_regex.as_deref()),
|
||||
actor_name_max_length: data.actor_name_max_length,
|
||||
federation_enabled: data.federation_enabled,
|
||||
captcha_enabled: data.captcha_enabled,
|
||||
|
@ -179,7 +183,9 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
|
|||
)?;
|
||||
|
||||
// Ensure that the sidebar has fewer than the max num characters...
|
||||
is_valid_body_field(&create_site.sidebar, false)?;
|
||||
if let Some(body) = &create_site.sidebar {
|
||||
is_valid_body_field(body, false)?;
|
||||
}
|
||||
|
||||
application_question_check(
|
||||
&local_site.application_question,
|
||||
|
|
|
@ -5,33 +5,29 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::source::{
|
||||
actor_language::{LocalUserLanguage, SiteLanguage},
|
||||
community_block::CommunityBlock,
|
||||
instance_block::InstanceBlock,
|
||||
language::Language,
|
||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||
person_block::PersonBlock,
|
||||
tagline::Tagline,
|
||||
};
|
||||
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommunityBlockView,
|
||||
CommunityFollowerView,
|
||||
CommunityModeratorView,
|
||||
InstanceBlockView,
|
||||
PersonBlockView,
|
||||
PersonView,
|
||||
};
|
||||
use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
CACHE_DURATION_API,
|
||||
VERSION,
|
||||
};
|
||||
use moka::future::Cache;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_site(
|
||||
local_user_view: Option<LocalUserView>,
|
||||
context: Data<LemmyContext>,
|
||||
) -> LemmyResult<Json<GetSiteResponse>> {
|
||||
static CACHE: Lazy<Cache<(), GetSiteResponse>> = Lazy::new(|| {
|
||||
static CACHE: LazyLock<Cache<(), GetSiteResponse>> = LazyLock::new(|| {
|
||||
Cache::builder()
|
||||
.max_capacity(1)
|
||||
.time_to_live(CACHE_DURATION_API)
|
||||
|
@ -81,10 +77,10 @@ pub async fn get_site(
|
|||
discussion_languages,
|
||||
) = lemmy_db_schema::try_join_with_pool!(pool => (
|
||||
|pool| CommunityFollowerView::for_person(pool, person_id),
|
||||
|pool| CommunityBlockView::for_person(pool, person_id),
|
||||
|pool| InstanceBlockView::for_person(pool, person_id),
|
||||
|pool| PersonBlockView::for_person(pool, person_id),
|
||||
|pool| CommunityModeratorView::for_person(pool, person_id, true),
|
||||
|pool| CommunityBlock::for_person(pool, person_id),
|
||||
|pool| InstanceBlock::for_person(pool, person_id),
|
||||
|pool| PersonBlock::for_person(pool, person_id),
|
||||
|pool| CommunityModeratorView::for_person(pool, person_id, Some(&local_user_view.local_user)),
|
||||
|pool| LocalUserLanguage::read(pool, local_user_id)
|
||||
))
|
||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||
|
|
|
@ -27,7 +27,7 @@ use lemmy_db_schema::{
|
|||
tagline::Tagline,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{diesel_option_overwrite, naive_now},
|
||||
utils::{diesel_string_update, diesel_url_update, naive_now},
|
||||
RegistrationMode,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||
|
@ -67,22 +67,29 @@ pub async fn update_site(
|
|||
SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?;
|
||||
}
|
||||
|
||||
replace_image(&data.icon, &site.icon, &context).await?;
|
||||
replace_image(&data.banner, &site.banner, &context).await?;
|
||||
|
||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
|
||||
let icon = proxy_image_link_opt_api(&data.icon, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
||||
let sidebar = diesel_string_update(
|
||||
process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
|
||||
.await?
|
||||
.as_deref(),
|
||||
);
|
||||
|
||||
let icon = diesel_url_update(data.icon.as_deref())?;
|
||||
replace_image(&icon, &site.icon, &context).await?;
|
||||
let icon = proxy_image_link_opt_api(icon, &context).await?;
|
||||
|
||||
let banner = diesel_url_update(data.banner.as_deref())?;
|
||||
replace_image(&banner, &site.banner, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(banner, &context).await?;
|
||||
|
||||
let site_form = SiteUpdateForm {
|
||||
name: data.name.clone(),
|
||||
sidebar: diesel_option_overwrite(sidebar),
|
||||
description: diesel_option_overwrite(data.description.clone()),
|
||||
sidebar,
|
||||
description: diesel_string_update(data.description.as_deref()),
|
||||
icon,
|
||||
banner,
|
||||
content_warning: diesel_option_overwrite(data.content_warning.clone()),
|
||||
content_warning: diesel_string_update(data.content_warning.as_deref()),
|
||||
updated: Some(Some(naive_now())),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -99,16 +106,16 @@ pub async fn update_site(
|
|||
enable_nsfw: data.enable_nsfw,
|
||||
community_creation_admin_only: data.community_creation_admin_only,
|
||||
require_email_verification: data.require_email_verification,
|
||||
application_question: diesel_option_overwrite(data.application_question.clone()),
|
||||
application_question: diesel_string_update(data.application_question.as_deref()),
|
||||
private_instance: data.private_instance,
|
||||
default_theme: data.default_theme.clone(),
|
||||
default_post_listing_type: data.default_post_listing_type,
|
||||
default_sort_type: data.default_sort_type,
|
||||
legal_information: diesel_option_overwrite(data.legal_information.clone()),
|
||||
legal_information: diesel_string_update(data.legal_information.as_deref()),
|
||||
application_email_admins: data.application_email_admins,
|
||||
hide_modlog_mod_names: data.hide_modlog_mod_names,
|
||||
updated: Some(Some(naive_now())),
|
||||
slur_filter_regex: diesel_option_overwrite(data.slur_filter_regex.clone()),
|
||||
slur_filter_regex: diesel_string_update(data.slur_filter_regex.as_deref()),
|
||||
actor_name_max_length: data.actor_name_max_length,
|
||||
federation_enabled: data.federation_enabled,
|
||||
captcha_enabled: data.captcha_enabled,
|
||||
|
@ -229,7 +236,9 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
|
|||
)?;
|
||||
|
||||
// Ensure that the sidebar has fewer than the max num characters...
|
||||
is_valid_body_field(&edit_site.sidebar, false)?;
|
||||
if let Some(body) = &edit_site.sidebar {
|
||||
is_valid_body_field(body, false)?;
|
||||
}
|
||||
|
||||
application_question_check(
|
||||
&local_site.application_question,
|
||||
|
|
|
@ -112,15 +112,17 @@ pub async fn register(
|
|||
// We have to create both a person, and local_user
|
||||
|
||||
// Register the new person
|
||||
let person_form = PersonInsertForm::builder()
|
||||
.name(data.username.clone())
|
||||
.actor_id(Some(actor_id.clone()))
|
||||
.private_key(Some(actor_keypair.private_key))
|
||||
.public_key(actor_keypair.public_key)
|
||||
.inbox_url(Some(generate_inbox_url(&actor_id)?))
|
||||
.shared_inbox_url(Some(generate_shared_inbox_url(context.settings())?))
|
||||
.instance_id(site_view.site.instance_id)
|
||||
.build();
|
||||
let person_form = PersonInsertForm {
|
||||
actor_id: Some(actor_id.clone()),
|
||||
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
||||
shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?),
|
||||
private_key: Some(actor_keypair.private_key),
|
||||
..PersonInsertForm::new(
|
||||
data.username.clone(),
|
||||
actor_keypair.public_key,
|
||||
site_view.site.instance_id,
|
||||
)
|
||||
};
|
||||
|
||||
// insert the person
|
||||
let inserted_person = Person::create(&mut context.pool(), &person_form)
|
||||
|
@ -148,18 +150,18 @@ pub async fn register(
|
|||
.unwrap_or(site_view.site.content_warning.is_some());
|
||||
|
||||
// Create the local user
|
||||
let local_user_form = LocalUserInsertForm::builder()
|
||||
.person_id(inserted_person.id)
|
||||
.email(data.email.as_deref().map(str::to_lowercase))
|
||||
.password_encrypted(data.password.to_string())
|
||||
.show_nsfw(Some(show_nsfw))
|
||||
.accepted_application(accepted_application)
|
||||
.default_listing_type(Some(local_site.default_post_listing_type))
|
||||
.post_listing_mode(Some(local_site.default_post_listing_mode))
|
||||
.interface_language(language_tags.first().cloned())
|
||||
let local_user_form = LocalUserInsertForm {
|
||||
email: data.email.as_deref().map(str::to_lowercase),
|
||||
password_encrypted: data.password.to_string(),
|
||||
show_nsfw: Some(show_nsfw),
|
||||
accepted_application,
|
||||
default_listing_type: Some(local_site.default_post_listing_type),
|
||||
post_listing_mode: Some(local_site.default_post_listing_mode),
|
||||
interface_language: language_tags.first().cloned(),
|
||||
// If its the initial site setup, they are an admin
|
||||
.admin(Some(!local_site.site_setup))
|
||||
.build();
|
||||
admin: Some(!local_site.site_setup),
|
||||
..LocalUserInsertForm::new(inserted_person.id, data.password.to_string())
|
||||
};
|
||||
|
||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||
// use hashset to avoid duplicates
|
||||
|
|
|
@ -31,7 +31,7 @@ serde = { workspace = true }
|
|||
actix-web = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
strum_macros = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
url = { workspace = true }
|
||||
http = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
@ -40,7 +40,6 @@ uuid = { workspace = true }
|
|||
async-trait = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
moka.workspace = true
|
||||
serde_with.workspace = true
|
||||
html2md = "0.2.14"
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
"type": "Block",
|
||||
"removeData": true,
|
||||
"summary": "spam post",
|
||||
"expires": "2021-11-01T12:23:50.151874Z",
|
||||
"endTime": "2021-11-01T12:23:50.151874Z",
|
||||
"id": "http://enterprise.lemmy.ml/activities/block/5d42fffb-0903-4625-86d4-0b39bb344fc2"
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"type": "Block",
|
||||
"removeData": true,
|
||||
"summary": "spam post",
|
||||
"expires": "2021-11-01T12:23:50.151874Z",
|
||||
"endTime": "2021-11-01T12:23:50.151874Z",
|
||||
"id": "http://enterprise.lemmy.ml/activities/block/726f43ab-bd0e-4ab3-89c8-627e976f553c"
|
||||
},
|
||||
"cc": ["http://enterprise.lemmy.ml/c/main"],
|
||||
|
|
|
@ -74,7 +74,6 @@ impl BlockUser {
|
|||
&context.settings().get_protocol_and_hostname(),
|
||||
)?,
|
||||
audience,
|
||||
expires,
|
||||
end_time: expires,
|
||||
})
|
||||
}
|
||||
|
@ -157,7 +156,7 @@ impl ActivityHandler for BlockUser {
|
|||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
let expires = self.expires.or(self.end_time).map(Into::into);
|
||||
let expires = self.end_time.map(Into::into);
|
||||
let mod_person = self.actor.dereference(context).await?;
|
||||
let blocked_person = self.object.dereference(context).await?;
|
||||
let target = self.target.dereference(context).await?;
|
||||
|
|
|
@ -38,7 +38,6 @@ pub enum SiteOrCommunity {
|
|||
Site(ApubSite),
|
||||
Community(ApubCommunity),
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum InstanceOrGroup {
|
||||
|
@ -74,12 +73,18 @@ impl Object for SiteOrCommunity {
|
|||
})
|
||||
}
|
||||
|
||||
async fn delete(self, _data: &Data<Self::DataType>) -> LemmyResult<()> {
|
||||
unimplemented!()
|
||||
async fn delete(self, data: &Data<Self::DataType>) -> LemmyResult<()> {
|
||||
match self {
|
||||
SiteOrCommunity::Site(i) => i.delete(data).await,
|
||||
SiteOrCommunity::Community(c) => c.delete(data).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn into_json(self, _data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
unimplemented!()
|
||||
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
Ok(match self {
|
||||
SiteOrCommunity::Site(i) => InstanceOrGroup::Instance(i.into_json(data).await?),
|
||||
SiteOrCommunity::Community(c) => InstanceOrGroup::Group(c.into_json(data).await?),
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
|
|
@ -98,7 +98,7 @@ impl ActivityHandler for UndoBlockUser {
|
|||
#[tracing::instrument(skip_all)]
|
||||
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
|
||||
insert_received_activity(&self.id, context).await?;
|
||||
let expires = self.object.expires.or(self.object.end_time).map(Into::into);
|
||||
let expires = self.object.end_time.map(Into::into);
|
||||
let mod_person = self.actor.dereference(context).await?;
|
||||
let blocked_person = self.object.object.dereference(context).await?;
|
||||
match self.object.target.dereference(context).await? {
|
||||
|
|
|
@ -178,7 +178,7 @@ impl ActivityHandler for CreateOrUpdateNote {
|
|||
// TODO: for compatibility with other projects, it would be much better to read this from cc or
|
||||
// tags
|
||||
let mentions = scrape_text_for_mentions(&comment.content);
|
||||
send_local_notifs(mentions, comment.id, &actor, do_send_email, context).await?;
|
||||
send_local_notifs(mentions, comment.id, &actor, do_send_email, context, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,8 +175,9 @@ pub(in crate::activities) async fn receive_remove_action(
|
|||
)
|
||||
.await?;
|
||||
}
|
||||
DeletableObjects::PrivateMessage(_) => unimplemented!(),
|
||||
DeletableObjects::Person { .. } => unimplemented!(),
|
||||
// TODO these need to be implemented yet, for now, return errors
|
||||
DeletableObjects::PrivateMessage(_) => Err(LemmyErrorType::CouldntFindPrivateMessage)?,
|
||||
DeletableObjects::Person(_) => Err(LemmyErrorType::CouldntFindPerson)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -155,8 +155,9 @@ impl UndoDelete {
|
|||
)
|
||||
.await?;
|
||||
}
|
||||
DeletableObjects::PrivateMessage(_) => unimplemented!(),
|
||||
DeletableObjects::Person { .. } => unimplemented!(),
|
||||
// TODO these need to be implemented yet, for now, return errors
|
||||
DeletableObjects::PrivateMessage(_) => Err(LemmyErrorType::CouldntFindPrivateMessage)?,
|
||||
DeletableObjects::Person(_) => Err(LemmyErrorType::CouldntFindPerson)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::{
|
|||
};
|
||||
use activitypub_federation::{config::Data, traits::ActivityHandler};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
|
@ -117,7 +117,7 @@ impl InCommunity for AnnouncableActivities {
|
|||
CollectionRemove(a) => a.community(context).await,
|
||||
LockPost(a) => a.community(context).await,
|
||||
UndoLockPost(a) => a.community(context).await,
|
||||
Page(_) => unimplemented!(),
|
||||
Page(_) => Err(LemmyErrorType::CouldntFindPost.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,11 +37,11 @@ pub async fn list_comments(
|
|||
};
|
||||
let sort = data.sort;
|
||||
let max_depth = data.max_depth;
|
||||
let saved_only = data.saved_only.unwrap_or_default();
|
||||
let saved_only = data.saved_only;
|
||||
|
||||
let liked_only = data.liked_only.unwrap_or_default();
|
||||
let disliked_only = data.disliked_only.unwrap_or_default();
|
||||
if liked_only && disliked_only {
|
||||
let liked_only = data.liked_only;
|
||||
let disliked_only = data.disliked_only;
|
||||
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
|
||||
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,8 @@ pub async fn list_comments(
|
|||
|
||||
let parent_path_cloned = parent_path.clone();
|
||||
let post_id = data.post_id;
|
||||
let local_user = local_user_view.as_ref().map(|l| &l.local_user);
|
||||
|
||||
let comments = CommentQuery {
|
||||
listing_type,
|
||||
sort,
|
||||
|
@ -80,7 +82,7 @@ pub async fn list_comments(
|
|||
community_id,
|
||||
parent_path: parent_path_cloned,
|
||||
post_id,
|
||||
local_user: local_user_view.as_ref(),
|
||||
local_user,
|
||||
page,
|
||||
limit,
|
||||
..Default::default()
|
||||
|
|
|
@ -40,26 +40,28 @@ pub async fn list_posts(
|
|||
} else {
|
||||
data.community_id
|
||||
};
|
||||
let saved_only = data.saved_only.unwrap_or_default();
|
||||
let show_hidden = data.show_hidden.unwrap_or_default();
|
||||
let saved_only = data.saved_only;
|
||||
let show_hidden = data.show_hidden;
|
||||
let show_read = data.show_read;
|
||||
let show_nsfw = data.show_nsfw;
|
||||
|
||||
let liked_only = data.liked_only.unwrap_or_default();
|
||||
let disliked_only = data.disliked_only.unwrap_or_default();
|
||||
if liked_only && disliked_only {
|
||||
let liked_only = data.liked_only;
|
||||
let disliked_only = data.disliked_only;
|
||||
if liked_only.unwrap_or_default() && disliked_only.unwrap_or_default() {
|
||||
return Err(LemmyError::from(LemmyErrorType::ContradictingFilters));
|
||||
}
|
||||
|
||||
let local_user_ref = local_user_view.as_ref().map(|u| &u.local_user);
|
||||
let local_user = local_user_view.as_ref().map(|u| &u.local_user);
|
||||
let listing_type = Some(listing_type_with_default(
|
||||
data.type_,
|
||||
local_user_ref,
|
||||
local_user,
|
||||
&local_site.local_site,
|
||||
community_id,
|
||||
));
|
||||
|
||||
let sort = Some(sort_type_with_default(
|
||||
data.sort,
|
||||
local_user_ref,
|
||||
local_user,
|
||||
&local_site.local_site,
|
||||
));
|
||||
|
||||
|
@ -71,7 +73,7 @@ pub async fn list_posts(
|
|||
};
|
||||
|
||||
let posts = PostQuery {
|
||||
local_user: local_user_view.as_ref(),
|
||||
local_user,
|
||||
listing_type,
|
||||
sort,
|
||||
community_id,
|
||||
|
@ -82,6 +84,8 @@ pub async fn list_posts(
|
|||
page_after,
|
||||
limit,
|
||||
show_hidden,
|
||||
show_read,
|
||||
show_nsfw,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&local_site.site, &mut context.pool())
|
||||
|
|
|
@ -29,7 +29,7 @@ pub async fn get_community(
|
|||
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
|
||||
let person_id = local_user_view.as_ref().map(|u| u.person.id);
|
||||
let local_user = local_user_view.as_ref().map(|u| &u.local_user);
|
||||
|
||||
let community_id = match data.id {
|
||||
Some(id) => id,
|
||||
|
@ -53,7 +53,7 @@ pub async fn get_community(
|
|||
let community_view = CommunityView::read(
|
||||
&mut context.pool(),
|
||||
community_id,
|
||||
person_id,
|
||||
local_user,
|
||||
is_mod_or_admin,
|
||||
)
|
||||
.await?
|
||||
|
|
|
@ -55,20 +55,22 @@ pub async fn read_person(
|
|||
let sort = data.sort;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let saved_only = data.saved_only.unwrap_or_default();
|
||||
let saved_only = data.saved_only;
|
||||
let community_id = data.community_id;
|
||||
// If its saved only, you don't care what creator it was
|
||||
// Or, if its not saved, then you only want it for that specific creator
|
||||
let creator_id = if !saved_only {
|
||||
let creator_id = if !saved_only.unwrap_or_default() {
|
||||
Some(person_details_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let local_user = local_user_view.as_ref().map(|l| &l.local_user);
|
||||
|
||||
let posts = PostQuery {
|
||||
sort,
|
||||
saved_only,
|
||||
local_user: local_user_view.as_ref(),
|
||||
local_user,
|
||||
community_id,
|
||||
page,
|
||||
limit,
|
||||
|
@ -79,7 +81,7 @@ pub async fn read_person(
|
|||
.await?;
|
||||
|
||||
let comments = CommentQuery {
|
||||
local_user: local_user_view.as_ref(),
|
||||
local_user,
|
||||
sort: sort.map(post_to_comment_sort_type),
|
||||
saved_only,
|
||||
community_id,
|
||||
|
@ -94,7 +96,7 @@ pub async fn read_person(
|
|||
let moderates = CommunityModeratorView::for_person(
|
||||
&mut context.pool(),
|
||||
person_details_id,
|
||||
local_user_view.is_some(),
|
||||
local_user_view.map(|l| l.local_user).as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use lemmy_api_common::{
|
|||
site::{ResolveObject, ResolveObjectResponse},
|
||||
utils::check_private_instance,
|
||||
};
|
||||
use lemmy_db_schema::{newtypes::PersonId, source::local_site::LocalSite, utils::DbPool};
|
||||
use lemmy_db_schema::{source::local_site::LocalSite, utils::DbPool};
|
||||
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
|
||||
use lemmy_db_views_actor::structs::{CommunityView, PersonView};
|
||||
use lemmy_utils::error::{LemmyErrorExt2, LemmyErrorType, LemmyResult};
|
||||
|
@ -23,12 +23,11 @@ pub async fn resolve_object(
|
|||
) -> LemmyResult<Json<ResolveObjectResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
check_private_instance(&local_user_view, &local_site)?;
|
||||
let person_id = local_user_view.map(|v| v.person.id);
|
||||
// If we get a valid personId back we can safely assume that the user is authenticated,
|
||||
// if there's no personId then the JWT was missing or invalid.
|
||||
let is_authenticated = person_id.is_some();
|
||||
let is_authenticated = local_user_view.is_some();
|
||||
|
||||
let res = if is_authenticated {
|
||||
let res = if is_authenticated || cfg!(debug_assertions) {
|
||||
// user is fully authenticated; allow remote lookups as well.
|
||||
search_query_to_object_id(data.q.clone(), &context).await
|
||||
} else {
|
||||
|
@ -37,24 +36,26 @@ pub async fn resolve_object(
|
|||
}
|
||||
.with_lemmy_type(LemmyErrorType::CouldntFindObject)?;
|
||||
|
||||
convert_response(res, person_id, &mut context.pool())
|
||||
convert_response(res, local_user_view, &mut context.pool())
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntFindObject)
|
||||
}
|
||||
|
||||
async fn convert_response(
|
||||
object: SearchableObjects,
|
||||
user_id: Option<PersonId>,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<Json<ResolveObjectResponse>> {
|
||||
use SearchableObjects::*;
|
||||
let removed_or_deleted;
|
||||
let mut res = ResolveObjectResponse::default();
|
||||
let local_user = local_user_view.map(|l| l.local_user);
|
||||
|
||||
match object {
|
||||
Post(p) => {
|
||||
removed_or_deleted = p.deleted || p.removed;
|
||||
res.post = Some(
|
||||
PostView::read(pool, p.id, user_id, false)
|
||||
PostView::read(pool, p.id, local_user.as_ref(), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindPost)?,
|
||||
)
|
||||
|
@ -62,7 +63,7 @@ async fn convert_response(
|
|||
Comment(c) => {
|
||||
removed_or_deleted = c.deleted || c.removed;
|
||||
res.comment = Some(
|
||||
CommentView::read(pool, c.id, user_id)
|
||||
CommentView::read(pool, c.id, local_user.as_ref())
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindComment)?,
|
||||
)
|
||||
|
@ -79,7 +80,7 @@ async fn convert_response(
|
|||
UserOrCommunity::Community(c) => {
|
||||
removed_or_deleted = c.deleted || c.removed;
|
||||
res.community = Some(
|
||||
CommunityView::read(pool, c.id, user_id, false)
|
||||
CommunityView::read(pool, c.id, local_user.as_ref(), false)
|
||||
.await?
|
||||
.ok_or(LemmyErrorType::CouldntFindCommunity)?,
|
||||
)
|
||||
|
|
|
@ -55,7 +55,7 @@ pub async fn search(
|
|||
data.community_id
|
||||
};
|
||||
let creator_id = data.creator_id;
|
||||
let local_user = local_user_view.as_ref().map(|luv| &luv.local_user);
|
||||
let local_user = local_user_view.as_ref().map(|l| &l.local_user);
|
||||
|
||||
match search_type {
|
||||
SearchType::Posts => {
|
||||
|
@ -64,7 +64,7 @@ pub async fn search(
|
|||
listing_type: (listing_type),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
local_user,
|
||||
search_term: (Some(q)),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
|
@ -80,7 +80,7 @@ pub async fn search(
|
|||
search_term: (Some(q)),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
local_user,
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
|
@ -125,7 +125,7 @@ pub async fn search(
|
|||
listing_type: (listing_type),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
local_user,
|
||||
search_term: (Some(q)),
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
|
@ -142,7 +142,7 @@ pub async fn search(
|
|||
search_term: (Some(q)),
|
||||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
local_user: (local_user_view.as_ref()),
|
||||
local_user,
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
|
@ -192,6 +192,7 @@ pub async fn search(
|
|||
community_id: (community_id),
|
||||
creator_id: (creator_id),
|
||||
url_search: (Some(q)),
|
||||
local_user,
|
||||
page: (page),
|
||||
limit: (limit),
|
||||
..Default::default()
|
||||
|
|
|
@ -122,7 +122,6 @@ pub async fn import_settings(
|
|||
.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),
|
||||
|
@ -338,19 +337,14 @@ mod tests {
|
|||
context: &Data<LemmyContext>,
|
||||
) -> LemmyResult<LocalUserView> {
|
||||
let instance = Instance::read_or_create(&mut context.pool(), "example.com".to_string()).await?;
|
||||
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_form = PersonInsertForm {
|
||||
display_name: Some(name.clone()),
|
||||
bio,
|
||||
..PersonInsertForm::test_form(instance.id, &name)
|
||||
};
|
||||
let person = Person::create(&mut context.pool(), &person_form).await?;
|
||||
|
||||
let user_form = LocalUserInsertForm::builder()
|
||||
.person_id(person.id)
|
||||
.password_encrypted("pass".to_string())
|
||||
.build();
|
||||
let user_form = LocalUserInsertForm::test_form(person.id);
|
||||
let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?;
|
||||
|
||||
Ok(
|
||||
|
|
|
@ -129,11 +129,7 @@ mod tests {
|
|||
let inserted_instance =
|
||||
Instance::read_or_create(&mut context.pool(), "my_domain.tld".to_string()).await?;
|
||||
|
||||
let old_mod = PersonInsertForm::builder()
|
||||
.name("holly".into())
|
||||
.public_key("pubkey".to_string())
|
||||
.instance_id(inserted_instance.id)
|
||||
.build();
|
||||
let old_mod = PersonInsertForm::test_form(inserted_instance.id, "holly");
|
||||
|
||||
let old_mod = Person::create(&mut context.pool(), &old_mod).await?;
|
||||
let community_moderator_form = CommunityModeratorForm {
|
||||
|
|
|
@ -61,8 +61,11 @@ impl Object for PostOrComment {
|
|||
}
|
||||
}
|
||||
|
||||
async fn into_json(self, _data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
unimplemented!()
|
||||
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
Ok(match self {
|
||||
PostOrComment::Post(p) => PageOrNote::Page(Box::new(p.into_json(data).await?)),
|
||||
PostOrComment::Comment(c) => PageOrNote::Note(c.into_json(data).await?),
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
|
|
@ -118,8 +118,17 @@ impl Object for SearchableObjects {
|
|||
}
|
||||
}
|
||||
|
||||
async fn into_json(self, _data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
unimplemented!()
|
||||
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
Ok(match self {
|
||||
SearchableObjects::Post(p) => SearchableKinds::Page(Box::new(p.into_json(data).await?)),
|
||||
SearchableObjects::Comment(c) => SearchableKinds::Note(c.into_json(data).await?),
|
||||
SearchableObjects::PersonOrCommunity(pc) => {
|
||||
SearchableKinds::PersonOrGroup(Box::new(match *pc {
|
||||
UserOrCommunity::User(p) => PersonOrGroup::Person(p.into_json(data).await?),
|
||||
UserOrCommunity::Community(c) => PersonOrGroup::Group(c.into_json(data).await?),
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
fetcher::user_or_community::{PersonOrGroup, UserOrCommunity},
|
||||
objects::instance::ApubSite,
|
||||
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
|
||||
protocol::objects::instance::Instance,
|
||||
};
|
||||
use activitypub_federation::{
|
||||
|
@ -41,11 +41,14 @@ impl Object for SiteOrCommunityOrUser {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn read_from_id(
|
||||
_object_id: Url,
|
||||
_data: &Data<Self::DataType>,
|
||||
) -> LemmyResult<Option<Self>> {
|
||||
unimplemented!();
|
||||
async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> LemmyResult<Option<Self>> {
|
||||
let site = ApubSite::read_from_id(object_id.clone(), data).await?;
|
||||
Ok(match site {
|
||||
Some(o) => Some(SiteOrCommunityOrUser::Site(o)),
|
||||
None => UserOrCommunity::read_from_id(object_id, data)
|
||||
.await?
|
||||
.map(SiteOrCommunityOrUser::UserOrCommunity),
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -56,8 +59,13 @@ impl Object for SiteOrCommunityOrUser {
|
|||
}
|
||||
}
|
||||
|
||||
async fn into_json(self, _data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
unimplemented!()
|
||||
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
Ok(match self {
|
||||
SiteOrCommunityOrUser::Site(p) => SiteOrPersonOrGroup::Instance(p.into_json(data).await?),
|
||||
SiteOrCommunityOrUser::UserOrCommunity(p) => {
|
||||
SiteOrPersonOrGroup::PersonOrGroup(p.into_json(data).await?)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -75,8 +83,18 @@ impl Object for SiteOrCommunityOrUser {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn from_json(_apub: Self::Kind, _data: &Data<Self::DataType>) -> LemmyResult<Self> {
|
||||
unimplemented!();
|
||||
async fn from_json(apub: Self::Kind, data: &Data<Self::DataType>) -> LemmyResult<Self> {
|
||||
Ok(match apub {
|
||||
SiteOrPersonOrGroup::Instance(a) => {
|
||||
SiteOrCommunityOrUser::Site(ApubSite::from_json(a, data).await?)
|
||||
}
|
||||
SiteOrPersonOrGroup::PersonOrGroup(a) => SiteOrCommunityOrUser::UserOrCommunity(match a {
|
||||
PersonOrGroup::Person(p) => UserOrCommunity::User(ApubPerson::from_json(p, data).await?),
|
||||
PersonOrGroup::Group(g) => {
|
||||
UserOrCommunity::Community(ApubCommunity::from_json(g, data).await?)
|
||||
}
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +121,9 @@ impl Actor for SiteOrCommunityOrUser {
|
|||
}
|
||||
|
||||
fn inbox(&self) -> Url {
|
||||
unimplemented!()
|
||||
match self {
|
||||
SiteOrCommunityOrUser::Site(u) => u.inbox(),
|
||||
SiteOrCommunityOrUser::UserOrCommunity(c) => c.inbox(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,11 @@ impl Object for UserOrCommunity {
|
|||
}
|
||||
}
|
||||
|
||||
async fn into_json(self, _data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
unimplemented!()
|
||||
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
|
||||
Ok(match self {
|
||||
UserOrCommunity::User(p) => PersonOrGroup::Person(p.into_json(data).await?),
|
||||
UserOrCommunity::Community(p) => PersonOrGroup::Group(p.into_json(data).await?),
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
|
@ -115,7 +118,10 @@ impl Actor for UserOrCommunity {
|
|||
}
|
||||
|
||||
fn inbox(&self) -> Url {
|
||||
unimplemented!()
|
||||
match self {
|
||||
UserOrCommunity::User(p) => p.inbox(),
|
||||
UserOrCommunity::Community(p) => p.inbox(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue