mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-09-27 14:00:17 +00:00
Compare commits
269 commits
0.19.4-bet
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
e82f72d3c8 | ||
|
50ce7961d1 | ||
|
33cbd95b7e | ||
|
f6a24e133a | ||
|
61a02482ff | ||
|
0fab5bed24 | ||
|
a65be776e3 | ||
|
9eee61dd06 | ||
|
bab5c93062 | ||
|
d476d32200 | ||
|
62e1790ae7 | ||
|
a8843335a6 | ||
|
458bb60144 | ||
|
25df9d255b | ||
|
8cdfc148d7 | ||
|
ad75192bae | ||
|
5a722146b5 | ||
|
dbb8f9553a | ||
|
89745bb37d | ||
|
43f20881cb | ||
|
026e23cf32 | ||
|
6b6457cc54 | ||
|
2b3fd70afd | ||
|
b26aaac523 | ||
|
6454a4d43d | ||
|
5febf2b8fb | ||
|
ff939e04fd | ||
|
987e3f8026 | ||
|
fa192f16bd | ||
|
dea6ee462c | ||
|
a6220537b5 | ||
|
fbb3960c36 | ||
|
fc13009611 | ||
|
ef5e2d96cd | ||
|
d1866cbd04 | ||
|
84794714da | ||
|
c90ee3094d | ||
|
ef49a0eb8d | ||
|
c8ad0f2d09 | ||
|
234f5c2060 | ||
|
f98b15511a | ||
|
5683d5ed7a | ||
|
b1afd98d29 | ||
|
f617227f0d | ||
|
fa94a5869e | ||
|
ae3df4db72 | ||
|
d479bb96c6 | ||
|
101476df87 | ||
|
bd1b7aa6ef | ||
|
2913cdf556 | ||
|
6b9e5ecb48 | ||
|
76a2c6e79b | ||
|
254ef6dab3 | ||
|
63a686d390 | ||
|
ea18d462b0 | ||
|
128e78f7c2 | ||
|
606545ccaf | ||
|
88fbcea246 | ||
|
eff87b2764 | ||
|
33fd31754a | ||
|
f8c7375731 | ||
|
c6cbc2f61d | ||
|
4729255514 | ||
|
b25f9c3e66 | ||
|
c0f3dff5cf | ||
|
79fddc965f | ||
|
2cab7935db | ||
|
2318473456 | ||
|
75d8d704e7 | ||
|
4e7e92da94 | ||
|
fa836bb35a | ||
|
4e48054b17 | ||
|
6c11c013b0 | ||
|
0bd0c0f11b | ||
|
c6db2ad9b7 | ||
|
60a7829638 | ||
|
cbb37fa2f1 | ||
|
0a6186d7aa | ||
|
5421e9a3c7 | ||
|
1ef375e5c3 | ||
|
069a9509d5 | ||
|
2ccb46b66d | ||
|
fb911679d0 | ||
|
7321a63005 | ||
|
9738d87f38 | ||
|
32b73193df | ||
|
db390a2f3a | ||
|
572a42d880 | ||
|
3d80ac2ebb | ||
|
a08642f813 | ||
|
073ff44676 | ||
|
847c01f348 | ||
|
8abbd56400 | ||
|
ba044c7d98 | ||
|
6ff128341c | ||
|
53a226b944 | ||
|
f229f09f92 | ||
|
32cee9cbca | ||
|
78702b59fd | ||
|
117a8b42c8 | ||
|
fd58b4f809 | ||
|
a7c39226e2 | ||
|
d90e8f8550 | ||
|
2c57f42022 | ||
|
9120207314 | ||
|
d09854a722 | ||
|
c8d155102a | ||
|
36e6f7ec78 | ||
|
6d8d23130d | ||
|
9cf6da1b9e | ||
|
59f274e531 | ||
|
fa143f72eb | ||
|
63a824a2ed | ||
|
b9dc7612a8 | ||
|
393f65db8d | ||
|
bfefdfd15d | ||
|
41d1b054fe | ||
|
42a6d8ab0f | ||
|
f080400826 | ||
|
c55636b0d0 | ||
|
32b7ee76e3 | ||
|
5cc798a146 | ||
|
4974dbb1dd | ||
|
d7a453dd68 | ||
|
393b221be0 | ||
|
966d949f73 | ||
|
b245bf48c0 | ||
|
b569c7df17 | ||
|
0f6bd94407 | ||
|
5cf1593c9f | ||
|
04400ceac3 | ||
|
dede17bf24 | ||
|
b0e2a14d04 | ||
|
027017b0a8 | ||
|
b27b38b9a9 | ||
|
a7771ff385 | ||
|
27e7aa1e04 | ||
|
6497ec519e | ||
|
65620913fc | ||
|
a3c8761bed | ||
|
99160228ae | ||
|
fc6f46c1ac | ||
|
046375171e | ||
|
b2a480f55c | ||
|
9236cf7d21 | ||
|
b559e0206b | ||
|
f5f2b5ffc6 | ||
|
1e11faf741 | ||
|
5d31f0d516 | ||
|
844b84a01a | ||
|
b0447ad94d | ||
|
3d25322089 | ||
|
16a82862b8 | ||
|
79e6dbf0de | ||
|
fda5ce4482 | ||
|
e8cfb5665f | ||
|
bb94fb1c79 | ||
|
92214a9364 | ||
|
78ae874b89 | ||
|
a947474c64 | ||
|
8bf17946bd | ||
|
9ceb5b6386 | ||
|
aefb41b551 | ||
|
4195a9b5a1 | ||
|
69b4c6647b | ||
|
f7fe0d46fc | ||
|
609a6411a7 | ||
|
44666a34a2 | ||
|
6db878f761 | ||
|
6031709fcf | ||
|
4d9e38d875 | ||
|
6a6c915014 | ||
|
96b7afc0b1 | ||
|
d2083f79d9 | ||
|
e8a7bb07a3 | ||
|
91e57ff954 | ||
|
7d80a3c7d6 | ||
|
abcfa266af | ||
|
51970ffc81 | ||
|
fd6a1283a5 | ||
|
af034f3b5e | ||
|
0d5db29bc9 | ||
|
ec77c00ef8 | ||
|
69bdcb3069 | ||
|
6a6108ac55 | ||
|
b2c1a14234 | ||
|
d8dc38eb06 | ||
|
c96017c009 | ||
|
9aa565b559 | ||
|
7d7cd8ded4 | ||
|
943c31cc72 | ||
|
973f39601c | ||
|
a39c19c9db | ||
|
55f84dd38a | ||
|
6b46a70535 | ||
|
4ffaa93431 | ||
|
a0ad7806cb | ||
|
99aac07714 | ||
|
1a4aa3eaba | ||
|
93c9a5f2b1 | ||
|
9a9d518153 | ||
|
7fb03c502e | ||
|
49bb17b583 | ||
|
723cb549d4 | ||
|
8b6a4c060e | ||
|
cb80980027 | ||
|
c4fc3a8ede | ||
|
b4f9ef24a5 | ||
|
866d752a3c | ||
|
e0b1d0553d | ||
|
7c146272c3 | ||
|
cfdc732d3a | ||
|
522f974e30 | ||
|
b152be7951 | ||
|
485b0f1a54 | ||
|
7540b02723 | ||
|
7746db4169 | ||
|
db2ce81fc4 | ||
|
4175a1af80 | ||
|
563280456e | ||
|
2fecb7ecdf | ||
|
2c6f9c7fd5 | ||
|
e338e59868 | ||
|
b0caa85ed4 | ||
|
ad60d91f5c | ||
|
6423d2dde5 | ||
|
12163701e7 | ||
|
5c35e97a75 | ||
|
b05f221565 | ||
|
beec080274 | ||
|
492d8f1b01 | ||
|
d3737d4453 | ||
|
b459949f57 | ||
|
93f5df2d2a | ||
|
cf426493e1 | ||
|
8e3ff0408e | ||
|
66e06b3952 | ||
|
6b9d9dfaa5 | ||
|
0eaf8d33e7 | ||
|
c31a29ec7f | ||
|
80635c9e24 | ||
|
95d75e07b2 | ||
|
efbfdc9340 | ||
|
1ae3aab764 | ||
|
f68881c552 | ||
|
2ba1ba88b8 | ||
|
079fa0b8f6 | ||
|
b0a740d5c5 | ||
|
ee46242a43 | ||
|
4ba6221e04 | ||
|
31829b6c05 | ||
|
b0370ae2fd | ||
|
6efab9aab1 | ||
|
d075acce43 | ||
|
3a0c1dca90 | ||
|
0f6b13a4ec | ||
|
64760ec960 | ||
|
555f789269 | ||
|
5dea21d531 | ||
|
d5622a65f8 | ||
|
9059de8569 | ||
|
0203b62a6d | ||
|
99d585b7be | ||
|
b4670988b5 | ||
|
1d0a6ac08f | ||
|
8e54a4a6cc | ||
|
a14ebefd24 | ||
|
705e86eb4c | ||
|
a1d632e582 |
451 changed files with 18851 additions and 11840 deletions
|
@ -3,3 +3,5 @@ edition = "2021"
|
||||||
imports_layout = "HorizontalVertical"
|
imports_layout = "HorizontalVertical"
|
||||||
imports_granularity = "Crate"
|
imports_granularity = "Crate"
|
||||||
group_imports = "One"
|
group_imports = "One"
|
||||||
|
wrap_comments = true
|
||||||
|
comment_width = 100
|
||||||
|
|
101
.woodpecker.yml
101
.woodpecker.yml
|
@ -2,8 +2,14 @@
|
||||||
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- &rust_image "rust:1.76"
|
- &rust_image "rust:1.81"
|
||||||
|
- &rust_nightly_image "rustlang/rust:nightly"
|
||||||
- &install_pnpm "corepack enable pnpm"
|
- &install_pnpm "corepack enable pnpm"
|
||||||
|
- &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin"
|
||||||
|
- install_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
|
- &slow_check_paths
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
path:
|
path:
|
||||||
|
@ -25,15 +31,6 @@ variables:
|
||||||
".gitmodules",
|
".gitmodules",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Broken for cron jobs currently, see
|
|
||||||
# https://github.com/woodpecker-ci/woodpecker/issues/1716
|
|
||||||
# clone:
|
|
||||||
# git:
|
|
||||||
# image: woodpeckerci/plugin-git
|
|
||||||
# settings:
|
|
||||||
# recursive: true
|
|
||||||
# submodule_update_remote: true
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
prepare_repo:
|
prepare_repo:
|
||||||
image: alpine:3
|
image: alpine:3
|
||||||
|
@ -42,7 +39,7 @@ steps:
|
||||||
- git submodule init
|
- git submodule init
|
||||||
- git submodule update
|
- git submodule update
|
||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: [pull_request, tag]
|
||||||
|
|
||||||
prettier_check:
|
prettier_check:
|
||||||
image: tmknom/prettier:3.0.0
|
image: tmknom/prettier:3.0.0
|
||||||
|
@ -66,7 +63,7 @@ steps:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
|
|
||||||
cargo_fmt:
|
cargo_fmt:
|
||||||
image: rustlang/rust:nightly
|
image: *rust_nightly_image
|
||||||
environment:
|
environment:
|
||||||
# store cargo data in repo folder so that it gets cached between steps
|
# store cargo data in repo folder so that it gets cached between steps
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
|
@ -77,11 +74,9 @@ steps:
|
||||||
- event: pull_request
|
- event: pull_request
|
||||||
|
|
||||||
cargo_machete:
|
cargo_machete:
|
||||||
image: rustlang/rust:nightly
|
image: *rust_nightly_image
|
||||||
commands:
|
commands:
|
||||||
- wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
|
- *install_binstall
|
||||||
- tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
|
|
||||||
- cp cargo-binstall /usr/local/cargo/bin
|
|
||||||
- cargo binstall -y cargo-machete
|
- cargo binstall -y cargo-machete
|
||||||
- cargo machete
|
- cargo machete
|
||||||
when:
|
when:
|
||||||
|
@ -133,26 +128,17 @@ steps:
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
check_diesel_schema:
|
check_diesel_schema:
|
||||||
image: willsquire/diesel-cli
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||||
commands:
|
commands:
|
||||||
|
- <<: *install_diesel_cli
|
||||||
- diesel migration run
|
- diesel migration run
|
||||||
- diesel print-schema --config-file=diesel.toml > tmp.schema
|
- diesel print-schema --config-file=diesel.toml > tmp.schema
|
||||||
- diff tmp.schema crates/db_schema/src/schema.rs
|
- diff tmp.schema crates/db_schema/src/schema.rs
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
check_diesel_migration_revertable:
|
|
||||||
image: willsquire/diesel-cli
|
|
||||||
environment:
|
|
||||||
CARGO_HOME: .cargo_home
|
|
||||||
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
|
||||||
commands:
|
|
||||||
- diesel migration run
|
|
||||||
- diesel migration redo
|
|
||||||
when: *slow_check_paths
|
|
||||||
|
|
||||||
check_db_perf_tool:
|
check_db_perf_tool:
|
||||||
image: *rust_image
|
image: *rust_image
|
||||||
environment:
|
environment:
|
||||||
|
@ -171,7 +157,7 @@ steps:
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
- rustup component add clippy
|
- rustup component add clippy
|
||||||
- cargo clippy --workspace --tests --all-targets --features console -- -D warnings
|
- cargo clippy --workspace --tests --all-targets -- -D warnings
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
cargo_build:
|
cargo_build:
|
||||||
|
@ -189,11 +175,45 @@ steps:
|
||||||
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||||
RUST_BACKTRACE: "1"
|
RUST_BACKTRACE: "1"
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
|
LEMMY_TEST_FAST_FEDERATION: "1"
|
||||||
commands:
|
commands:
|
||||||
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
|
||||||
- cargo test --workspace --no-fail-fast
|
- cargo test --workspace --no-fail-fast
|
||||||
when: *slow_check_paths
|
when: *slow_check_paths
|
||||||
|
|
||||||
|
check_diesel_migration:
|
||||||
|
# TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server
|
||||||
|
image: *rust_image
|
||||||
|
environment:
|
||||||
|
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||||
|
RUST_BACKTRACE: "1"
|
||||||
|
CARGO_HOME: .cargo_home
|
||||||
|
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
|
||||||
|
PGUSER: lemmy
|
||||||
|
PGPASSWORD: password
|
||||||
|
PGHOST: database
|
||||||
|
PGDATABASE: lemmy
|
||||||
|
commands:
|
||||||
|
# Install diesel_cli
|
||||||
|
- <<: *install_diesel_cli
|
||||||
|
# Run all migrations
|
||||||
|
- diesel migration run
|
||||||
|
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
|
||||||
|
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f before.sqldump
|
||||||
|
# Make sure that the newest migration is revertable without the `r` schema
|
||||||
|
- diesel migration redo
|
||||||
|
# Run schema setup twice, which fails on the 2nd time if `DROP SCHEMA IF EXISTS r CASCADE` drops the wrong things
|
||||||
|
- alias lemmy_schema_setup="target/lemmy_server --disable-scheduled-tasks --disable-http-server --disable-activity-sending"
|
||||||
|
- lemmy_schema_setup
|
||||||
|
- lemmy_schema_setup
|
||||||
|
# Make sure that the newest migration is revertable with the `r` schema
|
||||||
|
- diesel migration redo
|
||||||
|
# Check for changes in the schema, which would be caused by an incorrect migration
|
||||||
|
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
|
||||||
|
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f after.sqldump
|
||||||
|
- diff before.sqldump after.sqldump
|
||||||
|
when: *slow_check_paths
|
||||||
|
|
||||||
run_federation_tests:
|
run_federation_tests:
|
||||||
image: node:20-bookworm-slim
|
image: node:20-bookworm-slim
|
||||||
environment:
|
environment:
|
||||||
|
@ -201,7 +221,7 @@ steps:
|
||||||
DO_WRITE_HOSTS_FILE: "1"
|
DO_WRITE_HOSTS_FILE: "1"
|
||||||
commands:
|
commands:
|
||||||
- *install_pnpm
|
- *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
|
- bash api_tests/prepare-drone-federation-test.sh
|
||||||
- cd api_tests/
|
- cd api_tests/
|
||||||
- pnpm i
|
- pnpm i
|
||||||
|
@ -220,10 +240,13 @@ steps:
|
||||||
|
|
||||||
publish_release_docker:
|
publish_release_docker:
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
secrets: [docker_username, docker_password]
|
|
||||||
settings:
|
settings:
|
||||||
repo: dessalines/lemmy
|
repo: dessalines/lemmy
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
platforms: linux/amd64, linux/arm64
|
platforms: linux/amd64, linux/arm64
|
||||||
build_args:
|
build_args:
|
||||||
- RUST_RELEASE_MODE=release
|
- RUST_RELEASE_MODE=release
|
||||||
|
@ -233,10 +256,13 @@ steps:
|
||||||
|
|
||||||
nightly_build:
|
nightly_build:
|
||||||
image: woodpeckerci/plugin-docker-buildx
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
secrets: [docker_username, docker_password]
|
|
||||||
settings:
|
settings:
|
||||||
repo: dessalines/lemmy
|
repo: dessalines/lemmy
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
build_args:
|
build_args:
|
||||||
- RUST_RELEASE_MODE=release
|
- RUST_RELEASE_MODE=release
|
||||||
|
@ -248,11 +274,11 @@ steps:
|
||||||
publish_to_crates_io:
|
publish_to_crates_io:
|
||||||
image: *rust_image
|
image: *rust_image
|
||||||
commands:
|
commands:
|
||||||
- 'echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"'
|
- *install_binstall
|
||||||
- cargo install cargo-workspaces
|
# Install cargo-workspaces
|
||||||
|
- cargo binstall -y cargo-workspaces
|
||||||
- cp -r migrations crates/db_schema/
|
- cp -r migrations crates/db_schema/
|
||||||
- cargo login "$CARGO_API_TOKEN"
|
- cargo workspaces publish --token "$CARGO_API_TOKEN" --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
|
||||||
- cargo workspaces publish --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
|
|
||||||
secrets: [cargo_api_token]
|
secrets: [cargo_api_token]
|
||||||
when:
|
when:
|
||||||
- event: tag
|
- event: tag
|
||||||
|
@ -263,7 +289,7 @@ steps:
|
||||||
- apk add curl
|
- apk add curl
|
||||||
- "curl -d'Lemmy CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
|
- "curl -d'Lemmy CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
|
||||||
when:
|
when:
|
||||||
- event: pull_request
|
- event: [pull_request, tag]
|
||||||
status: failure
|
status: failure
|
||||||
|
|
||||||
notify_on_tag_deploy:
|
notify_on_tag_deploy:
|
||||||
|
@ -276,7 +302,8 @@ steps:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: postgres:16-alpine
|
# 15-alpine image necessary because of diesel tests
|
||||||
|
image: pgautoupgrade/pgautoupgrade:15-alpine
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: lemmy
|
POSTGRES_USER: lemmy
|
||||||
POSTGRES_PASSWORD: password
|
POSTGRES_PASSWORD: password
|
||||||
|
|
4110
Cargo.lock
generated
4110
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
131
Cargo.toml
131
Cargo.toml
|
@ -1,5 +1,5 @@
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.19.4-beta.1"
|
version = "0.19.6-beta.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A link aggregator for the fediverse"
|
description = "A link aggregator for the fediverse"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
|
@ -36,16 +36,6 @@ opt-level = "z" # Optimize for size.
|
||||||
debug = 0
|
debug = 0
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
embed-pictrs = ["pict-rs"]
|
|
||||||
# This feature requires building with `tokio_unstable` flag, see documentation:
|
|
||||||
# https://docs.rs/tokio/latest/tokio/#unstable-features
|
|
||||||
console = [
|
|
||||||
"console-subscriber",
|
|
||||||
"opentelemetry",
|
|
||||||
"opentelemetry-otlp",
|
|
||||||
"tracing-opentelemetry",
|
|
||||||
"reqwest-tracing/opentelemetry_0_16",
|
|
||||||
]
|
|
||||||
json-log = ["tracing-subscriber/json"]
|
json-log = ["tracing-subscriber/json"]
|
||||||
default = []
|
default = []
|
||||||
|
|
||||||
|
@ -67,8 +57,8 @@ members = [
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
cast_lossless = "deny"
|
cast_lossless = "deny"
|
||||||
complexity = "deny"
|
complexity = { level = "deny", priority = -1 }
|
||||||
correctness = "deny"
|
correctness = { level = "deny", priority = -1 }
|
||||||
dbg_macro = "deny"
|
dbg_macro = "deny"
|
||||||
explicit_into_iter_loop = "deny"
|
explicit_into_iter_loop = "deny"
|
||||||
explicit_iter_loop = "deny"
|
explicit_iter_loop = "deny"
|
||||||
|
@ -79,36 +69,38 @@ inefficient_to_string = "deny"
|
||||||
items-after-statements = "deny"
|
items-after-statements = "deny"
|
||||||
manual_string_new = "deny"
|
manual_string_new = "deny"
|
||||||
needless_collect = "deny"
|
needless_collect = "deny"
|
||||||
perf = "deny"
|
perf = { level = "deny", priority = -1 }
|
||||||
redundant_closure_for_method_calls = "deny"
|
redundant_closure_for_method_calls = "deny"
|
||||||
style = "deny"
|
style = { level = "deny", priority = -1 }
|
||||||
suspicious = "deny"
|
suspicious = { level = "deny", priority = -1 }
|
||||||
uninlined_format_args = "allow"
|
uninlined_format_args = "allow"
|
||||||
unused_self = "deny"
|
unused_self = "deny"
|
||||||
unwrap_used = "deny"
|
unwrap_used = "deny"
|
||||||
|
unimplemented = "deny"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
lemmy_api = { version = "=0.19.4-beta.1", path = "./crates/api" }
|
lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" }
|
||||||
lemmy_api_crud = { version = "=0.19.4-beta.1", path = "./crates/api_crud" }
|
lemmy_api_crud = { version = "=0.19.6-beta.7", path = "./crates/api_crud" }
|
||||||
lemmy_apub = { version = "=0.19.4-beta.1", path = "./crates/apub" }
|
lemmy_apub = { version = "=0.19.6-beta.7", path = "./crates/apub" }
|
||||||
lemmy_utils = { version = "=0.19.4-beta.1", path = "./crates/utils", default-features = false }
|
lemmy_utils = { version = "=0.19.6-beta.7", path = "./crates/utils", default-features = false }
|
||||||
lemmy_db_schema = { version = "=0.19.4-beta.1", path = "./crates/db_schema" }
|
lemmy_db_schema = { version = "=0.19.6-beta.7", path = "./crates/db_schema" }
|
||||||
lemmy_api_common = { version = "=0.19.4-beta.1", path = "./crates/api_common" }
|
lemmy_api_common = { version = "=0.19.6-beta.7", path = "./crates/api_common" }
|
||||||
lemmy_routes = { version = "=0.19.4-beta.1", path = "./crates/routes" }
|
lemmy_routes = { version = "=0.19.6-beta.7", path = "./crates/routes" }
|
||||||
lemmy_db_views = { version = "=0.19.4-beta.1", path = "./crates/db_views" }
|
lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
|
||||||
lemmy_db_views_actor = { version = "=0.19.4-beta.1", path = "./crates/db_views_actor" }
|
lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
|
||||||
lemmy_db_views_moderator = { version = "=0.19.4-beta.1", path = "./crates/db_views_moderator" }
|
lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
|
||||||
activitypub_federation = { version = "0.5.2", default-features = false, features = [
|
lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
|
||||||
|
activitypub_federation = { version = "0.6.0-alpha1", default-features = false, features = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
] }
|
] }
|
||||||
diesel = "2.1.4"
|
diesel = "2.1.6"
|
||||||
diesel_migrations = "2.1.0"
|
diesel_migrations = "2.1.0"
|
||||||
diesel-async = "0.4.1"
|
diesel-async = "0.4.1"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
serde_with = "3.7.0"
|
serde_with = "3.9.0"
|
||||||
actix-web = { version = "4.5.1", default-features = false, features = [
|
actix-web = { version = "4.9.0", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
"rustls",
|
"rustls-0_23",
|
||||||
"compress-brotli",
|
"compress-brotli",
|
||||||
"compress-gzip",
|
"compress-gzip",
|
||||||
"compress-zstd",
|
"compress-zstd",
|
||||||
|
@ -116,56 +108,55 @@ actix-web = { version = "4.5.1", default-features = false, features = [
|
||||||
] }
|
] }
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-actix-web = { version = "0.7.10", default-features = false }
|
tracing-actix-web = { version = "0.7.10", default-features = false }
|
||||||
tracing-error = "0.2.0"
|
|
||||||
tracing-log = "0.2.0"
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
url = { version = "2.5.0", features = ["serde"] }
|
url = { version = "2.5.2", features = ["serde"] }
|
||||||
reqwest = { version = "0.11.26", features = ["json", "blocking", "gzip"] }
|
reqwest = { version = "0.12.7", default-features = false, features = [
|
||||||
reqwest-middleware = "0.2.4"
|
"json",
|
||||||
reqwest-tracing = "0.4.7"
|
"blocking",
|
||||||
|
"gzip",
|
||||||
|
"rustls-tls",
|
||||||
|
] }
|
||||||
|
reqwest-middleware = "0.3.3"
|
||||||
|
reqwest-tracing = "0.5.3"
|
||||||
clokwerk = "0.4.0"
|
clokwerk = "0.4.0"
|
||||||
doku = { version = "0.21.1", features = ["url-2"] }
|
doku = { version = "0.21.1", features = ["url-2"] }
|
||||||
bcrypt = "0.15.0"
|
bcrypt = "0.15.1"
|
||||||
chrono = { version = "0.4.35", features = ["serde"], default-features = false }
|
chrono = { version = "0.4.38", features = ["serde"], default-features = false }
|
||||||
serde_json = { version = "1.0.114", features = ["preserve_order"] }
|
serde_json = { version = "1.0.121", features = ["preserve_order"] }
|
||||||
base64 = "0.21.7"
|
base64 = "0.22.1"
|
||||||
uuid = { version = "1.7.0", features = ["serde", "v4"] }
|
uuid = { version = "1.10.0", features = ["serde", "v4"] }
|
||||||
async-trait = "0.1.77"
|
async-trait = "0.1.81"
|
||||||
captcha = "0.0.9"
|
captcha = "0.0.9"
|
||||||
anyhow = { version = "1.0.81", features = [
|
anyhow = { version = "1.0.86", features = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
] } # backtrace is on by default on nightly, but not stable rust
|
] } # backtrace is on by default on nightly, but not stable rust
|
||||||
diesel_ltree = "0.3.1"
|
diesel_ltree = "0.3.1"
|
||||||
typed-builder = "0.18.1"
|
serial_test = "3.1.1"
|
||||||
serial_test = "2.0.0"
|
tokio = { version = "1.39.2", features = ["full"] }
|
||||||
tokio = { version = "1.36.0", features = ["full"] }
|
regex = "1.10.5"
|
||||||
regex = "1.10.3"
|
diesel-derive-newtype = "2.1.2"
|
||||||
once_cell = "1.19.0"
|
|
||||||
diesel-derive-newtype = "2.1.0"
|
|
||||||
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
|
||||||
strum = "0.25.0"
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
strum_macros = "0.25.3"
|
itertools = "0.13.0"
|
||||||
itertools = "0.12.1"
|
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
http = "0.2.12"
|
http = "1.1"
|
||||||
rosetta-i18n = "0.1.3"
|
rosetta-i18n = "0.1.3"
|
||||||
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
|
|
||||||
tracing-opentelemetry = { version = "0.19.0" }
|
|
||||||
ts-rs = { version = "7.1.1", features = [
|
ts-rs = { version = "7.1.1", features = [
|
||||||
"serde-compat",
|
"serde-compat",
|
||||||
"chrono-impl",
|
"chrono-impl",
|
||||||
"no-serde-warnings",
|
"no-serde-warnings",
|
||||||
] }
|
] }
|
||||||
rustls = { version = "0.21.10", features = ["dangerous_configuration"] }
|
rustls = { version = "0.23.12", features = ["ring"] }
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
tokio-postgres = "0.7.10"
|
tokio-postgres = "0.7.11"
|
||||||
tokio-postgres-rustls = "0.10.0"
|
tokio-postgres-rustls = "0.12.0"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
enum-map = "2.7"
|
enum-map = "2.7"
|
||||||
moka = { version = "0.12.5", features = ["future"] }
|
moka = { version = "0.12.8", features = ["future"] }
|
||||||
i-love-jesus = { version = "0.1.0" }
|
i-love-jesus = { version = "0.1.0" }
|
||||||
clap = { version = "4.5.2", features = ["derive"] }
|
clap = { version = "4.5.13", features = ["derive", "env"] }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
derive-new = "0.7.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { workspace = true }
|
lemmy_api = { workspace = true }
|
||||||
|
@ -175,15 +166,13 @@ lemmy_utils = { workspace = true }
|
||||||
lemmy_db_schema = { workspace = true }
|
lemmy_db_schema = { workspace = true }
|
||||||
lemmy_api_common = { workspace = true }
|
lemmy_api_common = { workspace = true }
|
||||||
lemmy_routes = { workspace = true }
|
lemmy_routes = { workspace = true }
|
||||||
lemmy_federate = { version = "0.19.4-beta.1", path = "crates/federate" }
|
lemmy_federate = { workspace = true }
|
||||||
activitypub_federation = { workspace = true }
|
activitypub_federation = { workspace = true }
|
||||||
diesel = { workspace = true }
|
diesel = { workspace = true }
|
||||||
diesel-async = { workspace = true }
|
diesel-async = { workspace = true }
|
||||||
actix-web = { workspace = true }
|
actix-web = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-actix-web = { workspace = true }
|
tracing-actix-web = { workspace = true }
|
||||||
tracing-error = { workspace = true }
|
|
||||||
tracing-log = { workspace = true }
|
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
|
@ -191,19 +180,15 @@ reqwest-middleware = { workspace = true }
|
||||||
reqwest-tracing = { workspace = true }
|
reqwest-tracing = { workspace = true }
|
||||||
clokwerk = { workspace = true }
|
clokwerk = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tracing-opentelemetry = { workspace = true, optional = true }
|
rustls = { workspace = 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.9", optional = true }
|
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
actix-cors = "0.6.5"
|
actix-cors = "0.7.0"
|
||||||
futures-util = { workspace = true }
|
futures-util = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
prometheus = { version = "0.13.3", features = ["process"] }
|
prometheus = { version = "0.13.4", features = ["process"] }
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
actix-web-prom = "0.7.0"
|
actix-web-prom = "0.8.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { 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
|
|
||||||
}
|
|
||||||
}
|
|
1
api_tests/.npmrc
Normal file
1
api_tests/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-manager-strict=false
|
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,10 +6,11 @@
|
||||||
"repository": "https://github.com/LemmyNet/lemmy",
|
"repository": "https://github.com/LemmyNet/lemmy",
|
||||||
"author": "Dessalines",
|
"author": "Dessalines",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
|
"packageManager": "pnpm@9.9.0",
|
||||||
"scripts": {
|
"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",
|
"fix": "prettier --write src && eslint --fix src",
|
||||||
"api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts && jest -i image.spec.ts",
|
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
|
||||||
"api-test-follow": "jest -i follow.spec.ts",
|
"api-test-follow": "jest -i follow.spec.ts",
|
||||||
"api-test-comment": "jest -i comment.spec.ts",
|
"api-test-comment": "jest -i comment.spec.ts",
|
||||||
"api-test-post": "jest -i post.spec.ts",
|
"api-test-post": "jest -i post.spec.ts",
|
||||||
|
@ -20,16 +21,16 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.11.27",
|
"@types/node": "^22.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^8.1.0",
|
||||||
"@typescript-eslint/parser": "^7.2.0",
|
"@typescript-eslint/parser": "^8.1.0",
|
||||||
"download-file-sync": "^1.0.4",
|
"eslint": "^9.9.0",
|
||||||
"eslint": "^8.57.0",
|
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"lemmy-js-client": "0.19.4-alpha.13",
|
"lemmy-js-client": "0.20.0-alpha.11",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"typescript": "^5.4.2"
|
"typescript": "^5.5.4",
|
||||||
|
"typescript-eslint": "^8.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,19 +3,19 @@
|
||||||
# it is expected that this script is called by run-federation-test.sh script.
|
# it is expected that this script is called by run-federation-test.sh script.
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ -n "$LEMMY_LOG_LEVEL" ];
|
if [ -z "$LEMMY_LOG_LEVEL" ];
|
||||||
then
|
then
|
||||||
LEMMY_LOG_LEVEL=warn
|
LEMMY_LOG_LEVEL=info
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export RUST_BACKTRACE=1
|
export RUST_BACKTRACE=1
|
||||||
#export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
|
export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
|
||||||
|
|
||||||
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
|
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
|
||||||
|
|
||||||
# pictrs setup
|
# pictrs setup
|
||||||
if [ ! -f "api_tests/pict-rs" ]; then
|
if [ ! -f "api_tests/pict-rs" ]; then
|
||||||
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.0-beta.2/pict-rs-linux-amd64" -o api_tests/pict-rs
|
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.16/pict-rs-linux-amd64" -o api_tests/pict-rs
|
||||||
chmod +x api_tests/pict-rs
|
chmod +x api_tests/pict-rs
|
||||||
fi
|
fi
|
||||||
./api_tests/pict-rs \
|
./api_tests/pict-rs \
|
||||||
|
|
|
@ -37,15 +37,15 @@ import {
|
||||||
followCommunity,
|
followCommunity,
|
||||||
blockCommunity,
|
blockCommunity,
|
||||||
delay,
|
delay,
|
||||||
|
saveUserSettings,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { CommentView, CommunityView } from "lemmy-js-client";
|
import { CommentView, CommunityView, SaveUserSettings } from "lemmy-js-client";
|
||||||
|
|
||||||
let betaCommunity: CommunityView | undefined;
|
let betaCommunity: CommunityView | undefined;
|
||||||
let postOnAlphaRes: PostResponse;
|
let postOnAlphaRes: PostResponse;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await setupLogins();
|
await setupLogins();
|
||||||
await unfollows();
|
|
||||||
await Promise.all([followBeta(alpha), followBeta(gamma)]);
|
await Promise.all([followBeta(alpha), followBeta(gamma)]);
|
||||||
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||||
if (betaCommunity) {
|
if (betaCommunity) {
|
||||||
|
@ -53,9 +53,7 @@ beforeAll(async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(unfollows);
|
||||||
unfollows();
|
|
||||||
});
|
|
||||||
|
|
||||||
function assertCommentFederation(
|
function assertCommentFederation(
|
||||||
commentOne?: CommentView,
|
commentOne?: CommentView,
|
||||||
|
@ -94,7 +92,7 @@ test("Create a comment", async () => {
|
||||||
|
|
||||||
test("Create a comment in a non-existent post", async () => {
|
test("Create a comment in a non-existent post", async () => {
|
||||||
await expect(createComment(alpha, -1)).rejects.toStrictEqual(
|
await expect(createComment(alpha, -1)).rejects.toStrictEqual(
|
||||||
Error("couldnt_find_post"),
|
Error("not_found"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -145,7 +143,7 @@ test("Delete a comment", async () => {
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() =>
|
() =>
|
||||||
resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
||||||
r => r.message !== "couldnt_find_object",
|
r => r.message !== "not_found",
|
||||||
)
|
)
|
||||||
).comment;
|
).comment;
|
||||||
if (!gammaComment) {
|
if (!gammaComment) {
|
||||||
|
@ -163,13 +161,13 @@ test("Delete a comment", async () => {
|
||||||
// Make sure that comment is undefined on beta
|
// Make sure that comment is undefined on beta
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
||||||
e => e.message == "couldnt_find_object",
|
e => e.message == "not_found",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure that comment is undefined on gamma after delete
|
// Make sure that comment is undefined on gamma after delete
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
|
||||||
e => e.message === "couldnt_find_object",
|
e => e.message === "not_found",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test undeleting the comment
|
// Test undeleting the comment
|
||||||
|
@ -184,7 +182,7 @@ test("Delete a comment", async () => {
|
||||||
let betaComment2 = (
|
let betaComment2 = (
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
|
||||||
e => e.message !== "couldnt_find_object",
|
e => e.message !== "not_found",
|
||||||
)
|
)
|
||||||
).comment;
|
).comment;
|
||||||
expect(betaComment2?.comment.deleted).toBe(false);
|
expect(betaComment2?.comment.deleted).toBe(false);
|
||||||
|
@ -446,6 +444,59 @@ test("Reply to a comment from another instance, get notification", async () => {
|
||||||
assertCommentFederation(alphaReply, replyRes.comment_view);
|
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 () => {
|
test("Mention beta from alpha", async () => {
|
||||||
if (!betaCommunity) throw Error("no community");
|
if (!betaCommunity) throw Error("no community");
|
||||||
const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
|
const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
jest.setTimeout(120000);
|
jest.setTimeout(120000);
|
||||||
|
|
||||||
|
import { AddModToCommunity } from "lemmy-js-client/dist/types/AddModToCommunity";
|
||||||
import { CommunityView } from "lemmy-js-client/dist/types/CommunityView";
|
import { CommunityView } from "lemmy-js-client/dist/types/CommunityView";
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
|
@ -9,6 +10,7 @@ import {
|
||||||
resolveCommunity,
|
resolveCommunity,
|
||||||
createCommunity,
|
createCommunity,
|
||||||
deleteCommunity,
|
deleteCommunity,
|
||||||
|
delay,
|
||||||
removeCommunity,
|
removeCommunity,
|
||||||
getCommunity,
|
getCommunity,
|
||||||
followCommunity,
|
followCommunity,
|
||||||
|
@ -31,10 +33,12 @@ import {
|
||||||
searchPostLocal,
|
searchPostLocal,
|
||||||
longDelay,
|
longDelay,
|
||||||
editCommunity,
|
editCommunity,
|
||||||
|
unfollows,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { EditCommunity, EditSite } from "lemmy-js-client";
|
import { EditCommunity, EditSite } from "lemmy-js-client";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
|
afterAll(unfollows);
|
||||||
|
|
||||||
function assertCommunityFederation(
|
function assertCommunityFederation(
|
||||||
communityOne?: CommunityView,
|
communityOne?: CommunityView,
|
||||||
|
@ -240,7 +244,7 @@ test("Admin actions in remote community are not federated to origin", async () =
|
||||||
);
|
);
|
||||||
expect(banRes.banned).toBe(true);
|
expect(banRes.banned).toBe(true);
|
||||||
|
|
||||||
// ban doesnt federate to community's origin instance alpha
|
// ban doesn't federate to community's origin instance alpha
|
||||||
let alphaPost = (await resolvePost(alpha, gammaPost.post)).post;
|
let alphaPost = (await resolvePost(alpha, gammaPost.post)).post;
|
||||||
expect(alphaPost?.creator_banned_from_community).toBe(false);
|
expect(alphaPost?.creator_banned_from_community).toBe(false);
|
||||||
|
|
||||||
|
@ -378,8 +382,8 @@ test("User blocks instance, communities are hidden", async () => {
|
||||||
test("Community follower count is federated", async () => {
|
test("Community follower count is federated", async () => {
|
||||||
// Follow the beta community from alpha
|
// Follow the beta community from alpha
|
||||||
let community = await createCommunity(beta);
|
let community = await createCommunity(beta);
|
||||||
let community_id = community.community_view.community.actor_id;
|
let communityActorId = community.community_view.community.actor_id;
|
||||||
let resolved = await resolveCommunity(alpha, community_id);
|
let resolved = await resolveCommunity(alpha, communityActorId);
|
||||||
if (!resolved.community) {
|
if (!resolved.community) {
|
||||||
throw "Missing beta community";
|
throw "Missing beta community";
|
||||||
}
|
}
|
||||||
|
@ -387,7 +391,7 @@ test("Community follower count is federated", async () => {
|
||||||
await followCommunity(alpha, true, resolved.community.community.id);
|
await followCommunity(alpha, true, resolved.community.community.id);
|
||||||
let followed = (
|
let followed = (
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveCommunity(alpha, community_id),
|
() => resolveCommunity(alpha, communityActorId),
|
||||||
c => c.community?.subscribed === "Subscribed",
|
c => c.community?.subscribed === "Subscribed",
|
||||||
)
|
)
|
||||||
).community;
|
).community;
|
||||||
|
@ -396,7 +400,7 @@ test("Community follower count is federated", async () => {
|
||||||
expect(followed?.counts.subscribers).toBe(1);
|
expect(followed?.counts.subscribers).toBe(1);
|
||||||
|
|
||||||
// Follow the community from gamma
|
// Follow the community from gamma
|
||||||
resolved = await resolveCommunity(gamma, community_id);
|
resolved = await resolveCommunity(gamma, communityActorId);
|
||||||
if (!resolved.community) {
|
if (!resolved.community) {
|
||||||
throw "Missing beta community";
|
throw "Missing beta community";
|
||||||
}
|
}
|
||||||
|
@ -404,7 +408,7 @@ test("Community follower count is federated", async () => {
|
||||||
await followCommunity(gamma, true, resolved.community.community.id);
|
await followCommunity(gamma, true, resolved.community.community.id);
|
||||||
followed = (
|
followed = (
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveCommunity(gamma, community_id),
|
() => resolveCommunity(gamma, communityActorId),
|
||||||
c => c.community?.subscribed === "Subscribed",
|
c => c.community?.subscribed === "Subscribed",
|
||||||
)
|
)
|
||||||
).community;
|
).community;
|
||||||
|
@ -413,7 +417,7 @@ test("Community follower count is federated", async () => {
|
||||||
expect(followed?.counts?.subscribers).toBe(2);
|
expect(followed?.counts?.subscribers).toBe(2);
|
||||||
|
|
||||||
// Follow the community from delta
|
// Follow the community from delta
|
||||||
resolved = await resolveCommunity(delta, community_id);
|
resolved = await resolveCommunity(delta, communityActorId);
|
||||||
if (!resolved.community) {
|
if (!resolved.community) {
|
||||||
throw "Missing beta community";
|
throw "Missing beta community";
|
||||||
}
|
}
|
||||||
|
@ -421,7 +425,7 @@ test("Community follower count is federated", async () => {
|
||||||
await followCommunity(delta, true, resolved.community.community.id);
|
await followCommunity(delta, true, resolved.community.community.id);
|
||||||
followed = (
|
followed = (
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveCommunity(delta, community_id),
|
() => resolveCommunity(delta, communityActorId),
|
||||||
c => c.community?.subscribed === "Subscribed",
|
c => c.community?.subscribed === "Subscribed",
|
||||||
)
|
)
|
||||||
).community;
|
).community;
|
||||||
|
@ -450,7 +454,7 @@ test("Dont receive community activities after unsubscribe", async () => {
|
||||||
);
|
);
|
||||||
expect(communityRes1.community_view.counts.subscribers).toBe(2);
|
expect(communityRes1.community_view.counts.subscribers).toBe(2);
|
||||||
|
|
||||||
// temporarily block alpha, so that it doesnt know about unfollow
|
// temporarily block alpha, so that it doesn't know about unfollow
|
||||||
let editSiteForm: EditSite = {};
|
let editSiteForm: EditSite = {};
|
||||||
editSiteForm.allowed_instances = ["lemmy-epsilon"];
|
editSiteForm.allowed_instances = ["lemmy-epsilon"];
|
||||||
await beta.editSite(editSiteForm);
|
await beta.editSite(editSiteForm);
|
||||||
|
@ -511,7 +515,7 @@ test("Fetch community, includes posts", async () => {
|
||||||
expect(post_listing.posts[0].post.ap_id).toBe(postRes.post_view.post.ap_id);
|
expect(post_listing.posts[0].post.ap_id).toBe(postRes.post_view.post.ap_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Content in local-only community doesnt federate", async () => {
|
test("Content in local-only community doesn't federate", async () => {
|
||||||
// create a community and set it local-only
|
// create a community and set it local-only
|
||||||
let communityRes = (await createCommunity(alpha)).community_view.community;
|
let communityRes = (await createCommunity(alpha)).community_view.community;
|
||||||
let form: EditCommunity = {
|
let form: EditCommunity = {
|
||||||
|
@ -523,11 +527,49 @@ test("Content in local-only community doesnt federate", async () => {
|
||||||
// cant resolve the community from another instance
|
// cant resolve the community from another instance
|
||||||
await expect(
|
await expect(
|
||||||
resolveCommunity(beta, communityRes.actor_id),
|
resolveCommunity(beta, communityRes.actor_id),
|
||||||
).rejects.toStrictEqual(Error("couldnt_find_object"));
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
|
||||||
// create a post, also cant resolve it
|
// create a post, also cant resolve it
|
||||||
let postRes = await createPost(alpha, communityRes.id);
|
let postRes = await createPost(alpha, communityRes.id);
|
||||||
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual(
|
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual(
|
||||||
Error("couldnt_find_object"),
|
Error("not_found"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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,51 +11,59 @@ import {
|
||||||
betaUrl,
|
betaUrl,
|
||||||
registerUser,
|
registerUser,
|
||||||
unfollows,
|
unfollows,
|
||||||
|
delay,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(unfollows);
|
||||||
unfollows();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Follow local community", async () => {
|
test("Follow local community", async () => {
|
||||||
let user = await registerUser(beta, betaUrl);
|
let user = await registerUser(beta, betaUrl);
|
||||||
|
|
||||||
let community = (await resolveBetaCommunity(user)).community!;
|
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);
|
let follow = await followCommunity(user, true, community.community.id);
|
||||||
|
|
||||||
// Make sure the follow response went through
|
// Make sure the follow response went through
|
||||||
expect(follow.community_view.community.local).toBe(true);
|
expect(follow.community_view.community.local).toBe(true);
|
||||||
expect(follow.community_view.subscribed).toBe("Subscribed");
|
expect(follow.community_view.subscribed).toBe("Subscribed");
|
||||||
expect(follow.community_view.counts.subscribers).toBe(2);
|
expect(follow.community_view.counts.subscribers).toBe(
|
||||||
expect(follow.community_view.counts.subscribers_local).toBe(2);
|
community.counts.subscribers + 1,
|
||||||
|
);
|
||||||
|
expect(follow.community_view.counts.subscribers_local).toBe(
|
||||||
|
community.counts.subscribers_local + 1,
|
||||||
|
);
|
||||||
|
|
||||||
// Test an unfollow
|
// Test an unfollow
|
||||||
let unfollow = await followCommunity(user, false, community.community.id);
|
let unfollow = await followCommunity(user, false, community.community.id);
|
||||||
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
|
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
|
||||||
expect(unfollow.community_view.counts.subscribers).toBe(1);
|
expect(unfollow.community_view.counts.subscribers).toBe(
|
||||||
expect(unfollow.community_view.counts.subscribers_local).toBe(1);
|
community.counts.subscribers,
|
||||||
|
);
|
||||||
|
expect(unfollow.community_view.counts.subscribers_local).toBe(
|
||||||
|
community.counts.subscribers_local,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Follow federated community", async () => {
|
test("Follow federated community", async () => {
|
||||||
// It takes about 1 second for the community aggregates to federate
|
// 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(
|
await waitUntil(
|
||||||
() => resolveBetaCommunity(alpha),
|
() => resolveBetaCommunity(alpha),
|
||||||
c =>
|
c => !!c.community && c.community?.counts.subscribers >= 1,
|
||||||
c.community?.counts.subscribers === 1 &&
|
|
||||||
c.community.counts.subscribers_local === 0,
|
|
||||||
)
|
)
|
||||||
).community;
|
).community;
|
||||||
if (!betaCommunity) {
|
if (!betaCommunityInitial) {
|
||||||
throw "Missing beta community";
|
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");
|
expect(follow.community_view.subscribed).toBe("Pending");
|
||||||
betaCommunity = (
|
const betaCommunity = (
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
() => resolveBetaCommunity(alpha),
|
() => resolveBetaCommunity(alpha),
|
||||||
c => c.community?.subscribed === "Subscribed",
|
c => c.community?.subscribed === "Subscribed",
|
||||||
|
@ -66,20 +74,24 @@ test("Follow federated community", async () => {
|
||||||
expect(betaCommunity?.community.local).toBe(false);
|
expect(betaCommunity?.community.local).toBe(false);
|
||||||
expect(betaCommunity?.community.name).toBe("main");
|
expect(betaCommunity?.community.name).toBe("main");
|
||||||
expect(betaCommunity?.subscribed).toBe("Subscribed");
|
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
|
// check that unfollow was federated
|
||||||
let communityOnBeta1 = await resolveBetaCommunity(beta);
|
let communityOnBeta1 = await resolveBetaCommunity(beta);
|
||||||
expect(communityOnBeta1.community?.counts.subscribers).toBe(2);
|
expect(communityOnBeta1.community?.counts.subscribers).toBe(
|
||||||
expect(communityOnBeta1.community?.counts.subscribers_local).toBe(1);
|
betaCommunityInitial.counts.subscribers + 1,
|
||||||
|
);
|
||||||
|
|
||||||
// Check it from local
|
// Check it from local
|
||||||
let site = await getSite(alpha);
|
let site = await getSite(alpha);
|
||||||
let remoteCommunityId = site.my_user?.follows.find(
|
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;
|
)?.community.id;
|
||||||
expect(remoteCommunityId).toBeDefined();
|
expect(remoteCommunityId).toBeDefined();
|
||||||
expect(site.my_user?.follows.length).toBe(2);
|
|
||||||
|
|
||||||
if (!remoteCommunityId) {
|
if (!remoteCommunityId) {
|
||||||
throw "Missing remote community id";
|
throw "Missing remote community id";
|
||||||
|
@ -91,10 +103,21 @@ test("Follow federated community", async () => {
|
||||||
|
|
||||||
// Make sure you are unsubbed locally
|
// Make sure you are unsubbed locally
|
||||||
let siteUnfollowCheck = await getSite(alpha);
|
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
|
// check that unfollow was federated
|
||||||
let communityOnBeta2 = await resolveBetaCommunity(beta);
|
let communityOnBeta2 = await waitUntil(
|
||||||
expect(communityOnBeta2.community?.counts.subscribers).toBe(1);
|
() => 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);
|
expect(communityOnBeta2.community?.counts.subscribers_local).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
createCommunity,
|
createCommunity,
|
||||||
createPost,
|
createPost,
|
||||||
deleteAllImages,
|
deleteAllImages,
|
||||||
delta,
|
|
||||||
epsilon,
|
epsilon,
|
||||||
followCommunity,
|
followCommunity,
|
||||||
gamma,
|
gamma,
|
||||||
|
@ -28,20 +27,24 @@ import {
|
||||||
setupLogins,
|
setupLogins,
|
||||||
waitForPost,
|
waitForPost,
|
||||||
unfollows,
|
unfollows,
|
||||||
|
getPost,
|
||||||
|
waitUntil,
|
||||||
|
createPostWithThumbnail,
|
||||||
|
sampleImage,
|
||||||
|
sampleSite,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
const downloadFileSync = require("download-file-sync");
|
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(async () => {
|
||||||
unfollows();
|
await Promise.all([unfollows(), deleteAllImages(alpha)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Upload image and delete it", async () => {
|
test("Upload image and delete it", async () => {
|
||||||
// Before running this test, you need to delete all previous images in the DB
|
// Before running this test, you need to delete all previous images in the DB
|
||||||
await deleteAllImages(alpha);
|
await deleteAllImages(alpha);
|
||||||
|
|
||||||
// Upload test image. We use a simple string buffer as pictrs doesnt require an actual image
|
// Upload test image. We use a simple string buffer as pictrs doesn't require an actual image
|
||||||
// in testing mode.
|
// in testing mode.
|
||||||
const upload_form: UploadImage = {
|
const upload_form: UploadImage = {
|
||||||
image: Buffer.from("test"),
|
image: Buffer.from("test"),
|
||||||
|
@ -53,7 +56,8 @@ test("Upload image and delete it", async () => {
|
||||||
expect(upload.delete_url).toBeDefined();
|
expect(upload.delete_url).toBeDefined();
|
||||||
|
|
||||||
// ensure that image download is working. theres probably a better way to do this
|
// 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);
|
expect(content.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
// Ensure that it comes back with the list_media endpoint
|
// Ensure that it comes back with the list_media endpoint
|
||||||
|
@ -71,9 +75,14 @@ test("Upload image and delete it", async () => {
|
||||||
|
|
||||||
// The deleteUrl is a combination of the endpoint, delete token, and alias
|
// The deleteUrl is a combination of the endpoint, delete token, and alias
|
||||||
let firstImage = listMediaRes.images[0];
|
let firstImage = listMediaRes.images[0];
|
||||||
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.pictrs_delete_token}/${firstImage.pictrs_alias}`;
|
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.local_image.pictrs_delete_token}/${firstImage.local_image.pictrs_alias}`;
|
||||||
expect(deleteUrl).toBe(upload.delete_url);
|
expect(deleteUrl).toBe(upload.delete_url);
|
||||||
|
|
||||||
|
// Make sure the uploader is correct
|
||||||
|
expect(firstImage.person.actor_id).toBe(
|
||||||
|
`http://lemmy-alpha:8541/u/lemmy_alpha`,
|
||||||
|
);
|
||||||
|
|
||||||
// delete image
|
// delete image
|
||||||
const delete_form: DeleteImage = {
|
const delete_form: DeleteImage = {
|
||||||
token: upload.files![0].delete_token,
|
token: upload.files![0].delete_token,
|
||||||
|
@ -83,7 +92,8 @@ test("Upload image and delete it", async () => {
|
||||||
expect(delete_).toBe(true);
|
expect(delete_).toBe(true);
|
||||||
|
|
||||||
// ensure that image is deleted
|
// ensure that image is deleted
|
||||||
const content2 = downloadFileSync(upload.url);
|
const response2 = await fetch(upload.url ?? "");
|
||||||
|
const content2 = await response2.text();
|
||||||
expect(content2).toBe("");
|
expect(content2).toBe("");
|
||||||
|
|
||||||
// Ensure that it shows the image is deleted
|
// Ensure that it shows the image is deleted
|
||||||
|
@ -111,7 +121,8 @@ test("Purge user, uploaded image removed", async () => {
|
||||||
expect(upload.delete_url).toBeDefined();
|
expect(upload.delete_url).toBeDefined();
|
||||||
|
|
||||||
// ensure that image download is working. theres probably a better way to do this
|
// 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);
|
expect(content.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
// purge user
|
// purge user
|
||||||
|
@ -123,7 +134,8 @@ test("Purge user, uploaded image removed", async () => {
|
||||||
expect(delete_.success).toBe(true);
|
expect(delete_.success).toBe(true);
|
||||||
|
|
||||||
// ensure that image is deleted
|
// ensure that image is deleted
|
||||||
const content2 = downloadFileSync(upload.url);
|
const response2 = await fetch(upload.url ?? "");
|
||||||
|
const content2 = await response2.text();
|
||||||
expect(content2).toBe("");
|
expect(content2).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -141,7 +153,8 @@ test("Purge post, linked image removed", async () => {
|
||||||
expect(upload.delete_url).toBeDefined();
|
expect(upload.delete_url).toBeDefined();
|
||||||
|
|
||||||
// ensure that image download is working. theres probably a better way to do this
|
// 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);
|
expect(content.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
let community = await resolveBetaCommunity(user);
|
let community = await resolveBetaCommunity(user);
|
||||||
|
@ -151,9 +164,9 @@ test("Purge post, linked image removed", async () => {
|
||||||
upload.url,
|
upload.url,
|
||||||
);
|
);
|
||||||
expect(post.post_view.post.url).toBe(upload.url);
|
expect(post.post_view.post.url).toBe(upload.url);
|
||||||
|
expect(post.post_view.image_details).toBeDefined();
|
||||||
|
|
||||||
// purge post
|
// purge post
|
||||||
|
|
||||||
const purgeForm: PurgePost = {
|
const purgeForm: PurgePost = {
|
||||||
post_id: post.post_view.post.id,
|
post_id: post.post_view.post.id,
|
||||||
};
|
};
|
||||||
|
@ -161,52 +174,102 @@ test("Purge post, linked image removed", async () => {
|
||||||
expect(delete_.success).toBe(true);
|
expect(delete_.success).toBe(true);
|
||||||
|
|
||||||
// ensure that image is deleted
|
// ensure that image is deleted
|
||||||
const content2 = downloadFileSync(upload.url);
|
const response2 = await fetch(upload.url ?? "");
|
||||||
|
const content2 = await response2.text();
|
||||||
expect(content2).toBe("");
|
expect(content2).toBe("");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Images in remote post are proxied if setting enabled", async () => {
|
test("Images in remote image post are proxied if setting enabled", async () => {
|
||||||
let user = await registerUser(beta, betaUrl);
|
|
||||||
let community = await createCommunity(gamma);
|
let community = await createCommunity(gamma);
|
||||||
|
let postRes = await createPost(
|
||||||
const upload_form: UploadImage = {
|
|
||||||
image: Buffer.from("test"),
|
|
||||||
};
|
|
||||||
const upload = await user.uploadImage(upload_form);
|
|
||||||
let post = await createPost(
|
|
||||||
gamma,
|
gamma,
|
||||||
community.community_view.community.id,
|
community.community_view.community.id,
|
||||||
upload.url,
|
sampleImage,
|
||||||
"![](http://example.com/image2.png)",
|
`![](${sampleImage})`,
|
||||||
);
|
);
|
||||||
expect(post.post_view.post).toBeDefined();
|
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
|
// remote image gets proxied after upload
|
||||||
expect(
|
expect(
|
||||||
post.post_view.post.url?.startsWith(
|
post.thumbnail_url?.startsWith(
|
||||||
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
post.post_view.post.body?.startsWith(
|
post.body?.startsWith("![](http://lemmy-gamma:8561/api/v3/image_proxy?url"),
|
||||||
"![](http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
|
||||||
),
|
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
let epsilonPost = await resolvePost(epsilon, post.post_view.post);
|
// Make sure that it ends with jpg, to be sure its an image
|
||||||
expect(epsilonPost.post).toBeDefined();
|
expect(post.thumbnail_url?.endsWith(".jpg")).toBeTruthy();
|
||||||
|
|
||||||
|
let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post);
|
||||||
|
expect(epsilonPostRes.post).toBeDefined();
|
||||||
|
|
||||||
|
// Fetch the post again, the metadata should be backgrounded now
|
||||||
|
// Wait for the metadata to get fetched, since this is backgrounded now
|
||||||
|
let epsilonPostRes2 = await waitUntil(
|
||||||
|
() => getPost(epsilon, epsilonPostRes.post!.post.id),
|
||||||
|
p => p.post_view.post.thumbnail_url != undefined,
|
||||||
|
);
|
||||||
|
const epsilonPost = epsilonPostRes2.post_view.post;
|
||||||
|
|
||||||
// remote image gets proxied after federation
|
|
||||||
expect(
|
expect(
|
||||||
epsilonPost.post!.post.url?.startsWith(
|
epsilonPost.thumbnail_url?.startsWith(
|
||||||
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
epsilonPost.post!.post.body?.startsWith(
|
epsilonPost.body?.startsWith(
|
||||||
"![](http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
"![](http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
|
// Make sure that it ends with jpg, to be sure its an image
|
||||||
|
expect(epsilonPost.thumbnail_url?.endsWith(".jpg")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Thumbnail of remote image link is proxied if setting enabled", async () => {
|
||||||
|
let community = await createCommunity(gamma);
|
||||||
|
let postRes = await createPost(
|
||||||
|
gamma,
|
||||||
|
community.community_view.community.id,
|
||||||
|
// The sample site metadata thumbnail ends in png
|
||||||
|
sampleSite,
|
||||||
|
);
|
||||||
|
const post = postRes.post_view.post;
|
||||||
|
expect(post).toBeDefined();
|
||||||
|
|
||||||
|
// remote image gets proxied after upload
|
||||||
|
expect(
|
||||||
|
post.thumbnail_url?.startsWith(
|
||||||
|
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
||||||
|
),
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
// Make sure that it ends with png, to be sure its an image
|
||||||
|
expect(post.thumbnail_url?.endsWith(".png")).toBeTruthy();
|
||||||
|
|
||||||
|
let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post);
|
||||||
|
expect(epsilonPostRes.post).toBeDefined();
|
||||||
|
|
||||||
|
let epsilonPostRes2 = await waitUntil(
|
||||||
|
() => getPost(epsilon, epsilonPostRes.post!.post.id),
|
||||||
|
p => p.post_view.post.thumbnail_url != undefined,
|
||||||
|
);
|
||||||
|
const epsilonPost = epsilonPostRes2.post_view.post;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
epsilonPost.thumbnail_url?.startsWith(
|
||||||
|
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
||||||
|
),
|
||||||
|
).toBeTruthy();
|
||||||
|
|
||||||
|
// Make sure that it ends with png, to be sure its an image
|
||||||
|
expect(epsilonPost.thumbnail_url?.endsWith(".png")).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("No image proxying if setting is disabled", async () => {
|
test("No image proxying if setting is disabled", async () => {
|
||||||
|
@ -226,15 +289,15 @@ test("No image proxying if setting is disabled", async () => {
|
||||||
alpha,
|
alpha,
|
||||||
community.community_view.community.id,
|
community.community_view.community.id,
|
||||||
upload.url,
|
upload.url,
|
||||||
"![](http://example.com/image2.png)",
|
`![](${sampleImage})`,
|
||||||
);
|
);
|
||||||
expect(post.post_view.post).toBeDefined();
|
expect(post.post_view.post).toBeDefined();
|
||||||
|
|
||||||
// remote image doesnt get proxied after upload
|
// remote image doesn't get proxied after upload
|
||||||
expect(
|
expect(
|
||||||
post.post_view.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
post.post_view.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(post.post_view.post.body).toBe("![](http://example.com/image2.png)");
|
expect(post.post_view.post.body).toBe(`![](${sampleImage})`);
|
||||||
|
|
||||||
let betaPost = await waitForPost(
|
let betaPost = await waitForPost(
|
||||||
beta,
|
beta,
|
||||||
|
@ -243,12 +306,67 @@ test("No image proxying if setting is disabled", async () => {
|
||||||
);
|
);
|
||||||
expect(betaPost.post).toBeDefined();
|
expect(betaPost.post).toBeDefined();
|
||||||
|
|
||||||
// remote image doesnt get proxied after federation
|
// remote image doesn't get proxied after federation
|
||||||
expect(
|
expect(
|
||||||
betaPost.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
betaPost.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(betaPost.post.body).toBe("![](http://example.com/image2.png)");
|
expect(betaPost.post.body).toBe(`![](${sampleImage})`);
|
||||||
|
|
||||||
// Make sure the alt text got federated
|
// Make sure the alt text got federated
|
||||||
expect(post.post_view.post.alt_text).toBe(betaPost.post.alt_text);
|
expect(post.post_view.post.alt_text).toBe(betaPost.post.alt_text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Make regular post, and give it a custom thumbnail", async () => {
|
||||||
|
const uploadForm1: UploadImage = {
|
||||||
|
image: Buffer.from("testRegular1"),
|
||||||
|
};
|
||||||
|
const upload1 = await alphaImage.uploadImage(uploadForm1);
|
||||||
|
|
||||||
|
const community = await createCommunity(alphaImage);
|
||||||
|
|
||||||
|
// Use wikipedia since it has an opengraph image
|
||||||
|
const wikipediaUrl = "https://wikipedia.org/";
|
||||||
|
|
||||||
|
let post = await createPostWithThumbnail(
|
||||||
|
alphaImage,
|
||||||
|
community.community_view.community.id,
|
||||||
|
wikipediaUrl,
|
||||||
|
upload1.url!,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for the metadata to get fetched, since this is backgrounded now
|
||||||
|
post = await waitUntil(
|
||||||
|
() => getPost(alphaImage, post.post_view.post.id),
|
||||||
|
p => p.post_view.post.thumbnail_url != undefined,
|
||||||
|
);
|
||||||
|
expect(post.post_view.post.url).toBe(wikipediaUrl);
|
||||||
|
// Make sure it uses custom thumbnail
|
||||||
|
expect(post.post_view.post.thumbnail_url).toBe(upload1.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Create an image post, and make sure a custom thumbnail doesn't overwrite it", async () => {
|
||||||
|
const uploadForm1: UploadImage = {
|
||||||
|
image: Buffer.from("test1"),
|
||||||
|
};
|
||||||
|
const upload1 = await alphaImage.uploadImage(uploadForm1);
|
||||||
|
|
||||||
|
const uploadForm2: UploadImage = {
|
||||||
|
image: Buffer.from("test2"),
|
||||||
|
};
|
||||||
|
const upload2 = await alphaImage.uploadImage(uploadForm2);
|
||||||
|
|
||||||
|
const community = await createCommunity(alphaImage);
|
||||||
|
|
||||||
|
let post = await createPostWithThumbnail(
|
||||||
|
alphaImage,
|
||||||
|
community.community_view.community.id,
|
||||||
|
upload1.url!,
|
||||||
|
upload2.url!,
|
||||||
|
);
|
||||||
|
post = await waitUntil(
|
||||||
|
() => getPost(alphaImage, post.post_view.post.id),
|
||||||
|
p => p.post_view.post.thumbnail_url != undefined,
|
||||||
|
);
|
||||||
|
expect(post.post_view.post.url).toBe(upload1.url);
|
||||||
|
// Make sure the custom thumbnail is ignored
|
||||||
|
expect(post.post_view.post.thumbnail_url == upload2.url).toBe(false);
|
||||||
|
});
|
||||||
|
|
|
@ -48,24 +48,27 @@ beforeAll(async () => {
|
||||||
await setupLogins();
|
await setupLogins();
|
||||||
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
betaCommunity = (await resolveBetaCommunity(alpha)).community;
|
||||||
expect(betaCommunity).toBeDefined();
|
expect(betaCommunity).toBeDefined();
|
||||||
await unfollows();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(unfollows);
|
||||||
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,
|
// 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.
|
// so wait for it explicitly. For removed posts we cant refetch anything.
|
||||||
|
if (waitForMeta) {
|
||||||
postOne = await waitForPost(beta, postOne.post, res => {
|
postOne = await waitForPost(beta, postOne.post, res => {
|
||||||
return res === null || res?.post.embed_title !== null;
|
return res === null || !!res?.post.embed_title;
|
||||||
});
|
});
|
||||||
postTwo = await waitForPost(
|
postTwo = await waitForPost(
|
||||||
beta,
|
beta,
|
||||||
postTwo.post,
|
postTwo.post,
|
||||||
res => res === null || res?.post.embed_title !== null,
|
res => res === null || !!res?.post.embed_title,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
expect(postOne?.post.ap_id).toBe(postTwo?.post.ap_id);
|
expect(postOne?.post.ap_id).toBe(postTwo?.post.ap_id);
|
||||||
expect(postOne?.post.name).toBe(postTwo?.post.name);
|
expect(postOne?.post.name).toBe(postTwo?.post.name);
|
||||||
|
@ -85,10 +88,7 @@ async function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
||||||
|
|
||||||
test("Create a post", async () => {
|
test("Create a post", async () => {
|
||||||
// Setup some allowlists and blocklists
|
// Setup some allowlists and blocklists
|
||||||
let editSiteForm: EditSite = {
|
const editSiteForm: EditSite = {};
|
||||||
allowed_instances: ["lemmy-beta"],
|
|
||||||
};
|
|
||||||
await delta.editSite(editSiteForm);
|
|
||||||
|
|
||||||
editSiteForm.allowed_instances = [];
|
editSiteForm.allowed_instances = [];
|
||||||
editSiteForm.blocked_instances = ["lemmy-alpha"];
|
editSiteForm.blocked_instances = ["lemmy-alpha"];
|
||||||
|
@ -125,12 +125,12 @@ test("Create a post", async () => {
|
||||||
// Delta only follows beta, so it should not see an alpha ap_id
|
// Delta only follows beta, so it should not see an alpha ap_id
|
||||||
await expect(
|
await expect(
|
||||||
resolvePost(delta, postRes.post_view.post),
|
resolvePost(delta, postRes.post_view.post),
|
||||||
).rejects.toStrictEqual(Error("couldnt_find_object"));
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
|
||||||
// Epsilon has alpha blocked, it should not see the alpha post
|
// Epsilon has alpha blocked, it should not see the alpha post
|
||||||
await expect(
|
await expect(
|
||||||
resolvePost(epsilon, postRes.post_view.post),
|
resolvePost(epsilon, postRes.post_view.post),
|
||||||
).rejects.toStrictEqual(Error("couldnt_find_object"));
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
|
||||||
// remove added allow/blocklists
|
// remove added allow/blocklists
|
||||||
editSiteForm.allowed_instances = [];
|
editSiteForm.allowed_instances = [];
|
||||||
|
@ -140,9 +140,7 @@ test("Create a post", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create a post in a non-existent community", async () => {
|
test("Create a post in a non-existent community", async () => {
|
||||||
await expect(createPost(alpha, -2)).rejects.toStrictEqual(
|
await expect(createPost(alpha, -2)).rejects.toStrictEqual(Error("not_found"));
|
||||||
Error("couldnt_find_community"),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Unlike a post", async () => {
|
test("Unlike a post", async () => {
|
||||||
|
@ -414,7 +412,11 @@ test("Remove a post from admin and community on same instance", async () => {
|
||||||
p => p?.post_view.post.removed ?? false,
|
p => p?.post_view.post.removed ?? false,
|
||||||
);
|
);
|
||||||
expect(alphaPost?.post_view.post.removed).toBe(true);
|
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
|
// Undelete
|
||||||
let undeletedPost = await removePost(beta, false, betaPost.post);
|
let undeletedPost = await removePost(beta, false, betaPost.post);
|
||||||
|
@ -498,17 +500,24 @@ test("Enforce site ban federation for local user", async () => {
|
||||||
alpha,
|
alpha,
|
||||||
alphaPerson.person.id,
|
alphaPerson.person.id,
|
||||||
false,
|
false,
|
||||||
false,
|
true,
|
||||||
);
|
);
|
||||||
expect(unBanAlpha.banned).toBe(false);
|
expect(unBanAlpha.banned).toBe(false);
|
||||||
|
|
||||||
|
// existing alpha post should be restored on beta
|
||||||
|
betaBanRes = await waitUntil(
|
||||||
|
() => getPost(beta, searchBeta1.post.id),
|
||||||
|
s => !s.post_view.post.removed,
|
||||||
|
);
|
||||||
|
expect(betaBanRes.post_view.post.removed).toBe(false);
|
||||||
|
|
||||||
// Login gets invalidated by ban, need to login again
|
// Login gets invalidated by ban, need to login again
|
||||||
if (!alphaUserPerson) {
|
if (!alphaUserPerson) {
|
||||||
throw "Missing alpha person";
|
throw "Missing alpha person";
|
||||||
}
|
}
|
||||||
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
|
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
|
||||||
alphaUserHttp.setHeaders({
|
alphaUserHttp.setHeaders({
|
||||||
Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "",
|
Authorization: "Bearer " + newAlphaUserJwt.jwt,
|
||||||
});
|
});
|
||||||
// alpha makes new post in beta community, it federates
|
// alpha makes new post in beta community, it federates
|
||||||
let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id);
|
let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id);
|
||||||
|
@ -619,7 +628,7 @@ test("Enforce community ban for federated user", async () => {
|
||||||
// Alpha tries to make post on beta, but it fails because of ban
|
// Alpha tries to make post on beta, but it fails because of ban
|
||||||
await expect(
|
await expect(
|
||||||
createPost(alpha, betaCommunity.community.id),
|
createPost(alpha, betaCommunity.community.id),
|
||||||
).rejects.toStrictEqual(Error("banned_from_community"));
|
).rejects.toStrictEqual(Error("person_is_banned_from_community"));
|
||||||
|
|
||||||
// Unban alpha
|
// Unban alpha
|
||||||
let unBanAlpha = await banPersonFromCommunity(
|
let unBanAlpha = await banPersonFromCommunity(
|
||||||
|
@ -663,40 +672,60 @@ test("A and G subscribe to B (center) A posts, it gets announced to G", async ()
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Report a post", async () => {
|
test("Report a post", async () => {
|
||||||
// Note, this is a different one from the setup
|
// Create post from alpha
|
||||||
let betaCommunity = (await resolveBetaCommunity(beta)).community;
|
let alphaCommunity = (await resolveBetaCommunity(alpha)).community!;
|
||||||
if (!betaCommunity) {
|
|
||||||
throw "Missing beta community";
|
|
||||||
}
|
|
||||||
await followBeta(alpha);
|
await followBeta(alpha);
|
||||||
let postRes = await createPost(beta, betaCommunity.community.id);
|
let postRes = await createPost(alpha, alphaCommunity.community.id);
|
||||||
expect(postRes.post_view.post).toBeDefined();
|
expect(postRes.post_view.post).toBeDefined();
|
||||||
|
|
||||||
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
|
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
|
||||||
if (!alphaPost) {
|
if (!alphaPost) {
|
||||||
throw "Missing alpha post";
|
throw "Missing alpha post";
|
||||||
}
|
}
|
||||||
let alphaReport = (
|
|
||||||
await reportPost(alpha, alphaPost.post.id, randomString(10))
|
|
||||||
).post_report_view.post_report;
|
|
||||||
|
|
||||||
|
// Send report from gamma
|
||||||
|
let gammaPost = (await resolvePost(gamma, alphaPost.post)).post!;
|
||||||
|
let gammaReport = (
|
||||||
|
await reportPost(gamma, gammaPost.post.id, randomString(10))
|
||||||
|
).post_report_view.post_report;
|
||||||
|
expect(gammaReport).toBeDefined();
|
||||||
|
|
||||||
|
// Report was federated to community instance
|
||||||
let betaReport = (await waitUntil(
|
let betaReport = (await waitUntil(
|
||||||
() =>
|
() =>
|
||||||
listPostReports(beta).then(p =>
|
listPostReports(beta).then(p =>
|
||||||
p.post_reports.find(
|
p.post_reports.find(
|
||||||
r =>
|
r =>
|
||||||
r.post_report.original_post_name === alphaReport.original_post_name,
|
r.post_report.original_post_name === gammaReport.original_post_name,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
res => !!res,
|
res => !!res,
|
||||||
))!.post_report;
|
))!.post_report;
|
||||||
expect(betaReport).toBeDefined();
|
expect(betaReport).toBeDefined();
|
||||||
expect(betaReport.resolved).toBe(false);
|
expect(betaReport.resolved).toBe(false);
|
||||||
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name);
|
expect(betaReport.original_post_name).toBe(gammaReport.original_post_name);
|
||||||
expect(betaReport.original_post_url).toBe(alphaReport.original_post_url);
|
//expect(betaReport.original_post_url).toBe(gammaReport.original_post_url);
|
||||||
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body);
|
expect(betaReport.original_post_body).toBe(gammaReport.original_post_body);
|
||||||
expect(betaReport.reason).toBe(alphaReport.reason);
|
expect(betaReport.reason).toBe(gammaReport.reason);
|
||||||
await unfollowRemotes(alpha);
|
await unfollowRemotes(alpha);
|
||||||
|
|
||||||
|
// Report was federated to poster's instance
|
||||||
|
let alphaReport = (await waitUntil(
|
||||||
|
() =>
|
||||||
|
listPostReports(alpha).then(p =>
|
||||||
|
p.post_reports.find(
|
||||||
|
r =>
|
||||||
|
r.post_report.original_post_name === gammaReport.original_post_name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
res => !!res,
|
||||||
|
))!.post_report;
|
||||||
|
expect(alphaReport).toBeDefined();
|
||||||
|
expect(alphaReport.resolved).toBe(false);
|
||||||
|
expect(alphaReport.original_post_name).toBe(gammaReport.original_post_name);
|
||||||
|
//expect(alphaReport.original_post_url).toBe(gammaReport.original_post_url);
|
||||||
|
expect(alphaReport.original_post_body).toBe(gammaReport.original_post_body);
|
||||||
|
expect(alphaReport.reason).toBe(gammaReport.reason);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Fetch post via redirect", async () => {
|
test("Fetch post via redirect", async () => {
|
||||||
|
@ -731,7 +760,7 @@ test("Block post that contains banned URL", async () => {
|
||||||
|
|
||||||
await epsilon.editSite(editSiteForm);
|
await epsilon.editSite(editSiteForm);
|
||||||
|
|
||||||
await delay(500);
|
await delay();
|
||||||
|
|
||||||
if (!betaCommunity) {
|
if (!betaCommunity) {
|
||||||
throw "Missing beta community";
|
throw "Missing beta community";
|
||||||
|
@ -745,3 +774,23 @@ test("Block post that contains banned URL", async () => {
|
||||||
editSiteForm.blocked_urls = [];
|
editSiteForm.blocked_urls = [];
|
||||||
await epsilon.editSite(editSiteForm);
|
await epsilon.editSite(editSiteForm);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Fetch post with redirect", async () => {
|
||||||
|
let alphaPost = await createPost(alpha, betaCommunity!.community.id);
|
||||||
|
expect(alphaPost.post_view.post).toBeDefined();
|
||||||
|
|
||||||
|
// beta fetches from alpha as usual
|
||||||
|
let betaPost = await resolvePost(beta, alphaPost.post_view.post);
|
||||||
|
expect(betaPost.post).toBeDefined();
|
||||||
|
|
||||||
|
// gamma fetches from beta, and gets redirected to alpha
|
||||||
|
let gammaPost = await resolvePost(gamma, betaPost.post!.post);
|
||||||
|
expect(gammaPost.post).toBeDefined();
|
||||||
|
|
||||||
|
// fetch remote object from local url, which redirects to the original url
|
||||||
|
let form: ResolveObject = {
|
||||||
|
q: `http://lemmy-gamma:8561/post/${gammaPost.post!.post.id}`,
|
||||||
|
};
|
||||||
|
let gammaPost2 = await gamma.resolveObject(form);
|
||||||
|
expect(gammaPost2.post).toBeDefined();
|
||||||
|
});
|
||||||
|
|
|
@ -21,9 +21,7 @@ beforeAll(async () => {
|
||||||
recipient_id = 3;
|
recipient_id = 3;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(unfollows);
|
||||||
unfollows();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Create a private message", async () => {
|
test("Create a private message", async () => {
|
||||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||||
|
|
|
@ -81,21 +81,24 @@ import { ListingType } from "lemmy-js-client/dist/types/ListingType";
|
||||||
|
|
||||||
export const fetchFunction = fetch;
|
export const fetchFunction = fetch;
|
||||||
export const imageFetchLimit = 50;
|
export const imageFetchLimit = 50;
|
||||||
|
export const sampleImage =
|
||||||
|
"https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg";
|
||||||
|
export const sampleSite = "https://yahoo.com";
|
||||||
|
|
||||||
export let alphaUrl = "http://127.0.0.1:8541";
|
export const alphaUrl = "http://127.0.0.1:8541";
|
||||||
export let betaUrl = "http://127.0.0.1:8551";
|
export const betaUrl = "http://127.0.0.1:8551";
|
||||||
export let gammaUrl = "http://127.0.0.1:8561";
|
export const gammaUrl = "http://127.0.0.1:8561";
|
||||||
export let deltaUrl = "http://127.0.0.1:8571";
|
export const deltaUrl = "http://127.0.0.1:8571";
|
||||||
export let epsilonUrl = "http://127.0.0.1:8581";
|
export const epsilonUrl = "http://127.0.0.1:8581";
|
||||||
|
|
||||||
export let alpha = new LemmyHttp(alphaUrl, { fetchFunction });
|
export const alpha = new LemmyHttp(alphaUrl, { fetchFunction });
|
||||||
export let alphaImage = new LemmyHttp(alphaUrl);
|
export const alphaImage = new LemmyHttp(alphaUrl);
|
||||||
export let beta = new LemmyHttp(betaUrl, { fetchFunction });
|
export const beta = new LemmyHttp(betaUrl, { fetchFunction });
|
||||||
export let gamma = new LemmyHttp(gammaUrl, { fetchFunction });
|
export const gamma = new LemmyHttp(gammaUrl, { fetchFunction });
|
||||||
export let delta = new LemmyHttp(deltaUrl, { fetchFunction });
|
export const delta = new LemmyHttp(deltaUrl, { fetchFunction });
|
||||||
export let epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
|
export const epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
|
||||||
|
|
||||||
export let betaAllowedInstances = [
|
export const betaAllowedInstances = [
|
||||||
"lemmy-alpha",
|
"lemmy-alpha",
|
||||||
"lemmy-gamma",
|
"lemmy-gamma",
|
||||||
"lemmy-delta",
|
"lemmy-delta",
|
||||||
|
@ -180,6 +183,10 @@ export async function setupLogins() {
|
||||||
];
|
];
|
||||||
await gamma.editSite(editSiteForm);
|
await gamma.editSite(editSiteForm);
|
||||||
|
|
||||||
|
// Setup delta allowed instance
|
||||||
|
editSiteForm.allowed_instances = ["lemmy-beta"];
|
||||||
|
await delta.editSite(editSiteForm);
|
||||||
|
|
||||||
// Create the main alpha/beta communities
|
// Create the main alpha/beta communities
|
||||||
// Ignore thrown errors of duplicates
|
// Ignore thrown errors of duplicates
|
||||||
try {
|
try {
|
||||||
|
@ -190,7 +197,7 @@ export async function setupLogins() {
|
||||||
// (because last_successful_id is set to current id when federation to an instance is first started)
|
// (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
|
// only needed the first time so do in this try
|
||||||
await delay(10_000);
|
await delay(10_000);
|
||||||
} catch (_) {
|
} catch {
|
||||||
console.log("Communities already exist");
|
console.log("Communities already exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,6 +210,7 @@ export async function createPost(
|
||||||
// use example.com for consistent title and embed description
|
// use example.com for consistent title and embed description
|
||||||
name: string = randomString(5),
|
name: string = randomString(5),
|
||||||
alt_text = randomString(10),
|
alt_text = randomString(10),
|
||||||
|
custom_thumbnail: string | undefined = undefined,
|
||||||
): Promise<PostResponse> {
|
): Promise<PostResponse> {
|
||||||
let form: CreatePost = {
|
let form: CreatePost = {
|
||||||
name,
|
name,
|
||||||
|
@ -210,6 +218,7 @@ export async function createPost(
|
||||||
body,
|
body,
|
||||||
alt_text,
|
alt_text,
|
||||||
community_id,
|
community_id,
|
||||||
|
custom_thumbnail,
|
||||||
};
|
};
|
||||||
return api.createPost(form);
|
return api.createPost(form);
|
||||||
}
|
}
|
||||||
|
@ -226,6 +235,21 @@ export async function editPost(
|
||||||
return api.editPost(form);
|
return api.editPost(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createPostWithThumbnail(
|
||||||
|
api: LemmyHttp,
|
||||||
|
community_id: number,
|
||||||
|
url: string,
|
||||||
|
custom_thumbnail: string,
|
||||||
|
): Promise<PostResponse> {
|
||||||
|
let form: CreatePost = {
|
||||||
|
name: randomString(10),
|
||||||
|
url,
|
||||||
|
community_id,
|
||||||
|
custom_thumbnail,
|
||||||
|
};
|
||||||
|
return api.createPost(form);
|
||||||
|
}
|
||||||
|
|
||||||
export async function deletePost(
|
export async function deletePost(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
deleted: boolean,
|
deleted: boolean,
|
||||||
|
@ -340,10 +364,13 @@ export async function getUnreadCount(
|
||||||
return api.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 = {
|
let form: GetReplies = {
|
||||||
sort: "New",
|
sort: "New",
|
||||||
unread_only: false,
|
unread_only,
|
||||||
};
|
};
|
||||||
return api.getReplies(form);
|
return api.getReplies(form);
|
||||||
}
|
}
|
||||||
|
@ -392,13 +419,13 @@ export async function banPersonFromSite(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
person_id: number,
|
person_id: number,
|
||||||
ban: boolean,
|
ban: boolean,
|
||||||
remove_data: boolean,
|
remove_or_restore_data: boolean,
|
||||||
): Promise<BanPersonResponse> {
|
): Promise<BanPersonResponse> {
|
||||||
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
|
||||||
let form: BanPerson = {
|
let form: BanPerson = {
|
||||||
person_id,
|
person_id,
|
||||||
ban,
|
ban,
|
||||||
remove_data,
|
remove_or_restore_data,
|
||||||
};
|
};
|
||||||
return api.banPerson(form);
|
return api.banPerson(form);
|
||||||
}
|
}
|
||||||
|
@ -407,13 +434,13 @@ export async function banPersonFromCommunity(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
person_id: number,
|
person_id: number,
|
||||||
community_id: number,
|
community_id: number,
|
||||||
remove_data: boolean,
|
remove_or_restore_data: boolean,
|
||||||
ban: boolean,
|
ban: boolean,
|
||||||
): Promise<BanFromCommunityResponse> {
|
): Promise<BanFromCommunityResponse> {
|
||||||
let form: BanFromCommunity = {
|
let form: BanFromCommunity = {
|
||||||
person_id,
|
person_id,
|
||||||
community_id,
|
community_id,
|
||||||
remove_data: remove_data,
|
remove_or_restore_data,
|
||||||
ban,
|
ban,
|
||||||
};
|
};
|
||||||
return api.banFromCommunity(form);
|
return api.banFromCommunity(form);
|
||||||
|
@ -663,7 +690,7 @@ export async function saveUserSettingsBio(
|
||||||
blur_nsfw: false,
|
blur_nsfw: false,
|
||||||
auto_expand: true,
|
auto_expand: true,
|
||||||
theme: "darkly",
|
theme: "darkly",
|
||||||
default_sort_type: "Active",
|
default_post_sort_type: "Active",
|
||||||
default_listing_type: "All",
|
default_listing_type: "All",
|
||||||
interface_language: "en",
|
interface_language: "en",
|
||||||
show_avatars: true,
|
show_avatars: true,
|
||||||
|
@ -676,14 +703,14 @@ export async function saveUserSettingsBio(
|
||||||
export async function saveUserSettingsFederated(
|
export async function saveUserSettingsFederated(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
): Promise<SuccessResponse> {
|
): Promise<SuccessResponse> {
|
||||||
let avatar = "https://image.flaticon.com/icons/png/512/35/35896.png";
|
let avatar = sampleImage;
|
||||||
let banner = "https://image.flaticon.com/icons/png/512/36/35896.png";
|
let banner = sampleImage;
|
||||||
let bio = "a changed bio";
|
let bio = "a changed bio";
|
||||||
let form: SaveUserSettings = {
|
let form: SaveUserSettings = {
|
||||||
show_nsfw: false,
|
show_nsfw: false,
|
||||||
blur_nsfw: true,
|
blur_nsfw: true,
|
||||||
auto_expand: false,
|
auto_expand: false,
|
||||||
default_sort_type: "Hot",
|
default_post_sort_type: "Hot",
|
||||||
default_listing_type: "All",
|
default_listing_type: "All",
|
||||||
interface_language: "",
|
interface_language: "",
|
||||||
avatar,
|
avatar,
|
||||||
|
@ -743,6 +770,7 @@ export async function unfollowRemotes(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
remoteFollowed.map(cu => followCommunity(api, false, cu.community.id)),
|
remoteFollowed.map(cu => followCommunity(api, false, cu.community.id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let siteRes = await getSite(api);
|
let siteRes = await getSite(api);
|
||||||
return siteRes;
|
return siteRes;
|
||||||
}
|
}
|
||||||
|
@ -871,15 +899,17 @@ export async function deleteAllImages(api: LemmyHttp) {
|
||||||
const imagesRes = await api.listAllMedia({
|
const imagesRes = await api.listAllMedia({
|
||||||
limit: imageFetchLimit,
|
limit: imageFetchLimit,
|
||||||
});
|
});
|
||||||
imagesRes.images;
|
Promise.all(
|
||||||
|
imagesRes.images
|
||||||
for (const image of imagesRes.images) {
|
.map(image => {
|
||||||
const form: DeleteImage = {
|
const form: DeleteImage = {
|
||||||
token: image.pictrs_delete_token,
|
token: image.local_image.pictrs_delete_token,
|
||||||
filename: image.pictrs_alias,
|
filename: image.local_image.pictrs_alias,
|
||||||
};
|
};
|
||||||
await api.deleteImage(form);
|
return form;
|
||||||
}
|
})
|
||||||
|
.map(form => api.deleteImage(form)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unfollows() {
|
export async function unfollows() {
|
||||||
|
@ -890,6 +920,24 @@ export async function unfollows() {
|
||||||
unfollowRemotes(delta),
|
unfollowRemotes(delta),
|
||||||
unfollowRemotes(epsilon),
|
unfollowRemotes(epsilon),
|
||||||
]);
|
]);
|
||||||
|
await Promise.all([
|
||||||
|
purgeAllPosts(alpha),
|
||||||
|
purgeAllPosts(beta),
|
||||||
|
purgeAllPosts(gamma),
|
||||||
|
purgeAllPosts(delta),
|
||||||
|
purgeAllPosts(epsilon),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function purgeAllPosts(api: LemmyHttp) {
|
||||||
|
// The best way to get all federated items, is to find the posts
|
||||||
|
let res = await api.getPosts({ type_: "All", limit: 50 });
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(new Set(res.posts.map(p => p.post.id)))
|
||||||
|
.map(post_id => api.purgePost({ post_id }))
|
||||||
|
// Ignore errors
|
||||||
|
.map(p => p.catch(e => e)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCommentParentId(comment: Comment): number | undefined {
|
export function getCommentParentId(comment: Comment): number | undefined {
|
||||||
|
|
|
@ -20,11 +20,14 @@ import {
|
||||||
getComments,
|
getComments,
|
||||||
fetchFunction,
|
fetchFunction,
|
||||||
alphaImage,
|
alphaImage,
|
||||||
|
unfollows,
|
||||||
|
saveUserSettingsBio,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
||||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
|
afterAll(unfollows);
|
||||||
|
|
||||||
let apShortname: string;
|
let apShortname: string;
|
||||||
|
|
||||||
|
@ -128,7 +131,11 @@ test("Requests with invalid auth should be treated as unauthenticated", async ()
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create user with Arabic name", 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);
|
let site = await getSite(user);
|
||||||
expect(site.my_user).toBeDefined();
|
expect(site.my_user).toBeDefined();
|
||||||
|
@ -184,10 +191,26 @@ test("Set a new avatar, old avatar is deleted", async () => {
|
||||||
expect(upload2.url).toBeDefined();
|
expect(upload2.url).toBeDefined();
|
||||||
|
|
||||||
let form2 = {
|
let form2 = {
|
||||||
avatar: upload1.url,
|
avatar: upload2.url,
|
||||||
};
|
};
|
||||||
await saveUserSettings(alpha, form2);
|
await saveUserSettings(alpha, form2);
|
||||||
// make sure only the new avatar is kept
|
// make sure only the new avatar is kept
|
||||||
const listMediaRes2 = await alphaImage.listMedia();
|
const listMediaRes2 = await alphaImage.listMedia();
|
||||||
expect(listMediaRes2.images.length).toBe(1);
|
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 %}
|
{%- endif %}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
|
||||||
|
{%- if github -%}
|
||||||
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
|
||||||
{% raw %}\n{% endraw -%}
|
{% raw %}\n{% endraw -%}
|
||||||
## New Contributors
|
## New Contributors
|
||||||
|
@ -36,6 +37,7 @@ body = """
|
||||||
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
{% if version %}
|
{% if version %}
|
||||||
{% if previous.version %}
|
{% if previous.version %}
|
||||||
|
@ -70,6 +72,7 @@ commit_preprocessors = [
|
||||||
# remove issue numbers from commits
|
# remove issue numbers from commits
|
||||||
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
|
{ 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 changes from being skipped due to matching a skipping commit_parser
|
||||||
protect_breaking_commits = false
|
protect_breaking_commits = false
|
||||||
# filter out the commits that are not matched by commit parsers
|
# filter out the commits that are not matched by commit parsers
|
||||||
|
|
|
@ -47,9 +47,10 @@
|
||||||
#
|
#
|
||||||
# To be removed in 0.20
|
# To be removed in 0.20
|
||||||
cache_external_link_previews: true
|
cache_external_link_previews: true
|
||||||
# Specifies how to handle remote images, so that users don't have to connect directly to remote servers.
|
# Specifies how to handle remote images, so that users don't have to connect directly to remote
|
||||||
|
# servers.
|
||||||
image_mode:
|
image_mode:
|
||||||
# Leave images unchanged, don't generate any local thumbnails for post urls. Instead the the
|
# Leave images unchanged, don't generate any local thumbnails for post urls. Instead the
|
||||||
# Opengraph image is directly returned as thumbnail
|
# Opengraph image is directly returned as thumbnail
|
||||||
"None"
|
"None"
|
||||||
|
|
||||||
|
@ -64,10 +65,11 @@
|
||||||
|
|
||||||
# or
|
# or
|
||||||
|
|
||||||
# If enabled, all images from remote domains are rewritten to pass through `/api/v3/image_proxy`,
|
# If enabled, all images from remote domains are rewritten to pass through
|
||||||
# including embedded images in markdown. Images are stored temporarily in pict-rs for caching.
|
# `/api/v3/image_proxy`, including embedded images in markdown. Images are stored temporarily
|
||||||
# This improves privacy as users don't expose their IP to untrusted servers, and decreases load
|
# in pict-rs for caching. This improves privacy as users don't expose their IP to untrusted
|
||||||
# on other servers. However it increases bandwidth use for the local server.
|
# servers, and decreases load on other servers. However it increases bandwidth use for the
|
||||||
|
# local server.
|
||||||
#
|
#
|
||||||
# Requires pict-rs 0.5
|
# Requires pict-rs 0.5
|
||||||
"ProxyAllImages"
|
"ProxyAllImages"
|
||||||
|
@ -106,10 +108,12 @@
|
||||||
port: 8536
|
port: 8536
|
||||||
# Whether the site is available over TLS. Needs to be true for federation to work.
|
# Whether the site is available over TLS. Needs to be true for federation to work.
|
||||||
tls_enabled: true
|
tls_enabled: true
|
||||||
# The number of activitypub federation workers that can be in-flight concurrently
|
federation: {
|
||||||
worker_count: 0
|
# Limit to the number of concurrent outgoing federation requests per target instance.
|
||||||
# The number of activitypub federation retry workers that can be in-flight concurrently
|
# Set this to a higher value than 1 (e.g. 6) only if you have a huge instance (>10 activities
|
||||||
retry_count: 0
|
# per second) and if a receiving instance is not keeping up.
|
||||||
|
concurrent_sends_per_instance: 1
|
||||||
|
}
|
||||||
prometheus: {
|
prometheus: {
|
||||||
bind: "127.0.0.1"
|
bind: "127.0.0.1"
|
||||||
port: 10002
|
port: 10002
|
||||||
|
|
|
@ -33,13 +33,14 @@ anyhow = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
wav = "1.0.0"
|
hound = "3.5.1"
|
||||||
sitemap-rs = "0.2.1"
|
sitemap-rs = "0.2.1"
|
||||||
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
|
totp-rs = { version = "5.6.0", features = ["gen_secret", "otpauth"] }
|
||||||
actix-web-httpauth = "0.8.1"
|
actix-web-httpauth = "0.8.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
elementtree = "1.2.3"
|
elementtree = "1.2.3"
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
|
lemmy_api_crud = { workspace = true }
|
||||||
|
|
|
@ -9,15 +9,20 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn distinguish_comment(
|
pub async fn distinguish_comment(
|
||||||
data: Json<DistinguishComment>,
|
data: Json<DistinguishComment>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommentResponse>> {
|
||||||
let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None).await?;
|
let orig_comment = CommentView::read(
|
||||||
|
&mut context.pool(),
|
||||||
|
data.comment_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
@ -52,7 +57,7 @@ pub async fn distinguish_comment(
|
||||||
let comment_view = CommentView::read(
|
let comment_view = CommentView::read(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
data.comment_id,
|
data.comment_id,
|
||||||
Some(local_user_view.person.id),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ use lemmy_db_schema::{
|
||||||
traits::Likeable,
|
traits::Likeable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -25,7 +25,7 @@ pub async fn like_comment(
|
||||||
data: Json<CreateCommentLike>,
|
data: Json<CreateCommentLike>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommentResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
let mut recipient_ids = Vec::<LocalUserId>::new();
|
||||||
|
@ -35,7 +35,12 @@ pub async fn like_comment(
|
||||||
check_bot_account(&local_user_view.person)?;
|
check_bot_account(&local_user_view.person)?;
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
let orig_comment = CommentView::read(
|
||||||
|
&mut context.pool(),
|
||||||
|
comment_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
@ -46,7 +51,7 @@ pub async fn like_comment(
|
||||||
|
|
||||||
// Add parent poster or commenter to recipients
|
// Add parent poster or commenter to recipients
|
||||||
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
|
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
|
||||||
if let Ok(reply) = comment_reply {
|
if let Ok(Some(reply)) = comment_reply {
|
||||||
let recipient_id = reply.recipient_id;
|
let recipient_id = reply.recipient_id;
|
||||||
if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
|
if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
utils::is_mod_or_admin,
|
utils::is_mod_or_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists likes for a comment
|
/// Lists likes for a comment
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -13,13 +13,14 @@ pub async fn list_comment_likes(
|
||||||
data: Query<ListCommentLikes>,
|
data: Query<ListCommentLikes>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListCommentLikesResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListCommentLikesResponse>> {
|
||||||
let comment_view = CommentView::read(
|
let comment_view = CommentView::read(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
data.comment_id,
|
data.comment_id,
|
||||||
Some(local_user_view.person.id),
|
Some(&local_user_view.local_user),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
is_mod_or_admin(
|
is_mod_or_admin(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
|
|
@ -8,14 +8,14 @@ use lemmy_db_schema::{
|
||||||
traits::Saveable,
|
traits::Saveable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn save_comment(
|
pub async fn save_comment(
|
||||||
data: Json<SaveComment>,
|
data: Json<SaveComment>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommentResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommentResponse>> {
|
||||||
let comment_saved_form = CommentSavedForm {
|
let comment_saved_form = CommentSavedForm {
|
||||||
comment_id: data.comment_id,
|
comment_id: data.comment_id,
|
||||||
person_id: local_user_view.person.id,
|
person_id: local_user_view.person.id,
|
||||||
|
@ -32,8 +32,12 @@ pub async fn save_comment(
|
||||||
}
|
}
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let person_id = local_user_view.person.id;
|
let comment_view = CommentView::read(
|
||||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, Some(person_id)).await?;
|
&mut context.pool(),
|
||||||
|
comment_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(Json(CommentResponse {
|
Ok(Json(CommentResponse {
|
||||||
comment_view,
|
comment_view,
|
||||||
|
|
|
@ -19,7 +19,7 @@ use lemmy_db_schema::{
|
||||||
traits::Reportable,
|
traits::Reportable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentReportView, CommentView, LocalUserView};
|
use lemmy_db_views::structs::{CommentReportView, CommentView, LocalUserView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
/// Creates a comment report and notifies the moderators of the community
|
/// Creates a comment report and notifies the moderators of the community
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -27,7 +27,7 @@ pub async fn create_comment_report(
|
||||||
data: Json<CreateCommentReport>,
|
data: Json<CreateCommentReport>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommentReportResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommentReportResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let reason = data.reason.trim().to_string();
|
let reason = data.reason.trim().to_string();
|
||||||
|
@ -35,7 +35,12 @@ pub async fn create_comment_report(
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
let comment_view = CommentView::read(
|
||||||
|
&mut context.pool(),
|
||||||
|
comment_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
check_community_user_action(
|
check_community_user_action(
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
utils::check_community_mod_of_any_or_admin_action,
|
utils::check_community_mod_of_any_or_admin_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView};
|
use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists comment reports for a community if an id is supplied
|
/// Lists comment reports for a community if an id is supplied
|
||||||
/// or returns all comment reports for communities a user moderates
|
/// or returns all comment reports for communities a user moderates
|
||||||
|
@ -14,7 +14,7 @@ pub async fn list_comment_reports(
|
||||||
data: Query<ListCommentReports>,
|
data: Query<ListCommentReports>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListCommentReportsResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListCommentReportsResponse>> {
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
|
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
|
||||||
use lemmy_db_views::structs::{CommentReportView, LocalUserView};
|
use lemmy_db_views::structs::{CommentReportView, LocalUserView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
/// Resolves or unresolves a comment report and notifies the moderators of the community
|
/// Resolves or unresolves a comment report and notifies the moderators of the community
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -14,7 +14,7 @@ pub async fn resolve_comment_report(
|
||||||
data: Json<ResolveCommentReport>,
|
data: Json<ResolveCommentReport>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommentReportResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommentReportResponse>> {
|
||||||
let report_id = data.report_id;
|
let report_id = data.report_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
|
|
|
@ -9,20 +9,21 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
|
local_user::LocalUser,
|
||||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Joinable},
|
traits::{Crud, Joinable},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn add_mod_to_community(
|
pub async fn add_mod_to_community(
|
||||||
data: Json<AddModToCommunity>,
|
data: Json<AddModToCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<AddModToCommunityResponse>, LemmyError> {
|
) -> LemmyResult<Json<AddModToCommunityResponse>> {
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
|
||||||
// Verify that only mods or admins can add mod
|
// Verify that only mods or admins can add mod
|
||||||
|
@ -33,9 +34,30 @@ pub async fn add_mod_to_community(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.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?;
|
let community = Community::read(&mut context.pool(), community_id).await?;
|
||||||
|
|
||||||
|
// If user is admin and community is remote, explicitly check that he is a
|
||||||
|
// moderator. This is necessary because otherwise the action would be rejected
|
||||||
|
// by the community's home instance.
|
||||||
if local_user_view.local_user.admin && !community.local {
|
if local_user_view.local_user.admin && !community.local {
|
||||||
Err(LemmyErrorType::NotAModerator)?
|
CommunityModeratorView::check_is_community_moderator(
|
||||||
|
&mut context.pool(),
|
||||||
|
community.id,
|
||||||
|
local_user_view.person.id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update in local database
|
// Update in local database
|
||||||
|
|
|
@ -4,7 +4,11 @@ use lemmy_api_common::{
|
||||||
community::{BanFromCommunity, BanFromCommunityResponse},
|
community::{BanFromCommunity, BanFromCommunityResponse},
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community},
|
utils::{
|
||||||
|
check_community_mod_action,
|
||||||
|
check_expire_time,
|
||||||
|
remove_or_restore_user_data_in_community,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -14,6 +18,7 @@ use lemmy_db_schema::{
|
||||||
CommunityPersonBan,
|
CommunityPersonBan,
|
||||||
CommunityPersonBanForm,
|
CommunityPersonBanForm,
|
||||||
},
|
},
|
||||||
|
local_user::LocalUser,
|
||||||
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
||||||
},
|
},
|
||||||
traits::{Bannable, Crud, Followable},
|
traits::{Bannable, Crud, Followable},
|
||||||
|
@ -21,7 +26,7 @@ use lemmy_db_schema::{
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
utils::validation::is_valid_body_field,
|
utils::validation::is_valid_body_field,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,9 +35,8 @@ pub async fn ban_from_community(
|
||||||
data: Json<BanFromCommunity>,
|
data: Json<BanFromCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
|
) -> LemmyResult<Json<BanFromCommunityResponse>> {
|
||||||
let banned_person_id = data.person_id;
|
let banned_person_id = data.person_id;
|
||||||
let remove_data = data.remove_data.unwrap_or(false);
|
|
||||||
let expires = check_expire_time(data.expires)?;
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
|
||||||
// Verify that only mods or admins can ban
|
// Verify that only mods or admins can ban
|
||||||
|
@ -43,7 +47,18 @@ pub async fn ban_from_community(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await?;
|
.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 {
|
let community_user_ban_form = CommunityPersonBanForm {
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
|
@ -73,9 +88,16 @@ pub async fn ban_from_community(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove/Restore their data if that's desired
|
// Remove/Restore their data if that's desired
|
||||||
if remove_data {
|
if data.remove_or_restore_data.unwrap_or(false) {
|
||||||
remove_user_data_in_community(data.community_id, banned_person_id, &mut context.pool()).await?;
|
let remove_data = data.ban;
|
||||||
}
|
remove_or_restore_user_data_in_community(
|
||||||
|
data.community_id,
|
||||||
|
banned_person_id,
|
||||||
|
remove_data,
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let form = ModBanFromCommunityForm {
|
let form = ModBanFromCommunityForm {
|
||||||
|
|
|
@ -14,14 +14,14 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommunityView;
|
use lemmy_db_views_actor::structs::CommunityView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn block_community(
|
pub async fn block_community(
|
||||||
data: Json<BlockCommunity>,
|
data: Json<BlockCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<BlockCommunityResponse>, LemmyError> {
|
) -> LemmyResult<Json<BlockCommunityResponse>> {
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let community_block_form = CommunityBlockForm {
|
let community_block_form = CommunityBlockForm {
|
||||||
|
@ -50,8 +50,13 @@ pub async fn block_community(
|
||||||
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
|
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let community_view =
|
let community_view = CommunityView::read(
|
||||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?;
|
&mut context.pool(),
|
||||||
|
community_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::FollowCommunity(
|
SendActivityData::FollowCommunity(
|
||||||
|
|
|
@ -15,14 +15,14 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommunityView;
|
use lemmy_db_views_actor::structs::CommunityView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn follow_community(
|
pub async fn follow_community(
|
||||||
data: Json<FollowCommunity>,
|
data: Json<FollowCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
let mut community_follower_form = CommunityFollowerForm {
|
let mut community_follower_form = CommunityFollowerForm {
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
|
@ -60,9 +60,14 @@ pub async fn follow_community(
|
||||||
}
|
}
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let person_id = local_user_view.person.id;
|
let community_view = CommunityView::read(
|
||||||
let community_view =
|
&mut context.pool(),
|
||||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?;
|
community_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
|
||||||
|
|
||||||
Ok(Json(CommunityResponse {
|
Ok(Json(CommunityResponse {
|
||||||
|
|
|
@ -15,14 +15,14 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn hide_community(
|
pub async fn hide_community(
|
||||||
data: Json<HideCommunity>,
|
data: Json<HideCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Verify its a admin (only admin can hide or unhide it)
|
// Verify its a admin (only admin can hide or unhide it)
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ use lemmy_db_schema::{
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
location_info,
|
location_info,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ pub async fn transfer_community(
|
||||||
data: Json<TransferCommunity>,
|
data: Json<TransferCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetCommunityResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetCommunityResponse>> {
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let mut community_mods =
|
let mut community_mods =
|
||||||
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
||||||
|
@ -76,16 +76,16 @@ pub async fn transfer_community(
|
||||||
ModTransferCommunity::create(&mut context.pool(), &form).await?;
|
ModTransferCommunity::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let person_id = local_user_view.person.id;
|
let community_view = CommunityView::read(
|
||||||
let community_view =
|
&mut context.pool(),
|
||||||
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
|
community_id,
|
||||||
.await
|
Some(&local_user_view.local_user),
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id)
|
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
|
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
Ok(Json(GetCommunityResponse {
|
Ok(Json(GetCommunityResponse {
|
||||||
|
|
|
@ -26,7 +26,7 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||||
utils::slurs::check_slurs,
|
utils::slurs::check_slurs,
|
||||||
};
|
};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
@ -44,31 +44,37 @@ pub mod site;
|
||||||
pub mod sitemap;
|
pub mod sitemap;
|
||||||
|
|
||||||
/// Converts the captcha to a base64 encoded wav audio file
|
/// Converts the captcha to a base64 encoded wav audio file
|
||||||
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyError> {
|
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
|
||||||
let letters = captcha.as_wav();
|
let letters = captcha.as_wav();
|
||||||
|
|
||||||
// Decode each wav file, concatenate the samples
|
// Decode each wav file, concatenate the samples
|
||||||
let mut concat_samples: Vec<i16> = Vec::new();
|
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 {
|
for letter in letters {
|
||||||
let mut cursor = Cursor::new(letter.unwrap_or_default());
|
let mut cursor = Cursor::new(letter.unwrap_or_default());
|
||||||
let (header, samples) = wav::read(&mut cursor)?;
|
let reader = hound::WavReader::new(&mut cursor)?;
|
||||||
any_header = Some(header);
|
any_header = Some(reader.spec());
|
||||||
if let Some(samples16) = samples.as_sixteen() {
|
let samples16 = reader
|
||||||
|
.into_samples::<i16>()
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
concat_samples.extend(samples16);
|
concat_samples.extend(samples16);
|
||||||
} else {
|
|
||||||
Err(LemmyErrorType::CouldntCreateAudioCaptcha)?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the concatenated result as a wav file
|
// Encode the concatenated result as a wav file
|
||||||
let mut output_buffer = Cursor::new(vec![]);
|
let mut output_buffer = Cursor::new(vec![]);
|
||||||
if let Some(header) = any_header {
|
if let Some(header) = any_header {
|
||||||
wav::write(
|
let mut writer = hound::WavWriter::new(&mut output_buffer, header)
|
||||||
header,
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
&wav::BitDepth::Sixteen(concat_samples),
|
let mut writer16 = writer.get_i16_writer(concat_samples.len() as u32);
|
||||||
&mut output_buffer,
|
for sample in concat_samples {
|
||||||
)
|
writer16.write_sample(sample);
|
||||||
|
}
|
||||||
|
writer16
|
||||||
|
.flush()
|
||||||
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
|
writer
|
||||||
|
.finalize()
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
|
||||||
|
|
||||||
Ok(base64.encode(output_buffer.into_inner()))
|
Ok(base64.encode(output_buffer.into_inner()))
|
||||||
|
@ -78,7 +84,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyEr
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check size of report
|
/// Check size of report
|
||||||
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
|
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> LemmyResult<()> {
|
||||||
let slur_regex = &local_site_to_slur_regex(local_site);
|
let slur_regex = &local_site_to_slur_regex(local_site);
|
||||||
|
|
||||||
check_slurs(reason, slur_regex)?;
|
check_slurs(reason, slur_regex)?;
|
||||||
|
@ -91,7 +97,7 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_auth_token(req: &HttpRequest) -> Result<Option<String>, LemmyError> {
|
pub fn read_auth_token(req: &HttpRequest) -> LemmyResult<Option<String>> {
|
||||||
// Try reading jwt from auth header
|
// Try reading jwt from auth header
|
||||||
if let Ok(header) = Authorization::<Bearer>::parse(req) {
|
if let Ok(header) = Authorization::<Bearer>::parse(req) {
|
||||||
Ok(Some(header.as_ref().token().to_string()))
|
Ok(Some(header.as_ref().token().to_string()))
|
||||||
|
@ -135,7 +141,7 @@ pub(crate) fn generate_totp_2fa_secret() -> String {
|
||||||
Secret::generate_secret().to_string()
|
Secret::generate_secret().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> Result<TOTP, LemmyError> {
|
fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> LemmyResult<TOTP> {
|
||||||
let sec = Secret::Raw(secret.as_bytes().to_vec());
|
let sec = Secret::Raw(secret.as_bytes().to_vec());
|
||||||
let sec_bytes = sec
|
let sec_bytes = sec
|
||||||
.to_bytes()
|
.to_bytes()
|
||||||
|
@ -166,7 +172,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
||||||
target: &Person,
|
target: &Person,
|
||||||
ban: bool,
|
ban: bool,
|
||||||
reason: &Option<String>,
|
reason: &Option<String>,
|
||||||
remove_data: &Option<bool>,
|
remove_or_restore_data: &Option<bool>,
|
||||||
expires: &Option<i64>,
|
expires: &Option<i64>,
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
|
@ -224,7 +230,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
||||||
person_id: target.id,
|
person_id: target.id,
|
||||||
ban,
|
ban,
|
||||||
reason: reason.clone(),
|
reason: reason.clone(),
|
||||||
remove_data: *remove_data,
|
remove_or_restore_data: *remove_or_restore_data,
|
||||||
expires: *expires,
|
expires: *expires,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -248,7 +254,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
|
||||||
pub async fn local_user_view_from_jwt(
|
pub async fn local_user_view_from_jwt(
|
||||||
jwt: &str,
|
jwt: &str,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<LocalUserView, LemmyError> {
|
) -> LemmyResult<LocalUserView> {
|
||||||
let local_user_id = Claims::validate(jwt, context)
|
let local_user_id = Claims::validate(jwt, context)
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
||||||
|
@ -259,8 +265,6 @@ pub async fn local_user_view_from_jwt(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -13,23 +13,33 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn add_admin(
|
pub async fn add_admin(
|
||||||
data: Json<AddAdmin>,
|
data: Json<AddAdmin>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<AddAdminResponse>, LemmyError> {
|
) -> LemmyResult<Json<AddAdminResponse>> {
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
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
|
// Make sure that the person_id added is local
|
||||||
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
|
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::ObjectNotLocal)?;
|
.map_err(|_| LemmyErrorType::ObjectNotLocal)?;
|
||||||
|
|
||||||
let added_admin = LocalUser::update(
|
LocalUser::update(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
added_local_user.local_user.id,
|
added_local_user.local_user.id,
|
||||||
&LocalUserUpdateForm {
|
&LocalUserUpdateForm {
|
||||||
|
@ -43,7 +53,7 @@ pub async fn add_admin(
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let form = ModAddForm {
|
let form = ModAddForm {
|
||||||
mod_person_id: local_user_view.person.id,
|
mod_person_id: local_user_view.person.id,
|
||||||
other_person_id: added_admin.person_id,
|
other_person_id: added_local_user.person.id,
|
||||||
removed: Some(!data.added),
|
removed: Some(!data.added),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,11 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{BanPerson, BanPersonResponse},
|
person::{BanPerson, BanPersonResponse},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_expire_time, is_admin, remove_user_data},
|
utils::{check_expire_time, is_admin, remove_user_data, restore_user_data},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
local_user::LocalUser,
|
||||||
login_token::LoginToken,
|
login_token::LoginToken,
|
||||||
moderator::{ModBan, ModBanForm},
|
moderator::{ModBan, ModBanForm},
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
|
@ -18,7 +19,7 @@ use lemmy_db_schema::{
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
utils::validation::is_valid_body_field,
|
utils::validation::is_valid_body_field,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,11 +28,21 @@ pub async fn ban_from_site(
|
||||||
data: Json<BanPerson>,
|
data: Json<BanPerson>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<BanPersonResponse>, LemmyError> {
|
) -> LemmyResult<Json<BanPersonResponse>> {
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
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)?;
|
let expires = check_expire_time(data.expires)?;
|
||||||
|
|
||||||
|
@ -54,10 +65,13 @@ pub async fn ban_from_site(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove their data if that's desired
|
// Remove their data if that's desired
|
||||||
let remove_data = data.remove_data.unwrap_or(false);
|
if data.remove_or_restore_data.unwrap_or(false) {
|
||||||
if remove_data {
|
if data.ban {
|
||||||
remove_user_data(person.id, &context).await?;
|
remove_user_data(person.id, &context).await?;
|
||||||
|
} else {
|
||||||
|
restore_user_data(person.id, &context).await?;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Mod tables
|
// Mod tables
|
||||||
let form = ModBanForm {
|
let form = ModBanForm {
|
||||||
|
@ -77,7 +91,7 @@ pub async fn ban_from_site(
|
||||||
&person,
|
&person,
|
||||||
data.ban,
|
data.ban,
|
||||||
&data.reason,
|
&data.reason,
|
||||||
&data.remove_data,
|
&data.remove_or_restore_data,
|
||||||
&data.expires,
|
&data.expires,
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
|
@ -88,7 +102,7 @@ pub async fn ban_from_site(
|
||||||
moderator: local_user_view.person,
|
moderator: local_user_view.person,
|
||||||
banned_user: person_view.person.clone(),
|
banned_user: person_view.person.clone(),
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
remove_data: data.remove_data,
|
remove_or_restore_data: data.remove_or_restore_data,
|
||||||
ban: data.ban,
|
ban: data.ban,
|
||||||
expires: data.expires,
|
expires: data.expires,
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn block_person(
|
pub async fn block_person(
|
||||||
data: Json<BlockPerson>,
|
data: Json<BlockPerson>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<BlockPersonResponse>, LemmyError> {
|
) -> LemmyResult<Json<BlockPersonResponse>> {
|
||||||
let target_id = data.person_id;
|
let target_id = data.person_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
|
|
||||||
|
@ -30,8 +30,11 @@ pub async fn block_person(
|
||||||
target_id,
|
target_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
let target_user = LocalUserView::read_person(&mut context.pool(), target_id).await;
|
let target_user = LocalUserView::read_person(&mut context.pool(), target_id)
|
||||||
if target_user.map(|t| t.local_user.admin) == Ok(true) {
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
if target_user.is_some_and(|t| t.local_user.admin) {
|
||||||
Err(LemmyErrorType::CantBlockAdmin)?
|
Err(LemmyErrorType::CantBlockAdmin)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::{local_user::LocalUser, login_token::LoginToken};
|
use lemmy_db_schema::source::{local_user::LocalUser, login_token::LoginToken};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn change_password(
|
pub async fn change_password(
|
||||||
|
@ -19,7 +19,7 @@ pub async fn change_password(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<LoginResponse>, LemmyError> {
|
) -> LemmyResult<Json<LoginResponse>> {
|
||||||
password_length_check(&data.new_password)?;
|
password_length_check(&data.new_password)?;
|
||||||
|
|
||||||
// Make sure passwords match
|
// Make sure passwords match
|
||||||
|
@ -28,11 +28,13 @@ pub async fn change_password(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the old password
|
// Check the old password
|
||||||
let valid: bool = verify(
|
let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted
|
||||||
&data.old_password,
|
{
|
||||||
&local_user_view.local_user.password_encrypted,
|
verify(&data.old_password, password_encrypted).unwrap_or(false)
|
||||||
)
|
} else {
|
||||||
.unwrap_or(false);
|
data.old_password.is_empty()
|
||||||
|
};
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,18 +10,18 @@ use lemmy_db_schema::source::{
|
||||||
login_token::LoginToken,
|
login_token::LoginToken,
|
||||||
password_reset_request::PasswordResetRequest,
|
password_reset_request::PasswordResetRequest,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn change_password_after_reset(
|
pub async fn change_password_after_reset(
|
||||||
data: Json<PasswordChangeAfterReset>,
|
data: Json<PasswordChangeAfterReset>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Fetch the user_id from the token
|
// Fetch the user_id from the token
|
||||||
let token = data.token.clone();
|
let token = data.token.clone();
|
||||||
let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token)
|
let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token)
|
||||||
.await
|
.await?
|
||||||
.map(|p| p.local_user_id)?;
|
.local_user_id;
|
||||||
|
|
||||||
password_length_check(&data.password)?;
|
password_length_check(&data.password)?;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::{build_totp_2fa, generate_totp_2fa_secret};
|
use crate::{build_totp_2fa, generate_totp_2fa_secret};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse};
|
||||||
context::LemmyContext,
|
use lemmy_db_schema::source::{
|
||||||
person::GenerateTotpSecretResponse,
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
sensitive::Sensitive,
|
site::Site,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
|
||||||
|
|
||||||
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
|
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
|
||||||
/// to enable it. This can only be called if 2FA is currently disabled.
|
/// to enable it. This can only be called if 2FA is currently disabled.
|
||||||
|
@ -16,16 +15,15 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||||
pub async fn generate_totp_secret(
|
pub async fn generate_totp_secret(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<GenerateTotpSecretResponse>, LemmyError> {
|
) -> LemmyResult<Json<GenerateTotpSecretResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site = Site::read_local(&mut context.pool()).await?;
|
||||||
|
|
||||||
if local_user_view.local_user.totp_2fa_enabled {
|
if local_user_view.local_user.totp_2fa_enabled {
|
||||||
return Err(LemmyErrorType::TotpAlreadyEnabled)?;
|
return Err(LemmyErrorType::TotpAlreadyEnabled)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let secret = generate_totp_2fa_secret();
|
let secret = generate_totp_2fa_secret();
|
||||||
let secret_url =
|
let secret_url = build_totp_2fa(&site.name, &local_user_view.person.name, &secret)?.get_url();
|
||||||
build_totp_2fa(&site_view.site.name, &local_user_view.person.name, &secret)?.get_url();
|
|
||||||
|
|
||||||
let local_user_form = LocalUserUpdateForm {
|
let local_user_form = LocalUserUpdateForm {
|
||||||
totp_2fa_secret: Some(Some(secret)),
|
totp_2fa_secret: Some(Some(secret)),
|
||||||
|
@ -39,6 +37,6 @@ pub async fn generate_totp_secret(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Json(GenerateTotpSecretResponse {
|
Ok(Json(GenerateTotpSecretResponse {
|
||||||
totp_secret_url: Sensitive::new(secret_url),
|
totp_secret_url: secret_url.into(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,10 @@ use lemmy_db_schema::source::{
|
||||||
captcha_answer::{CaptchaAnswer, CaptchaAnswerForm},
|
captcha_answer::{CaptchaAnswer, CaptchaAnswerForm},
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn get_captcha(context: Data<LemmyContext>) -> Result<HttpResponse, LemmyError> {
|
pub async fn get_captcha(context: Data<LemmyContext>) -> LemmyResult<HttpResponse> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
let mut res = HttpResponseBuilder::new(StatusCode::OK);
|
let mut res = HttpResponseBuilder::new(StatusCode::OK);
|
||||||
res.insert_header(CacheControl(vec![CacheDirective::NoStore]));
|
res.insert_header(CacheControl(vec![CacheDirective::NoStore]));
|
||||||
|
|
|
@ -2,12 +2,12 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin};
|
use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
pub async fn list_banned_users(
|
pub async fn list_banned_users(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<BannedPersonsResponse>, LemmyError> {
|
) -> LemmyResult<Json<BannedPersonsResponse>> {
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::{context::LemmyContext, person::ListLoginsResponse};
|
||||||
use lemmy_db_schema::source::login_token::LoginToken;
|
use lemmy_db_schema::source::login_token::LoginToken;
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
pub async fn list_logins(
|
pub async fn list_logins(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<Vec<LoginToken>>, LemmyError> {
|
) -> LemmyResult<Json<ListLoginsResponse>> {
|
||||||
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
|
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
|
||||||
|
|
||||||
Ok(Json(logins))
|
Ok(Json(ListLoginsResponse { logins }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,18 @@ use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{ListMedia, ListMediaResponse},
|
person::{ListMedia, ListMediaResponse},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::images::LocalImage;
|
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_utils::error::LemmyResult;
|
||||||
use lemmy_utils::error::LemmyError;
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_media(
|
pub async fn list_media(
|
||||||
data: Query<ListMedia>,
|
data: Query<ListMedia>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListMediaResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListMediaResponse>> {
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let images = LocalImage::get_all_paged_by_local_user_id(
|
let images = LocalImageView::get_all_paged_by_local_user_id(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
local_user_view.local_user.id,
|
local_user_view.local_user.id,
|
||||||
page,
|
page,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{check_totp_2fa_valid, local_user::check_email_verified};
|
use crate::check_totp_2fa_valid;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
web::{Data, Json},
|
web::{Data, Json},
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
|
@ -8,36 +8,30 @@ use lemmy_api_common::{
|
||||||
claims::Claims,
|
claims::Claims,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{Login, LoginResponse},
|
person::{Login, LoginResponse},
|
||||||
utils::check_user_valid,
|
utils::{check_email_verified, check_registration_application, check_user_valid},
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
source::{local_site::LocalSite, registration_application::RegistrationApplication},
|
|
||||||
utils::DbPool,
|
|
||||||
RegistrationMode,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
data: Json<Login>,
|
data: Json<Login>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<LoginResponse>, LemmyError> {
|
) -> LemmyResult<Json<LoginResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
|
|
||||||
// Fetch that username / email
|
// Fetch that username / email
|
||||||
let username_or_email = data.username_or_email.clone();
|
let username_or_email = data.username_or_email.clone();
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email)
|
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email).await?;
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::IncorrectLogin)?;
|
|
||||||
|
|
||||||
// Verify the password
|
// Verify the password
|
||||||
let valid: bool = verify(
|
let valid: bool = local_user_view
|
||||||
&data.password,
|
.local_user
|
||||||
&local_user_view.local_user.password_encrypted,
|
.password_encrypted
|
||||||
)
|
.as_ref()
|
||||||
|
.and_then(|password_encrypted| verify(&data.password, password_encrypted).ok())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
|
@ -65,26 +59,3 @@ pub async fn login(
|
||||||
registration_created: false,
|
registration_created: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_registration_application(
|
|
||||||
local_user_view: &LocalUserView,
|
|
||||||
local_site: &LocalSite,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> Result<(), LemmyError> {
|
|
||||||
if (local_site.registration_mode == RegistrationMode::RequireApplication
|
|
||||||
|| local_site.registration_mode == RegistrationMode::Closed)
|
|
||||||
&& !local_user_view.local_user.accepted_application
|
|
||||||
&& !local_user_view.local_user.admin
|
|
||||||
{
|
|
||||||
// Fetch the registration application. If no admin id is present its still pending. Otherwise it
|
|
||||||
// was processed (either accepted or denied).
|
|
||||||
let local_user_id = local_user_view.local_user.id;
|
|
||||||
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?;
|
|
||||||
if registration.admin_id.is_some() {
|
|
||||||
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
|
|
||||||
} else {
|
|
||||||
Err(LemmyErrorType::RegistrationApplicationIsPending)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
|
||||||
|
|
||||||
pub mod add_admin;
|
pub mod add_admin;
|
||||||
pub mod ban_person;
|
pub mod ban_person;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
|
@ -20,15 +17,3 @@ pub mod save_settings;
|
||||||
pub mod update_totp;
|
pub mod update_totp;
|
||||||
pub mod validate_auth;
|
pub mod validate_auth;
|
||||||
pub mod verify_email;
|
pub mod verify_email;
|
||||||
|
|
||||||
/// Check if the user's email is verified if email verification is turned on
|
|
||||||
/// However, skip checking verification if the user is an admin
|
|
||||||
fn check_email_verified(local_user_view: &LocalUserView, site_view: &SiteView) -> LemmyResult<()> {
|
|
||||||
if !local_user_view.local_user.admin
|
|
||||||
&& site_view.local_site.require_email_verification
|
|
||||||
&& !local_user_view.local_user.email_verified
|
|
||||||
{
|
|
||||||
Err(LemmyErrorType::EmailNotVerified)?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,14 +5,14 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::person_mention_view::PersonMentionQuery;
|
use lemmy_db_views_actor::person_mention_view::PersonMentionQuery;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_mentions(
|
pub async fn list_mentions(
|
||||||
data: Query<GetPersonMentions>,
|
data: Query<GetPersonMentions>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetPersonMentionsResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetPersonMentionsResponse>> {
|
||||||
let sort = data.sort;
|
let sort = data.sort;
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
|
|
|
@ -5,14 +5,14 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::comment_reply_view::CommentReplyQuery;
|
use lemmy_db_views_actor::comment_reply_view::CommentReplyQuery;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_replies(
|
pub async fn list_replies(
|
||||||
data: Query<GetReplies>,
|
data: Query<GetReplies>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetRepliesResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetRepliesResponse>> {
|
||||||
let sort = data.sort;
|
let sort = data.sort;
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
|
|
|
@ -6,13 +6,13 @@ use lemmy_db_schema::source::{
|
||||||
private_message::PrivateMessage,
|
private_message::PrivateMessage,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn mark_all_notifications_read(
|
pub async fn mark_all_notifications_read(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetRepliesResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetRepliesResponse>> {
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
|
|
||||||
// Mark all comment_replies as read
|
// Mark all comment_replies as read
|
||||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::PersonMentionView;
|
use lemmy_db_views_actor::structs::PersonMentionView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn mark_person_mention_as_read(
|
pub async fn mark_person_mention_as_read(
|
||||||
data: Json<MarkPersonMentionAsRead>,
|
data: Json<MarkPersonMentionAsRead>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PersonMentionResponse>, LemmyError> {
|
) -> LemmyResult<Json<PersonMentionResponse>> {
|
||||||
let person_mention_id = data.person_mention_id;
|
let person_mention_id = data.person_mention_id;
|
||||||
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;
|
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommentReplyView;
|
use lemmy_db_views_actor::structs::CommentReplyView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn mark_reply_as_read(
|
pub async fn mark_reply_as_read(
|
||||||
data: Json<MarkCommentReplyAsRead>,
|
data: Json<MarkCommentReplyAsRead>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<CommentReplyResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommentReplyResponse>> {
|
||||||
let comment_reply_id = data.comment_reply_id;
|
let comment_reply_id = data.comment_reply_id;
|
||||||
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;
|
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,21 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
|
use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||||
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView};
|
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn unread_count(
|
pub async fn unread_count(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetUnreadCountResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetUnreadCountResponse>> {
|
||||||
let person_id = local_user_view.person.id;
|
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 =
|
let private_messages =
|
||||||
PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?;
|
PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?;
|
||||||
|
|
|
@ -10,14 +10,14 @@ use lemmy_db_views::structs::{
|
||||||
PostReportView,
|
PostReportView,
|
||||||
PrivateMessageReportView,
|
PrivateMessageReportView,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn report_count(
|
pub async fn report_count(
|
||||||
data: Query<GetReportCount>,
|
data: Query<GetReportCount>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetReportCountResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetReportCountResponse>> {
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let admin = local_user_view.local_user.admin;
|
let admin = local_user_view.local_user.admin;
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
use crate::local_user::check_email_verified;
|
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::PasswordReset,
|
person::PasswordReset,
|
||||||
utils::send_password_reset_email,
|
utils::{check_email_verified, send_password_reset_email},
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::password_reset_request::PasswordResetRequest;
|
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn reset_password(
|
pub async fn reset_password(
|
||||||
|
@ -19,17 +17,8 @@ pub async fn reset_password(
|
||||||
let email = data.email.to_lowercase();
|
let email = data.email.to_lowercase();
|
||||||
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
|
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::IncorrectLogin)?;
|
.map_err(|_| LemmyErrorType::IncorrectLogin)?;
|
||||||
|
|
||||||
// Check for too many attempts (to limit potential abuse)
|
|
||||||
let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count(
|
|
||||||
&mut context.pool(),
|
|
||||||
local_user_view.local_user.id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
if recent_resets_count >= 3 {
|
|
||||||
Err(LemmyErrorType::PasswordResetLimitReached)?
|
|
||||||
}
|
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
check_email_verified(&local_user_view, &site_view)?;
|
check_email_verified(&local_user_view, &site_view)?;
|
||||||
|
|
||||||
|
|
|
@ -21,44 +21,49 @@ use lemmy_db_schema::{
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::diesel_option_overwrite,
|
utils::{diesel_string_update, diesel_url_update},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorType},
|
error::{LemmyErrorType, LemmyResult},
|
||||||
utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id},
|
utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id},
|
||||||
};
|
};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn save_user_settings(
|
pub async fn save_user_settings(
|
||||||
data: Json<SaveUserSettings>,
|
data: Json<SaveUserSettings>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
|
|
||||||
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
|
||||||
let url_blocklist = get_url_blocklist(&context).await?;
|
let url_blocklist = get_url_blocklist(&context).await?;
|
||||||
let bio = diesel_option_overwrite(
|
let bio = diesel_string_update(
|
||||||
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?,
|
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 avatar = diesel_url_update(data.avatar.as_deref())?;
|
||||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
replace_image(&avatar, &local_user_view.person.avatar, &context).await?;
|
||||||
let display_name = diesel_option_overwrite(data.display_name.clone());
|
let avatar = proxy_image_link_opt_api(avatar, &context).await?;
|
||||||
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
|
|
||||||
|
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_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 {
|
if let Some(Some(email)) = &email {
|
||||||
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
|
||||||
// if email was changed, check that it is not taken and send verification mail
|
// if email was changed, check that it is not taken and send verification mail
|
||||||
if &previous_email != email {
|
if previous_email.deref() != email {
|
||||||
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
|
LocalUser::check_is_email_taken(&mut context.pool(), email).await?;
|
||||||
return Err(LemmyErrorType::EmailAlreadyExists)?;
|
|
||||||
}
|
|
||||||
send_verification_email(
|
send_verification_email(
|
||||||
&local_user_view,
|
&local_user_view,
|
||||||
email,
|
email,
|
||||||
|
@ -69,7 +74,8 @@ pub async fn save_user_settings(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
|
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None
|
||||||
|
// value
|
||||||
if let Some(email) = &email {
|
if let Some(email) = &email {
|
||||||
if email.is_none() && site_view.local_site.require_email_verification {
|
if email.is_none() && site_view.local_site.require_email_verification {
|
||||||
Err(LemmyErrorType::EmailRequired)?
|
Err(LemmyErrorType::EmailRequired)?
|
||||||
|
@ -94,7 +100,8 @@ pub async fn save_user_settings(
|
||||||
let local_user_id = local_user_view.local_user.id;
|
let local_user_id = local_user_view.local_user.id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let default_listing_type = data.default_listing_type;
|
let default_listing_type = data.default_listing_type;
|
||||||
let default_sort_type = data.default_sort_type;
|
let default_post_sort_type = data.default_post_sort_type;
|
||||||
|
let default_comment_sort_type = data.default_comment_sort_type;
|
||||||
|
|
||||||
let person_form = PersonUpdateForm {
|
let person_form = PersonUpdateForm {
|
||||||
display_name,
|
display_name,
|
||||||
|
@ -123,10 +130,9 @@ pub async fn save_user_settings(
|
||||||
send_notifications_to_email: data.send_notifications_to_email,
|
send_notifications_to_email: data.send_notifications_to_email,
|
||||||
show_nsfw: data.show_nsfw,
|
show_nsfw: data.show_nsfw,
|
||||||
blur_nsfw: data.blur_nsfw,
|
blur_nsfw: data.blur_nsfw,
|
||||||
auto_expand: data.auto_expand,
|
|
||||||
show_bot_accounts: data.show_bot_accounts,
|
show_bot_accounts: data.show_bot_accounts,
|
||||||
show_scores: data.show_scores,
|
default_post_sort_type,
|
||||||
default_sort_type,
|
default_comment_sort_type,
|
||||||
default_listing_type,
|
default_listing_type,
|
||||||
theme: data.theme.clone(),
|
theme: data.theme.clone(),
|
||||||
interface_language: data.interface_language.clone(),
|
interface_language: data.interface_language.clone(),
|
||||||
|
@ -139,11 +145,7 @@ pub async fn save_user_settings(
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ignore errors, because 'no fields updated' will return an error.
|
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;
|
||||||
// https://github.com/LemmyNet/lemmy/issues/4076
|
|
||||||
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form)
|
|
||||||
.await
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
// Update the vote display modes
|
// Update the vote display modes
|
||||||
let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm {
|
let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
|
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Enable or disable two-factor-authentication. The current setting is determined from
|
/// Enable or disable two-factor-authentication. The current setting is determined from
|
||||||
/// [LocalUser.totp_2fa_enabled].
|
/// [LocalUser.totp_2fa_enabled].
|
||||||
|
@ -21,7 +21,7 @@ pub async fn update_totp(
|
||||||
data: Json<UpdateTotp>,
|
data: Json<UpdateTotp>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<UpdateTotpResponse>, LemmyError> {
|
) -> LemmyResult<Json<UpdateTotpResponse>> {
|
||||||
check_totp_2fa_valid(
|
check_totp_2fa_valid(
|
||||||
&local_user_view,
|
&local_user_view,
|
||||||
&Some(data.totp_token.clone()),
|
&Some(data.totp_token.clone()),
|
||||||
|
|
|
@ -4,7 +4,7 @@ use actix_web::{
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
};
|
};
|
||||||
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
|
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
/// Returns an error message if the auth token is invalid for any reason. Necessary because other
|
/// Returns an error message if the auth token is invalid for any reason. Necessary because other
|
||||||
/// endpoints silently treat any call with invalid auth as unauthenticated.
|
/// endpoints silently treat any call with invalid auth as unauthenticated.
|
||||||
|
@ -12,7 +12,7 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType};
|
||||||
pub async fn validate_auth(
|
pub async fn validate_auth(
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let jwt = read_auth_token(&req)?;
|
let jwt = read_auth_token(&req)?;
|
||||||
if let Some(jwt) = jwt {
|
if let Some(jwt) = jwt {
|
||||||
local_user_view_from_jwt(&jwt, &context).await?;
|
local_user_view_from_jwt(&jwt, &context).await?;
|
||||||
|
|
|
@ -5,17 +5,12 @@ use lemmy_api_common::{
|
||||||
utils::send_new_applicant_email_to_admins,
|
utils::send_new_applicant_email_to_admins,
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::source::{
|
||||||
source::{
|
|
||||||
email_verification::EmailVerification,
|
email_verification::EmailVerification,
|
||||||
local_user::{LocalUser, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
person::Person,
|
|
||||||
},
|
|
||||||
traits::Crud,
|
|
||||||
RegistrationMode,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::SiteView;
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
pub async fn verify_email(
|
pub async fn verify_email(
|
||||||
data: Json<VerifyEmail>,
|
data: Json<VerifyEmail>,
|
||||||
|
@ -23,9 +18,7 @@ pub async fn verify_email(
|
||||||
) -> LemmyResult<Json<SuccessResponse>> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
let token = data.token.clone();
|
let token = data.token.clone();
|
||||||
let verification = EmailVerification::read_for_token(&mut context.pool(), &token)
|
let verification = EmailVerification::read_for_token(&mut context.pool(), &token).await?;
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::TokenNotFound)?;
|
|
||||||
|
|
||||||
let form = LocalUserUpdateForm {
|
let form = LocalUserUpdateForm {
|
||||||
// necessary in case this is a new signup
|
// necessary in case this is a new signup
|
||||||
|
@ -36,16 +29,19 @@ pub async fn verify_email(
|
||||||
};
|
};
|
||||||
let local_user_id = verification.local_user_id;
|
let local_user_id = verification.local_user_id;
|
||||||
|
|
||||||
let local_user = LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
|
LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
|
||||||
|
|
||||||
EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?;
|
EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?;
|
||||||
|
|
||||||
// send out notification about registration application to admins if enabled
|
// send out notification about registration application to admins if enabled
|
||||||
if site_view.local_site.registration_mode == RegistrationMode::RequireApplication
|
if site_view.local_site.application_email_admins {
|
||||||
&& site_view.local_site.application_email_admins
|
let local_user = LocalUserView::read(&mut context.pool(), local_user_id).await?;
|
||||||
{
|
|
||||||
let person = Person::read(&mut context.pool(), local_user.person_id).await?;
|
send_new_applicant_email_to_admins(
|
||||||
send_new_applicant_email_to_admins(&person.name, &mut context.pool(), context.settings())
|
&local_user.person.name,
|
||||||
|
&mut context.pool(),
|
||||||
|
context.settings(),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,14 +16,14 @@ use lemmy_db_schema::{
|
||||||
PostFeatureType,
|
PostFeatureType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn feature_post(
|
pub async fn feature_post(
|
||||||
data: Json<FeaturePost>,
|
data: Json<FeaturePost>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PostResponse>, LemmyError> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
|
@ -70,11 +70,5 @@ pub async fn feature_post(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_post_response(
|
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
||||||
&context,
|
|
||||||
orig_post.community_id,
|
|
||||||
&local_user_view.person,
|
|
||||||
post_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,22 @@ use lemmy_api_common::{
|
||||||
post::{GetSiteMetadata, GetSiteMetadataResponse},
|
post::{GetSiteMetadata, GetSiteMetadataResponse},
|
||||||
request::fetch_link_metadata,
|
request::fetch_link_metadata,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::{
|
||||||
|
error::{LemmyErrorExt, LemmyResult},
|
||||||
|
LemmyErrorType,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn get_link_metadata(
|
pub async fn get_link_metadata(
|
||||||
data: Query<GetSiteMetadata>,
|
data: Query<GetSiteMetadata>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<GetSiteMetadataResponse>, LemmyError> {
|
// Require an account for this API
|
||||||
let metadata = fetch_link_metadata(&data.url, false, &context).await?;
|
_local_user_view: LocalUserView,
|
||||||
|
) -> LemmyResult<Json<GetSiteMetadataResponse>> {
|
||||||
|
let url = Url::parse(&data.url).with_lemmy_type(LemmyErrorType::InvalidUrl)?;
|
||||||
|
let metadata = fetch_link_metadata(&url, &context).await?;
|
||||||
|
|
||||||
Ok(Json(GetSiteMetadataResponse { metadata }))
|
Ok(Json(GetSiteMetadataResponse { metadata }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{context::LemmyContext, post::HidePost, SuccessResponse};
|
use lemmy_api_common::{context::LemmyContext, post::HidePost, SuccessResponse};
|
||||||
use lemmy_db_schema::source::post::PostHide;
|
use lemmy_db_schema::source::post::PostHide;
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -10,7 +10,7 @@ pub async fn hide_post(
|
||||||
data: Json<HidePost>,
|
data: Json<HidePost>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let post_ids = HashSet::from_iter(data.post_ids.clone());
|
let post_ids = HashSet::from_iter(data.post_ids.clone());
|
||||||
|
|
||||||
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
||||||
|
|
|
@ -21,7 +21,7 @@ use lemmy_db_schema::{
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -29,7 +29,7 @@ pub async fn like_post(
|
||||||
data: Json<CreatePostLike>,
|
data: Json<CreatePostLike>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PostResponse>, LemmyError> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
// Don't do a downvote if site has downvotes disabled
|
// Don't do a downvote if site has downvotes disabled
|
||||||
|
@ -66,25 +66,20 @@ pub async fn like_post(
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the post as read
|
|
||||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||||
|
|
||||||
|
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::LikePostOrComment {
|
SendActivityData::LikePostOrComment {
|
||||||
object_id: post.ap_id,
|
object_id: post.ap_id,
|
||||||
actor: local_user_view.person.clone(),
|
actor: local_user_view.person.clone(),
|
||||||
community: Community::read(&mut context.pool(), post.community_id).await?,
|
community,
|
||||||
score: data.score,
|
score: data.score,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_post_response(
|
build_post_response(context.deref(), post.community_id, local_user_view, post_id).await
|
||||||
context.deref(),
|
|
||||||
post.community_id,
|
|
||||||
&local_user_view.person,
|
|
||||||
post_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::post::Post, traits::Crud};
|
use lemmy_db_schema::{source::post::Post, traits::Crud};
|
||||||
use lemmy_db_views::structs::{LocalUserView, VoteView};
|
use lemmy_db_views::structs::{LocalUserView, VoteView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists likes for a post
|
/// Lists likes for a post
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -14,7 +14,7 @@ pub async fn list_post_likes(
|
||||||
data: Query<ListPostLikes>,
|
data: Query<ListPostLikes>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListPostLikesResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListPostLikesResponse>> {
|
||||||
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
||||||
is_mod_or_admin(
|
is_mod_or_admin(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
|
|
|
@ -15,14 +15,14 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn lock_post(
|
pub async fn lock_post(
|
||||||
data: Json<LockPost>,
|
data: Json<LockPost>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PostResponse>, LemmyError> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
let orig_post = Post::read(&mut context.pool(), post_id).await?;
|
||||||
|
|
||||||
|
@ -61,11 +61,5 @@ pub async fn lock_post(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
build_post_response(
|
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
|
||||||
&context,
|
|
||||||
orig_post.community_id,
|
|
||||||
&local_user_view.person,
|
|
||||||
post_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse};
|
use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse};
|
||||||
use lemmy_db_schema::source::post::PostRead;
|
use lemmy_db_schema::source::post::PostRead;
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -10,7 +10,7 @@ pub async fn mark_post_as_read(
|
||||||
data: Json<MarkPostAsRead>,
|
data: Json<MarkPostAsRead>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let post_ids = HashSet::from_iter(data.post_ids.clone());
|
let post_ids = HashSet::from_iter(data.post_ids.clone());
|
||||||
|
|
||||||
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
|
||||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
||||||
traits::Saveable,
|
traits::Saveable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PostView};
|
use lemmy_db_views::structs::{LocalUserView, PostView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn save_post(
|
pub async fn save_post(
|
||||||
data: Json<SavePost>,
|
data: Json<SavePost>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PostResponse>, LemmyError> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let post_saved_form = PostSavedForm {
|
let post_saved_form = PostSavedForm {
|
||||||
post_id: data.post_id,
|
post_id: data.post_id,
|
||||||
person_id: local_user_view.person.id,
|
person_id: local_user_view.person.id,
|
||||||
|
@ -34,9 +34,14 @@ pub async fn save_post(
|
||||||
|
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false).await?;
|
let post_view = PostView::read(
|
||||||
|
&mut context.pool(),
|
||||||
|
post_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Mark the post as read
|
|
||||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||||
|
|
||||||
Ok(Json(PostResponse { post_view }))
|
Ok(Json(PostResponse { post_view }))
|
||||||
|
|
|
@ -19,7 +19,7 @@ use lemmy_db_schema::{
|
||||||
traits::Reportable,
|
traits::Reportable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PostReportView, PostView};
|
use lemmy_db_views::structs::{LocalUserView, PostReportView, PostView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
/// Creates a post report and notifies the moderators of the community
|
/// Creates a post report and notifies the moderators of the community
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -27,7 +27,7 @@ pub async fn create_post_report(
|
||||||
data: Json<CreatePostReport>,
|
data: Json<CreatePostReport>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PostReportResponse>, LemmyError> {
|
) -> LemmyResult<Json<PostReportResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let reason = data.reason.trim().to_string();
|
let reason = data.reason.trim().to_string();
|
||||||
|
|
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
||||||
utils::check_community_mod_of_any_or_admin_action,
|
utils::check_community_mod_of_any_or_admin_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView};
|
use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists post reports for a community if an id is supplied
|
/// Lists post reports for a community if an id is supplied
|
||||||
/// or returns all post reports for communities a user moderates
|
/// or returns all post reports for communities a user moderates
|
||||||
|
@ -14,7 +14,7 @@ pub async fn list_post_reports(
|
||||||
data: Query<ListPostReports>,
|
data: Query<ListPostReports>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListPostReportsResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListPostReportsResponse>> {
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
|
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PostReportView};
|
use lemmy_db_views::structs::{LocalUserView, PostReportView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
/// Resolves or unresolves a post report and notifies the moderators of the community
|
/// Resolves or unresolves a post report and notifies the moderators of the community
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -14,7 +14,7 @@ pub async fn resolve_post_report(
|
||||||
data: Json<ResolvePostReport>,
|
data: Json<ResolvePostReport>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PostReportResponse>, LemmyError> {
|
) -> LemmyResult<Json<PostReportResponse>> {
|
||||||
let report_id = data.report_id;
|
let report_id = data.report_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
|
||||||
|
|
|
@ -8,14 +8,14 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn mark_pm_as_read(
|
pub async fn mark_pm_as_read(
|
||||||
data: Json<MarkPrivateMessageAsRead>,
|
data: Json<MarkPrivateMessageAsRead>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
|
) -> LemmyResult<Json<PrivateMessageResponse>> {
|
||||||
// Checking permissions
|
// Checking permissions
|
||||||
let private_message_id = data.private_message_id;
|
let private_message_id = data.private_message_id;
|
||||||
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
||||||
|
|
|
@ -14,14 +14,14 @@ use lemmy_db_schema::{
|
||||||
traits::{Crud, Reportable},
|
traits::{Crud, Reportable},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
|
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn create_pm_report(
|
pub async fn create_pm_report(
|
||||||
data: Json<CreatePrivateMessageReport>,
|
data: Json<CreatePrivateMessageReport>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
|
) -> LemmyResult<Json<PrivateMessageReportResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
let reason = data.reason.trim().to_string();
|
let reason = data.reason.trim().to_string();
|
||||||
|
|
|
@ -8,14 +8,14 @@ use lemmy_db_views::{
|
||||||
private_message_report_view::PrivateMessageReportQuery,
|
private_message_report_view::PrivateMessageReportQuery,
|
||||||
structs::LocalUserView,
|
structs::LocalUserView,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_pm_reports(
|
pub async fn list_pm_reports(
|
||||||
data: Query<ListPrivateMessageReports>,
|
data: Query<ListPrivateMessageReports>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListPrivateMessageReportsResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListPrivateMessageReportsResponse>> {
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
let unresolved_only = data.unresolved_only.unwrap_or_default();
|
||||||
|
|
|
@ -6,14 +6,14 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, traits::Reportable};
|
use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, traits::Reportable};
|
||||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
|
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn resolve_pm_report(
|
pub async fn resolve_pm_report(
|
||||||
data: Json<ResolvePrivateMessageReport>,
|
data: Json<ResolvePrivateMessageReport>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
|
) -> LemmyResult<Json<PrivateMessageReportResponse>> {
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let report_id = data.report_id;
|
let report_id = data.report_id;
|
||||||
|
|
|
@ -9,14 +9,14 @@ use lemmy_db_schema::{
|
||||||
traits::Blockable,
|
traits::Blockable,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn block_instance(
|
pub async fn block_instance(
|
||||||
data: Json<BlockInstance>,
|
data: Json<BlockInstance>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<BlockInstanceResponse>, LemmyError> {
|
) -> LemmyResult<Json<BlockInstanceResponse>> {
|
||||||
let instance_id = data.instance_id;
|
let instance_id = data.instance_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
if local_user_view.person.instance_id == instance_id {
|
if local_user_view.person.instance_id == instance_id {
|
||||||
|
|
|
@ -5,12 +5,12 @@ use lemmy_api_common::{
|
||||||
utils::build_federated_instances,
|
utils::build_federated_instances,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::SiteView;
|
use lemmy_db_views::structs::SiteView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn get_federated_instances(
|
pub async fn get_federated_instances(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> Result<Json<GetFederatedInstancesResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetFederatedInstancesResponse>> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||||
let federated_instances =
|
let federated_instances =
|
||||||
build_federated_instances(&site_view.local_site, &mut context.pool()).await?;
|
build_federated_instances(&site_view.local_site, &mut context.pool()).await?;
|
||||||
|
|
|
@ -7,14 +7,15 @@ use lemmy_db_schema::{
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
local_user::{LocalUser, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
moderator::{ModAdd, ModAddForm},
|
moderator::{ModAdd, ModAddForm},
|
||||||
|
oauth_provider::OAuthProvider,
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_db_views_actor::structs::PersonView;
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorType},
|
error::{LemmyErrorType, LemmyResult},
|
||||||
VERSION,
|
VERSION,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ use lemmy_utils::{
|
||||||
pub async fn leave_admin(
|
pub async fn leave_admin(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetSiteResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetSiteResponse>> {
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
|
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
|
||||||
|
@ -60,10 +61,9 @@ pub async fn leave_admin(
|
||||||
|
|
||||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||||
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
let oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?;
|
||||||
let custom_emojis =
|
|
||||||
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
|
||||||
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||||
|
let tagline = Tagline::get_random(&mut context.pool()).await?;
|
||||||
|
|
||||||
Ok(Json(GetSiteResponse {
|
Ok(Json(GetSiteResponse {
|
||||||
site_view,
|
site_view,
|
||||||
|
@ -72,8 +72,11 @@ pub async fn leave_admin(
|
||||||
my_user: None,
|
my_user: None,
|
||||||
all_languages,
|
all_languages,
|
||||||
discussion_languages,
|
discussion_languages,
|
||||||
taglines,
|
oauth_providers: Some(oauth_providers),
|
||||||
custom_emojis,
|
admin_oauth_providers: None,
|
||||||
blocked_urls,
|
blocked_urls,
|
||||||
|
tagline,
|
||||||
|
taglines: vec![],
|
||||||
|
custom_emojis: vec![],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,21 +4,20 @@ use lemmy_api_common::{
|
||||||
person::{ListMedia, ListMediaResponse},
|
person::{ListMedia, ListMediaResponse},
|
||||||
utils::is_admin,
|
utils::is_admin,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::images::LocalImage;
|
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_utils::error::LemmyResult;
|
||||||
use lemmy_utils::error::LemmyError;
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn list_all_media(
|
pub async fn list_all_media(
|
||||||
data: Query<ListMedia>,
|
data: Query<ListMedia>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListMediaResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListMediaResponse>> {
|
||||||
// Only let admins view all media
|
// Only let admins view all media
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
let limit = data.limit;
|
let limit = data.limit;
|
||||||
let images = LocalImage::get_all(&mut context.pool(), page, limit).await?;
|
let images = LocalImageView::get_all(&mut context.pool(), page, limit).await?;
|
||||||
Ok(Json(ListMediaResponse { images }))
|
Ok(Json(ListMediaResponse { images }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ use lemmy_db_views_moderator::structs::{
|
||||||
ModTransferCommunityView,
|
ModTransferCommunityView,
|
||||||
ModlogListParams,
|
ModlogListParams,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
use ModlogActionType::*;
|
use ModlogActionType::*;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
|
@ -32,7 +32,7 @@ pub async fn get_mod_log(
|
||||||
data: Query<GetModlog>,
|
data: Query<GetModlog>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: Option<LocalUserView>,
|
local_user_view: Option<LocalUserView>,
|
||||||
) -> Result<Json<GetModlogResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetModlogResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
check_private_instance(&local_user_view, &local_site)?;
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
|
|
|
@ -10,26 +10,40 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgeComment, AdminPurgeCommentForm},
|
moderator::{AdminPurgeComment, AdminPurgeCommentForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_comment(
|
pub async fn purge_comment(
|
||||||
data: Json<PurgeComment>,
|
data: Json<PurgeComment>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
|
|
||||||
// Read the comment to get the post_id and community
|
// Read the comment to get the post_id and community
|
||||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
|
let comment_view = CommentView::read(
|
||||||
|
&mut context.pool(),
|
||||||
|
comment_id,
|
||||||
|
Some(&local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// 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;
|
let post_id = comment_view.comment.post_id;
|
||||||
|
|
||||||
|
|
|
@ -9,27 +9,45 @@ use lemmy_api_common::{
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
newtypes::PersonId,
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgeCommunity, AdminPurgeCommunityForm},
|
moderator::{AdminPurgeCommunity, AdminPurgeCommunityForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_db_views_actor::structs::CommunityModeratorView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_community(
|
pub async fn purge_community(
|
||||||
data: Json<PurgeCommunity>,
|
data: Json<PurgeCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
// Read the community to get its images
|
// Read the community to get its images
|
||||||
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
let community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||||
|
|
||||||
|
// 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 {
|
if let Some(banner) = &community.banner {
|
||||||
purge_image_from_pictrs(banner, &context).await.ok();
|
purge_image_from_pictrs(banner, &context).await.ok();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,24 +10,34 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgePerson, AdminPurgePersonForm},
|
moderator::{AdminPurgePerson, AdminPurgePersonForm},
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_person(
|
pub async fn purge_person(
|
||||||
data: Json<PurgePerson>,
|
data: Json<PurgePerson>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
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?;
|
let person = Person::read(&mut context.pool(), data.person_id).await?;
|
||||||
|
|
||||||
ban_nonlocal_user_from_local_communities(
|
ban_nonlocal_user_from_local_communities(
|
||||||
&local_user_view,
|
&local_user_view,
|
||||||
&person,
|
&person,
|
||||||
|
@ -65,7 +75,7 @@ pub async fn purge_person(
|
||||||
moderator: local_user_view.person,
|
moderator: local_user_view.person,
|
||||||
banned_user: person,
|
banned_user: person,
|
||||||
reason: data.reason.clone(),
|
reason: data.reason.clone(),
|
||||||
remove_data: Some(true),
|
remove_or_restore_data: Some(true),
|
||||||
ban: true,
|
ban: true,
|
||||||
expires: None,
|
expires: None,
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,26 +10,35 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgePost, AdminPurgePostForm},
|
moderator::{AdminPurgePost, AdminPurgePostForm},
|
||||||
post::Post,
|
post::Post,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn purge_post(
|
pub async fn purge_post(
|
||||||
data: Json<PurgePost>,
|
data: Json<PurgePost>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<SuccessResponse>, LemmyError> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Only let admin purge an item
|
// Only let admin purge an item
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
// Read the post to get the community_id
|
// Read the post to get the community_id
|
||||||
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
let post = Post::read(&mut context.pool(), data.post_id).await?;
|
||||||
|
|
||||||
|
// 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
|
// Purge image
|
||||||
if let Some(url) = &post.url {
|
if let Some(url) = &post.url {
|
||||||
purge_image_from_pictrs(url, &context).await.ok();
|
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::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{ApproveRegistrationApplication, RegistrationApplicationResponse},
|
site::{ApproveRegistrationApplication, RegistrationApplicationResponse},
|
||||||
|
@ -10,48 +11,60 @@ use lemmy_db_schema::{
|
||||||
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
|
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::diesel_option_overwrite,
|
utils::{diesel_string_update, get_conn},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
|
|
||||||
pub async fn approve_registration_application(
|
pub async fn approve_registration_application(
|
||||||
data: Json<ApproveRegistrationApplication>,
|
data: Json<ApproveRegistrationApplication>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<RegistrationApplicationResponse>, LemmyError> {
|
) -> LemmyResult<Json<RegistrationApplicationResponse>> {
|
||||||
let app_id = data.id;
|
let app_id = data.id;
|
||||||
|
|
||||||
// Only let admins do this
|
// Only let admins do this
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
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
|
// Update the registration with reason, admin_id
|
||||||
let deny_reason = diesel_option_overwrite(data.deny_reason.clone());
|
let deny_reason = diesel_string_update(tx_data.deny_reason.as_deref());
|
||||||
let app_form = RegistrationApplicationUpdateForm {
|
let app_form = RegistrationApplicationUpdateForm {
|
||||||
admin_id: Some(Some(local_user_view.person.id)),
|
admin_id: Some(Some(local_user_view.person.id)),
|
||||||
deny_reason,
|
deny_reason,
|
||||||
};
|
};
|
||||||
|
|
||||||
let registration_application =
|
let registration_application =
|
||||||
RegistrationApplication::update(&mut context.pool(), app_id, &app_form).await?;
|
RegistrationApplication::update(&mut conn.into(), app_id, &app_form).await?;
|
||||||
|
|
||||||
// Update the local_user row
|
// Update the local_user row
|
||||||
let local_user_form = LocalUserUpdateForm {
|
let local_user_form = LocalUserUpdateForm {
|
||||||
accepted_application: Some(data.approve),
|
accepted_application: Some(tx_data.approve),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let approved_user_id = registration_application.local_user_id;
|
let approved_user_id = registration_application.local_user_id;
|
||||||
LocalUser::update(&mut context.pool(), approved_user_id, &local_user_form).await?;
|
LocalUser::update(&mut conn.into(), approved_user_id, &local_user_form).await?;
|
||||||
|
|
||||||
|
Ok::<_, LemmyError>(approved_user_id)
|
||||||
|
}) as _
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
if data.approve {
|
if data.approve {
|
||||||
let approved_local_user_view =
|
let approved_local_user_view =
|
||||||
LocalUserView::read(&mut context.pool(), approved_user_id).await?;
|
LocalUserView::read(&mut context.pool(), approved_user_id).await?;
|
||||||
|
|
||||||
if approved_local_user_view.local_user.email.is_some() {
|
if approved_local_user_view.local_user.email.is_some() {
|
||||||
|
// Email sending may fail, but this won't revert the application approval
|
||||||
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
|
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Read the view
|
// Read the view
|
||||||
let registration_application =
|
let registration_application =
|
||||||
|
|
26
crates/api/src/site/registration_applications/get.rs
Normal file
26
crates/api/src/site/registration_applications/get.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// 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(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::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::{ListRegistrationApplications, ListRegistrationApplicationsResponse},
|
site::{ListRegistrationApplications, ListRegistrationApplicationsResponse},
|
||||||
|
@ -9,14 +10,14 @@ use lemmy_db_views::{
|
||||||
registration_application_view::RegistrationApplicationQuery,
|
registration_application_view::RegistrationApplicationQuery,
|
||||||
structs::LocalUserView,
|
structs::LocalUserView,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
/// Lists registration applications, filterable by undenied only.
|
/// Lists registration applications, filterable by undenied only.
|
||||||
pub async fn list_registration_applications(
|
pub async fn list_registration_applications(
|
||||||
data: Query<ListRegistrationApplications>,
|
data: Query<ListRegistrationApplications>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<ListRegistrationApplicationsResponse>, LemmyError> {
|
) -> LemmyResult<Json<ListRegistrationApplicationsResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
pub mod approve;
|
pub mod approve;
|
||||||
|
pub mod get;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
pub mod unread_count;
|
pub mod unread_count;
|
||||||
|
|
420
crates/api/src/site/registration_applications/tests.rs
Normal file
420
crates/api/src/site/registration_applications/tests.rs
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
#[expect(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?;
|
||||||
|
|
||||||
|
let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id);
|
||||||
|
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 {
|
||||||
|
require_email_verification: Some(true),
|
||||||
|
application_question: Some(".".to_string()),
|
||||||
|
registration_mode: Some(RegistrationMode::RequireApplication),
|
||||||
|
site_setup: Some(true),
|
||||||
|
..LocalSiteInsertForm::new(site.id)
|
||||||
|
};
|
||||||
|
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::new(local_site.id);
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[expect(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))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[serial]
|
||||||
|
#[tokio::test]
|
||||||
|
#[expect(clippy::indexing_slicing)]
|
||||||
|
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::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
site::GetUnreadRegistrationApplicationCountResponse,
|
site::GetUnreadRegistrationApplicationCountResponse,
|
||||||
|
@ -6,12 +7,12 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::source::local_site::LocalSite;
|
use lemmy_db_schema::source::local_site::LocalSite;
|
||||||
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
pub async fn get_unread_registration_application_count(
|
pub async fn get_unread_registration_application_count(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> Result<Json<GetUnreadRegistrationApplicationCountResponse>, LemmyError> {
|
) -> LemmyResult<Json<GetUnreadRegistrationApplicationCountResponse>> {
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||||
|
|
||||||
// Only let admins do this
|
// Only let admins do this
|
||||||
|
|
|
@ -42,7 +42,7 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
#[expect(clippy::unwrap_used)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
|
|
||||||
use crate::sitemap::generate_urlset;
|
use crate::sitemap::generate_urlset;
|
||||||
|
|
|
@ -25,7 +25,7 @@ full = [
|
||||||
"lemmy_db_views_moderator/full",
|
"lemmy_db_views_moderator/full",
|
||||||
"lemmy_utils/full",
|
"lemmy_utils/full",
|
||||||
"activitypub_federation",
|
"activitypub_federation",
|
||||||
"encoding",
|
"encoding_rs",
|
||||||
"reqwest-middleware",
|
"reqwest-middleware",
|
||||||
"webpage",
|
"webpage",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
|
@ -34,7 +34,6 @@ full = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"futures",
|
"futures",
|
||||||
"once_cell",
|
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"mime",
|
"mime",
|
||||||
]
|
]
|
||||||
|
@ -61,18 +60,17 @@ reqwest = { workspace = true, optional = true }
|
||||||
ts-rs = { workspace = true, optional = true }
|
ts-rs = { workspace = true, optional = true }
|
||||||
moka.workspace = true
|
moka.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
once_cell = { workspace = true, optional = true }
|
|
||||||
actix-web = { workspace = true, optional = true }
|
actix-web = { workspace = true, optional = true }
|
||||||
enum-map = { workspace = true }
|
enum-map = { workspace = true }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
mime = { version = "0.3.17", optional = true }
|
mime = { version = "0.3.17", optional = true }
|
||||||
webpage = { version = "1.6", default-features = false, features = [
|
webpage = { version = "2.0", default-features = false, features = [
|
||||||
"serde",
|
"serde",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
encoding = { version = "0.2.33", optional = true }
|
encoding_rs = { version = "0.8.34", optional = true }
|
||||||
jsonwebtoken = { version = "8.3.0", optional = true }
|
jsonwebtoken = { version = "9.3.0", optional = true }
|
||||||
# necessary for wasmt compilation
|
# necessary for wasmt compilation
|
||||||
getrandom = { version = "0.2.12", features = ["js"] }
|
getrandom = { version = "0.2.15", features = ["js"] }
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-machete]
|
||||||
ignored = ["getrandom"]
|
ignored = ["getrandom"]
|
||||||
|
|
|
@ -19,14 +19,13 @@ use lemmy_db_schema::{
|
||||||
comment_reply::{CommentReply, CommentReplyInsertForm},
|
comment_reply::{CommentReply, CommentReplyInsertForm},
|
||||||
person::Person,
|
person::Person,
|
||||||
person_mention::{PersonMention, PersonMentionInsertForm},
|
person_mention::{PersonMention, PersonMentionInsertForm},
|
||||||
post::Post,
|
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
|
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
|
||||||
use lemmy_db_views_actor::structs::CommunityView;
|
use lemmy_db_views_actor::structs::CommunityView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::LemmyError,
|
error::LemmyResult,
|
||||||
utils::{markdown::markdown_to_html, mention::MentionData},
|
utils::{markdown::markdown_to_html, mention::MentionData},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,9 +34,10 @@ pub async fn build_comment_response(
|
||||||
comment_id: CommentId,
|
comment_id: CommentId,
|
||||||
local_user_view: Option<LocalUserView>,
|
local_user_view: Option<LocalUserView>,
|
||||||
recipient_ids: Vec<LocalUserId>,
|
recipient_ids: Vec<LocalUserId>,
|
||||||
) -> Result<CommentResponse, LemmyError> {
|
) -> LemmyResult<CommentResponse> {
|
||||||
let person_id = local_user_view.map(|l| l.person.id);
|
let local_user = local_user_view.map(|l| l.local_user);
|
||||||
let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id).await?;
|
let comment_view =
|
||||||
|
CommentView::read(&mut context.pool(), comment_id, local_user.as_ref()).await?;
|
||||||
Ok(CommentResponse {
|
Ok(CommentResponse {
|
||||||
comment_view,
|
comment_view,
|
||||||
recipient_ids,
|
recipient_ids,
|
||||||
|
@ -48,15 +48,15 @@ pub async fn build_community_response(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
) -> Result<Json<CommunityResponse>, LemmyError> {
|
) -> LemmyResult<Json<CommunityResponse>> {
|
||||||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
|
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
|
||||||
.await
|
.await
|
||||||
.is_ok();
|
.is_ok();
|
||||||
let person_id = local_user_view.person.id;
|
let local_user = local_user_view.local_user;
|
||||||
let community_view = CommunityView::read(
|
let community_view = CommunityView::read(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
community_id,
|
community_id,
|
||||||
Some(person_id),
|
Some(&local_user),
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -71,16 +71,17 @@ pub async fn build_community_response(
|
||||||
pub async fn build_post_response(
|
pub async fn build_post_response(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
community_id: CommunityId,
|
community_id: CommunityId,
|
||||||
person: &Person,
|
local_user_view: LocalUserView,
|
||||||
post_id: PostId,
|
post_id: PostId,
|
||||||
) -> Result<Json<PostResponse>, LemmyError> {
|
) -> LemmyResult<Json<PostResponse>> {
|
||||||
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id)
|
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
|
.await
|
||||||
.is_ok();
|
.is_ok();
|
||||||
let post_view = PostView::read(
|
let post_view = PostView::read(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
post_id,
|
post_id,
|
||||||
Some(person.id),
|
Some(&local_user),
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -91,16 +92,26 @@ pub async fn build_post_response(
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn send_local_notifs(
|
pub async fn send_local_notifs(
|
||||||
mentions: Vec<MentionData>,
|
mentions: Vec<MentionData>,
|
||||||
comment: &Comment,
|
comment_id: CommentId,
|
||||||
person: &Person,
|
person: &Person,
|
||||||
post: &Post,
|
|
||||||
do_send_email: bool,
|
do_send_email: bool,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> Result<Vec<LocalUserId>, LemmyError> {
|
local_user_view: Option<&LocalUserView>,
|
||||||
|
) -> LemmyResult<Vec<LocalUserId>> {
|
||||||
let mut recipient_ids = Vec::new();
|
let mut recipient_ids = Vec::new();
|
||||||
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||||
|
|
||||||
let community_id = post.community_id;
|
// let person = my_local_user.person;
|
||||||
|
// Read the comment view to get extra info
|
||||||
|
let comment_view = CommentView::read(
|
||||||
|
&mut context.pool(),
|
||||||
|
comment_id,
|
||||||
|
local_user_view.map(|view| &view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let comment = comment_view.comment;
|
||||||
|
let post = comment_view.post;
|
||||||
|
let community = comment_view.community;
|
||||||
|
|
||||||
// Send the local mentions
|
// Send the local mentions
|
||||||
for mention in mentions
|
for mention in mentions
|
||||||
|
@ -112,12 +123,13 @@ pub async fn send_local_notifs(
|
||||||
if let Ok(mention_user_view) = user_view {
|
if let Ok(mention_user_view) = user_view {
|
||||||
// TODO
|
// TODO
|
||||||
// At some point, make it so you can't tag the parent creator either
|
// At some point, make it so you can't tag the parent creator either
|
||||||
// Potential duplication of notifications, one for reply and the other for mention, is handled below by checking recipient ids
|
// Potential duplication of notifications, one for reply and the other for mention, is handled
|
||||||
|
// below by checking recipient ids
|
||||||
recipient_ids.push(mention_user_view.local_user.id);
|
recipient_ids.push(mention_user_view.local_user.id);
|
||||||
|
|
||||||
let user_mention_form = PersonMentionInsertForm {
|
let user_mention_form = PersonMentionInsertForm {
|
||||||
recipient_id: mention_user_view.person.id,
|
recipient_id: mention_user_view.person.id,
|
||||||
comment_id: comment.id,
|
comment_id,
|
||||||
read: None,
|
read: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -152,8 +164,9 @@ pub async fn send_local_notifs(
|
||||||
let check_blocks = check_person_instance_community_block(
|
let check_blocks = check_person_instance_community_block(
|
||||||
person.id,
|
person.id,
|
||||||
parent_creator_id,
|
parent_creator_id,
|
||||||
person.instance_id,
|
// Only block from the community's instance_id
|
||||||
community_id,
|
community.instance_id,
|
||||||
|
community.id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -194,11 +207,13 @@ pub async fn send_local_notifs(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Use the post creator to check blocks
|
||||||
let check_blocks = check_person_instance_community_block(
|
let check_blocks = check_person_instance_community_block(
|
||||||
person.id,
|
person.id,
|
||||||
post.creator_id,
|
post.creator_id,
|
||||||
person.instance_id,
|
// Only block from the community's instance_id
|
||||||
community_id,
|
community.instance_id,
|
||||||
|
community.id,
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::{context::LemmyContext, sensitive::Sensitive};
|
use crate::context::LemmyContext;
|
||||||
use actix_web::{http::header::USER_AGENT, HttpRequest};
|
use actix_web::{http::header::USER_AGENT, HttpRequest};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::LocalUserId,
|
newtypes::LocalUserId,
|
||||||
|
sensitive::SensitiveString,
|
||||||
source::login_token::{LoginToken, LoginTokenCreateForm},
|
source::login_token::{LoginToken, LoginTokenCreateForm},
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
@ -28,19 +29,15 @@ impl Claims {
|
||||||
let claims =
|
let claims =
|
||||||
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
|
||||||
let user_id = LocalUserId(claims.claims.sub.parse()?);
|
let user_id = LocalUserId(claims.claims.sub.parse()?);
|
||||||
let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
|
LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
|
||||||
if !is_valid {
|
|
||||||
Err(LemmyErrorType::NotLoggedIn)?
|
|
||||||
} else {
|
|
||||||
Ok(user_id)
|
Ok(user_id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn generate(
|
pub async fn generate(
|
||||||
user_id: LocalUserId,
|
user_id: LocalUserId,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> LemmyResult<Sensitive<String>> {
|
) -> LemmyResult<SensitiveString> {
|
||||||
let hostname = context.settings().hostname.clone();
|
let hostname = context.settings().hostname.clone();
|
||||||
let my_claims = Claims {
|
let my_claims = Claims {
|
||||||
sub: user_id.0.to_string(),
|
sub: user_id.0.to_string(),
|
||||||
|
@ -50,7 +47,7 @@ impl Claims {
|
||||||
|
|
||||||
let secret = &context.secret().jwt_secret;
|
let secret = &context.secret().jwt_secret;
|
||||||
let key = EncodingKey::from_secret(secret.as_ref());
|
let key = EncodingKey::from_secret(secret.as_ref());
|
||||||
let token = encode(&Header::default(), &my_claims, &key)?;
|
let token: SensitiveString = encode(&Header::default(), &my_claims, &key)?.into();
|
||||||
let ip = req
|
let ip = req
|
||||||
.connection_info()
|
.connection_info()
|
||||||
.realip_remote_addr()
|
.realip_remote_addr()
|
||||||
|
@ -67,13 +64,12 @@ impl Claims {
|
||||||
user_agent,
|
user_agent,
|
||||||
};
|
};
|
||||||
LoginToken::create(&mut context.pool(), form).await?;
|
LoginToken::create(&mut context.pool(), form).await?;
|
||||||
Ok(Sensitive::new(token))
|
Ok(token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
#[expect(clippy::unwrap_used)]
|
||||||
#[allow(clippy::indexing_slicing)]
|
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::{claims::Claims, context::LemmyContext};
|
use crate::{claims::Claims, context::LemmyContext};
|
||||||
|
@ -99,7 +95,7 @@ mod tests {
|
||||||
async fn test_should_not_validate_user_token_after_password_change() {
|
async fn test_should_not_validate_user_token_after_password_change() {
|
||||||
let pool_ = build_db_pool_for_tests().await;
|
let pool_ = build_db_pool_for_tests().await;
|
||||||
let pool = &mut (&pool_).into();
|
let pool = &mut (&pool_).into();
|
||||||
let secret = Secret::init(pool).await.unwrap();
|
let secret = Secret::init(pool).await.unwrap().unwrap();
|
||||||
let context = LemmyContext::create(
|
let context = LemmyContext::create(
|
||||||
pool_.clone(),
|
pool_.clone(),
|
||||||
ClientBuilder::new(Client::default()).build(),
|
ClientBuilder::new(Client::default()).build(),
|
||||||
|
@ -111,18 +107,11 @@ mod tests {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let new_person = PersonInsertForm::builder()
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
|
||||||
.name("Gerry9812".into())
|
|
||||||
.public_key("pubkey".to_string())
|
|
||||||
.instance_id(inserted_instance.id)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
let inserted_person = Person::create(pool, &new_person).await.unwrap();
|
||||||
|
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
|
||||||
.person_id(inserted_person.id)
|
|
||||||
.password_encrypted("123456".to_string())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -3,7 +3,7 @@ use lemmy_db_schema::{
|
||||||
source::site::Site,
|
source::site::Site,
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
ListingType,
|
ListingType,
|
||||||
SortType,
|
PostSortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -74,7 +74,7 @@ pub struct CommunityResponse {
|
||||||
/// Fetches a list of communities.
|
/// Fetches a list of communities.
|
||||||
pub struct ListCommunities {
|
pub struct ListCommunities {
|
||||||
pub type_: Option<ListingType>,
|
pub type_: Option<ListingType>,
|
||||||
pub sort: Option<SortType>,
|
pub sort: Option<PostSortType>,
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
@ -97,7 +97,9 @@ pub struct BanFromCommunity {
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
pub ban: bool,
|
pub ban: bool,
|
||||||
pub remove_data: Option<bool>,
|
/// Optionally remove or restore all their data. Useful for new troll accounts.
|
||||||
|
/// If ban is true, then this means remove. If ban is false, it means restore.
|
||||||
|
pub remove_or_restore_data: Option<bool>,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
/// A time that the ban will expire, in unix epoch seconds.
|
/// A time that the ban will expire, in unix epoch seconds.
|
||||||
///
|
///
|
||||||
|
|
|
@ -55,7 +55,7 @@ impl LemmyContext {
|
||||||
/// Initialize a context for use in tests which blocks federation network calls.
|
/// Initialize a context for use in tests which blocks federation network calls.
|
||||||
///
|
///
|
||||||
/// Do not use this in production code.
|
/// 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
|
// call this to run migrations
|
||||||
let pool = build_db_pool_for_tests().await;
|
let pool = build_db_pool_for_tests().await;
|
||||||
|
|
||||||
|
@ -64,20 +64,25 @@ impl LemmyContext {
|
||||||
let client = ClientBuilder::new(client).build();
|
let client = ClientBuilder::new(client).build();
|
||||||
let secret = Secret {
|
let secret = Secret {
|
||||||
id: 0,
|
id: 0,
|
||||||
jwt_secret: String::new(),
|
jwt_secret: String::new().into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let rate_limit_cell = RateLimitCell::with_test_config();
|
let rate_limit_cell = RateLimitCell::with_test_config();
|
||||||
|
|
||||||
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
|
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
|
||||||
let config = FederationConfig::builder()
|
|
||||||
|
FederationConfig::builder()
|
||||||
.domain(context.settings().hostname.clone())
|
.domain(context.settings().hostname.clone())
|
||||||
.app_data(context)
|
.app_data(context)
|
||||||
|
.debug(true)
|
||||||
// Dont allow any network fetches
|
// Dont allow any network fetches
|
||||||
.http_fetch_limit(0)
|
.http_fetch_limit(0)
|
||||||
.build()
|
.build()
|
||||||
.await
|
.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()
|
config.to_request_data()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use lemmy_db_schema::newtypes::CustomEmojiId;
|
use lemmy_db_schema::newtypes::CustomEmojiId;
|
||||||
use lemmy_db_views::structs::CustomEmojiView;
|
use lemmy_db_views::structs::CustomEmojiView;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -46,3 +47,23 @@ pub struct DeleteCustomEmoji {
|
||||||
pub struct CustomEmojiResponse {
|
pub struct CustomEmojiResponse {
|
||||||
pub custom_emoji: CustomEmojiView,
|
pub custom_emoji: CustomEmojiView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// A response for custom emojis.
|
||||||
|
pub struct ListCustomEmojisResponse {
|
||||||
|
pub custom_emojis: Vec<CustomEmojiView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Fetches a list of custom emojis.
|
||||||
|
pub struct ListCustomEmojis {
|
||||||
|
pub page: Option<i64>,
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
pub category: Option<String>,
|
||||||
|
pub ignore_page_limits: Option<bool>,
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub mod community;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
|
pub mod oauth_provider;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
@ -14,8 +15,8 @@ pub mod private_message;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod send_activity;
|
pub mod send_activity;
|
||||||
pub mod sensitive;
|
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
pub mod tagline;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ pub extern crate lemmy_utils;
|
||||||
|
|
||||||
pub use lemmy_utils::LemmyErrorType;
|
pub use lemmy_utils::LemmyErrorType;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::{cmp::min, time::Duration};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(ts_rs::TS))]
|
#[cfg_attr(feature = "full", derive(ts_rs::TS))]
|
||||||
|
@ -43,7 +44,39 @@ impl Default for SuccessResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// how long to sleep based on how many retries have already happened
|
// TODO: use from_days once stabilized
|
||||||
|
// https://github.com/rust-lang/rust/issues/120301
|
||||||
|
const DAY: Duration = Duration::from_secs(24 * 60 * 60);
|
||||||
|
|
||||||
|
/// Calculate how long to sleep until next federation send based on how many
|
||||||
|
/// retries have already happened. Uses exponential backoff with maximum of one day. The first
|
||||||
|
/// error is ignored.
|
||||||
pub fn federate_retry_sleep_duration(retry_count: i32) -> Duration {
|
pub fn federate_retry_sleep_duration(retry_count: i32) -> Duration {
|
||||||
Duration::from_secs_f64(2.0_f64.powf(f64::from(retry_count)))
|
debug_assert!(retry_count != 0);
|
||||||
|
if retry_count == 1 {
|
||||||
|
return Duration::from_secs(0);
|
||||||
|
}
|
||||||
|
let retry_count = retry_count - 1;
|
||||||
|
let pow = 1.25_f64.powf(retry_count.into());
|
||||||
|
let pow = Duration::try_from_secs_f64(pow).unwrap_or(DAY);
|
||||||
|
min(DAY, pow)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_federate_retry_sleep_duration() {
|
||||||
|
assert_eq!(Duration::from_secs(0), federate_retry_sleep_duration(1));
|
||||||
|
assert_eq!(
|
||||||
|
Duration::new(1, 250000000),
|
||||||
|
federate_retry_sleep_duration(2)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Duration::new(2, 441406250),
|
||||||
|
federate_retry_sleep_duration(5)
|
||||||
|
);
|
||||||
|
assert_eq!(DAY, federate_retry_sleep_duration(100));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
70
crates/api_common/src/oauth_provider.rs
Normal file
70
crates/api_common/src/oauth_provider.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
use lemmy_db_schema::newtypes::OAuthProviderId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use ts_rs::TS;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Create an external auth method.
|
||||||
|
pub struct CreateOAuthProvider {
|
||||||
|
pub display_name: String,
|
||||||
|
pub issuer: String,
|
||||||
|
pub authorization_endpoint: String,
|
||||||
|
pub token_endpoint: String,
|
||||||
|
pub userinfo_endpoint: String,
|
||||||
|
pub id_claim: String,
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
pub scopes: String,
|
||||||
|
pub auto_verify_email: Option<bool>,
|
||||||
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Edit an external auth method.
|
||||||
|
pub struct EditOAuthProvider {
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
pub authorization_endpoint: Option<String>,
|
||||||
|
pub token_endpoint: Option<String>,
|
||||||
|
pub userinfo_endpoint: Option<String>,
|
||||||
|
pub id_claim: Option<String>,
|
||||||
|
pub client_secret: Option<String>,
|
||||||
|
pub scopes: Option<String>,
|
||||||
|
pub auto_verify_email: Option<bool>,
|
||||||
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Delete an external auth method.
|
||||||
|
pub struct DeleteOAuthProvider {
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Logging in with an OAuth 2.0 authorization
|
||||||
|
pub struct AuthenticateWithOauth {
|
||||||
|
pub code: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub oauth_provider_id: OAuthProviderId,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub redirect_uri: Url,
|
||||||
|
pub show_nsfw: Option<bool>,
|
||||||
|
/// Username is mandatory at registration time
|
||||||
|
pub username: Option<String>,
|
||||||
|
/// An answer is mandatory if require application is enabled on the server
|
||||||
|
pub answer: Option<String>,
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::sensitive::Sensitive;
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||||
source::{images::LocalImage, site::Site},
|
sensitive::SensitiveString,
|
||||||
|
source::{login_token::LoginToken, site::Site},
|
||||||
CommentSortType,
|
CommentSortType,
|
||||||
ListingType,
|
ListingType,
|
||||||
PostListingMode,
|
PostListingMode,
|
||||||
SortType,
|
PostSortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentView, PostView};
|
use lemmy_db_views::structs::{CommentView, LocalImageView, PostView};
|
||||||
use lemmy_db_views_actor::structs::{
|
use lemmy_db_views_actor::structs::{
|
||||||
CommentReplyView,
|
CommentReplyView,
|
||||||
CommunityModeratorView,
|
CommunityModeratorView,
|
||||||
|
@ -25,8 +25,8 @@ use ts_rs::TS;
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Logging into lemmy.
|
/// Logging into lemmy.
|
||||||
pub struct Login {
|
pub struct Login {
|
||||||
pub username_or_email: Sensitive<String>,
|
pub username_or_email: SensitiveString,
|
||||||
pub password: Sensitive<String>,
|
pub password: SensitiveString,
|
||||||
/// May be required, if totp is enabled for their account.
|
/// May be required, if totp is enabled for their account.
|
||||||
pub totp_2fa_token: Option<String>,
|
pub totp_2fa_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -38,11 +38,11 @@ pub struct Login {
|
||||||
/// Register / Sign up to lemmy.
|
/// Register / Sign up to lemmy.
|
||||||
pub struct Register {
|
pub struct Register {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: Sensitive<String>,
|
pub password: SensitiveString,
|
||||||
pub password_verify: Sensitive<String>,
|
pub password_verify: SensitiveString,
|
||||||
pub show_nsfw: bool,
|
pub show_nsfw: Option<bool>,
|
||||||
/// email is mandatory if email verification is enabled on the server
|
/// email is mandatory if email verification is enabled on the server
|
||||||
pub email: Option<Sensitive<String>>,
|
pub email: Option<SensitiveString>,
|
||||||
/// The UUID of the captcha item.
|
/// The UUID of the captcha item.
|
||||||
pub captcha_uuid: Option<String>,
|
pub captcha_uuid: Option<String>,
|
||||||
/// Your captcha answer.
|
/// Your captcha answer.
|
||||||
|
@ -84,12 +84,18 @@ pub struct CaptchaResponse {
|
||||||
pub struct SaveUserSettings {
|
pub struct SaveUserSettings {
|
||||||
/// Show nsfw posts.
|
/// Show nsfw posts.
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
|
/// Blur nsfw posts.
|
||||||
pub blur_nsfw: Option<bool>,
|
pub blur_nsfw: Option<bool>,
|
||||||
pub auto_expand: Option<bool>,
|
|
||||||
/// Your user's theme.
|
/// Your user's theme.
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
pub default_sort_type: Option<SortType>,
|
/// The default post listing type, usually "local"
|
||||||
pub default_listing_type: Option<ListingType>,
|
pub default_listing_type: Option<ListingType>,
|
||||||
|
/// A post-view mode that changes how multiple post listings look.
|
||||||
|
pub post_listing_mode: Option<PostListingMode>,
|
||||||
|
/// The default post sort, usually "active"
|
||||||
|
pub default_post_sort_type: Option<PostSortType>,
|
||||||
|
/// The default comment sort, usually "hot"
|
||||||
|
pub default_comment_sort_type: Option<CommentSortType>,
|
||||||
/// The language of the lemmy interface
|
/// The language of the lemmy interface
|
||||||
pub interface_language: Option<String>,
|
pub interface_language: Option<String>,
|
||||||
/// A URL for your avatar.
|
/// A URL for your avatar.
|
||||||
|
@ -99,7 +105,7 @@ pub struct SaveUserSettings {
|
||||||
/// Your display name, which can contain strange characters, and does not need to be unique.
|
/// Your display name, which can contain strange characters, and does not need to be unique.
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
/// Your email.
|
/// Your email.
|
||||||
pub email: Option<Sensitive<String>>,
|
pub email: Option<SensitiveString>,
|
||||||
/// Your bio / info, in markdown.
|
/// Your bio / info, in markdown.
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
/// Your matrix user id. Ex: @my_user:matrix.org
|
/// Your matrix user id. Ex: @my_user:matrix.org
|
||||||
|
@ -120,11 +126,10 @@ pub struct SaveUserSettings {
|
||||||
pub open_links_in_new_tab: Option<bool>,
|
pub open_links_in_new_tab: Option<bool>,
|
||||||
/// Enable infinite scroll
|
/// Enable infinite scroll
|
||||||
pub infinite_scroll_enabled: Option<bool>,
|
pub infinite_scroll_enabled: Option<bool>,
|
||||||
/// A post-view mode that changes how multiple post listings look.
|
|
||||||
pub post_listing_mode: Option<PostListingMode>,
|
|
||||||
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
|
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
|
||||||
pub enable_keyboard_navigation: Option<bool>,
|
pub enable_keyboard_navigation: Option<bool>,
|
||||||
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or should be paused
|
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or
|
||||||
|
/// should be paused
|
||||||
pub enable_animated_images: Option<bool>,
|
pub enable_animated_images: Option<bool>,
|
||||||
/// Whether to auto-collapse bot comments.
|
/// Whether to auto-collapse bot comments.
|
||||||
pub collapse_bot_comments: Option<bool>,
|
pub collapse_bot_comments: Option<bool>,
|
||||||
|
@ -140,9 +145,9 @@ pub struct SaveUserSettings {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Changes your account password.
|
/// Changes your account password.
|
||||||
pub struct ChangePassword {
|
pub struct ChangePassword {
|
||||||
pub new_password: Sensitive<String>,
|
pub new_password: SensitiveString,
|
||||||
pub new_password_verify: Sensitive<String>,
|
pub new_password_verify: SensitiveString,
|
||||||
pub old_password: Sensitive<String>,
|
pub old_password: SensitiveString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
@ -151,8 +156,9 @@ pub struct ChangePassword {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// A response for your login.
|
/// A response for your login.
|
||||||
pub struct LoginResponse {
|
pub struct LoginResponse {
|
||||||
/// This is None in response to `Register` if email verification is enabled, or the server requires registration applications.
|
/// This is None in response to `Register` if email verification is enabled, or the server
|
||||||
pub jwt: Option<Sensitive<String>>,
|
/// requires registration applications.
|
||||||
|
pub jwt: Option<SensitiveString>,
|
||||||
/// If registration applications are required, this will return true for a signup response.
|
/// If registration applications are required, this will return true for a signup response.
|
||||||
pub registration_created: bool,
|
pub registration_created: bool,
|
||||||
/// If email verifications are required, this will return true for a signup response.
|
/// If email verifications are required, this will return true for a signup response.
|
||||||
|
@ -170,7 +176,7 @@ pub struct GetPersonDetails {
|
||||||
pub person_id: Option<PersonId>,
|
pub person_id: Option<PersonId>,
|
||||||
/// Example: dessalines , or dessalines@xyz.tld
|
/// Example: dessalines , or dessalines@xyz.tld
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
pub sort: Option<SortType>,
|
pub sort: Option<PostSortType>,
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
@ -215,8 +221,9 @@ pub struct AddAdminResponse {
|
||||||
pub struct BanPerson {
|
pub struct BanPerson {
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
pub ban: bool,
|
pub ban: bool,
|
||||||
/// Optionally remove all their data. Useful for new troll accounts.
|
/// Optionally remove or restore all their data. Useful for new troll accounts.
|
||||||
pub remove_data: Option<bool>,
|
/// If ban is true, then this means remove. If ban is false, it means restore.
|
||||||
|
pub remove_or_restore_data: Option<bool>,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
/// A time that the ban will expire, in unix epoch seconds.
|
/// A time that the ban will expire, in unix epoch seconds.
|
||||||
///
|
///
|
||||||
|
@ -340,7 +347,7 @@ pub struct CommentReplyResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Delete your account.
|
/// Delete your account.
|
||||||
pub struct DeleteAccount {
|
pub struct DeleteAccount {
|
||||||
pub password: Sensitive<String>,
|
pub password: SensitiveString,
|
||||||
pub delete_content: bool,
|
pub delete_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +356,7 @@ pub struct DeleteAccount {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Reset your password via email.
|
/// Reset your password via email.
|
||||||
pub struct PasswordReset {
|
pub struct PasswordReset {
|
||||||
pub email: Sensitive<String>,
|
pub email: SensitiveString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
@ -357,9 +364,9 @@ pub struct PasswordReset {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Change your password after receiving a reset request.
|
/// Change your password after receiving a reset request.
|
||||||
pub struct PasswordChangeAfterReset {
|
pub struct PasswordChangeAfterReset {
|
||||||
pub token: Sensitive<String>,
|
pub token: SensitiveString,
|
||||||
pub password: Sensitive<String>,
|
pub password: SensitiveString,
|
||||||
pub password_verify: Sensitive<String>,
|
pub password_verify: SensitiveString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
@ -405,7 +412,7 @@ pub struct VerifyEmail {
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
pub struct GenerateTotpSecretResponse {
|
pub struct GenerateTotpSecretResponse {
|
||||||
pub totp_secret_url: Sensitive<String>,
|
pub totp_secret_url: SensitiveString,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
|
@ -437,5 +444,12 @@ pub struct ListMedia {
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
pub struct ListMediaResponse {
|
pub struct ListMediaResponse {
|
||||||
pub images: Vec<LocalImage>,
|
pub images: Vec<LocalImageView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct ListLoginsResponse {
|
||||||
|
pub logins: Vec<LoginToken>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
|
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
|
||||||
ListingType,
|
ListingType,
|
||||||
PostFeatureType,
|
PostFeatureType,
|
||||||
SortType,
|
PostSortType,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView};
|
use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView};
|
||||||
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
|
||||||
|
@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
@ -20,8 +19,7 @@ use url::Url;
|
||||||
pub struct CreatePost {
|
pub struct CreatePost {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub community_id: CommunityId,
|
pub community_id: CommunityId,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
pub url: Option<String>,
|
||||||
pub url: Option<Url>,
|
|
||||||
/// An optional body for the post in markdown.
|
/// An optional body for the post in markdown.
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
/// An optional alt_text, usable for image posts.
|
/// An optional alt_text, usable for image posts.
|
||||||
|
@ -30,9 +28,10 @@ pub struct CreatePost {
|
||||||
pub honeypot: Option<String>,
|
pub honeypot: Option<String>,
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
pub custom_thumbnail: Option<Url>,
|
pub custom_thumbnail: Option<String>,
|
||||||
|
/// Time when this post should be scheduled. Null means publish immediately.
|
||||||
|
pub scheduled_publish_time: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
@ -72,7 +71,7 @@ pub struct GetPostResponse {
|
||||||
/// Get a list of posts.
|
/// Get a list of posts.
|
||||||
pub struct GetPosts {
|
pub struct GetPosts {
|
||||||
pub type_: Option<ListingType>,
|
pub type_: Option<ListingType>,
|
||||||
pub sort: Option<SortType>,
|
pub sort: Option<PostSortType>,
|
||||||
/// DEPRECATED, use page_cursor
|
/// DEPRECATED, use page_cursor
|
||||||
pub page: Option<i64>,
|
pub page: Option<i64>,
|
||||||
pub limit: Option<i64>,
|
pub limit: Option<i64>,
|
||||||
|
@ -82,6 +81,10 @@ pub struct GetPosts {
|
||||||
pub liked_only: Option<bool>,
|
pub liked_only: Option<bool>,
|
||||||
pub disliked_only: Option<bool>,
|
pub disliked_only: Option<bool>,
|
||||||
pub show_hidden: Option<bool>,
|
pub show_hidden: Option<bool>,
|
||||||
|
/// If true, then show the read posts (even if your user setting is to hide them)
|
||||||
|
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>,
|
pub page_cursor: Option<PaginationCursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,17 +117,17 @@ pub struct CreatePostLike {
|
||||||
pub struct EditPost {
|
pub struct EditPost {
|
||||||
pub post_id: PostId,
|
pub post_id: PostId,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
pub url: Option<String>,
|
||||||
pub url: Option<Url>,
|
|
||||||
/// An optional body for the post in markdown.
|
/// An optional body for the post in markdown.
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
/// An optional alt_text, usable for image posts.
|
/// An optional alt_text, usable for image posts.
|
||||||
pub alt_text: Option<String>,
|
pub alt_text: Option<String>,
|
||||||
pub nsfw: Option<bool>,
|
pub nsfw: Option<bool>,
|
||||||
pub language_id: Option<LanguageId>,
|
pub language_id: Option<LanguageId>,
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
pub custom_thumbnail: Option<Url>,
|
pub custom_thumbnail: Option<String>,
|
||||||
|
/// Time when this post should be scheduled. Null means publish immediately.
|
||||||
|
pub scheduled_publish_time: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||||
|
@ -249,8 +252,7 @@ pub struct ListPostReportsResponse {
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Get metadata for a given site.
|
/// Get metadata for a given site.
|
||||||
pub struct GetSiteMetadata {
|
pub struct GetSiteMetadata {
|
||||||
#[cfg_attr(feature = "full", ts(type = "string"))]
|
pub url: String,
|
||||||
pub url: Url,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
@ -270,8 +272,6 @@ pub struct LinkMetadata {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub opengraph_data: OpenGraphData,
|
pub opengraph_data: OpenGraphData,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
#[serde(skip)]
|
|
||||||
pub thumbnail: Option<DbUrl>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue