Merge remote-tracking branch 'upstream/main' into new-migration-diff-check

This commit is contained in:
Dull Bananas 2025-03-11 20:47:12 -07:00
parent 3ac183599f
commit 28bb1c7497
410 changed files with 13137 additions and 9307 deletions

View file

@ -14,7 +14,7 @@ body:
label: Requirements
description: Before you create a bug report please do the following.
options:
- label: Is this a bug report? For questions or discussions use https://lemmy.ml/c/lemmy_support
- label: Is this a bug report? For questions or discussions use https://lemmy.ml/c/lemmy_support or the [matrix chat](https://matrix.to/#/#lemmy:matrix.org).
required: true
- label: Did you check to see if this issue already exists?
required: true

View file

@ -12,7 +12,7 @@ body:
label: Requirements
description: Before you create a bug report please do the following.
options:
- label: Is this a feature request? For questions or discussions use https://lemmy.ml/c/lemmy_support
- label: Is this a feature request? For questions or discussions use https://lemmy.ml/c/lemmy_support or the [matrix chat](https://matrix.to/#/#lemmy:matrix.org).
required: true
- label: Did you check to see if this issue already exists?
required: true

View file

@ -6,7 +6,9 @@ body:
- type: markdown
attributes:
value: |
Have a question about Lemmy?
For questions or discussions use https://lemmy.ml/c/lemmy_support or the [matrix chat](https://matrix.to/#/#lemmy:matrix.org).
Have a question about how Lemmy works?
Please check the docs first: https://join-lemmy.org/docs/en/index.html
- type: textarea
id: question

View file

@ -6,13 +6,13 @@ variables:
# as well. Otherwise release builds can fail if Lemmy or dependencies rely on new Rust
# features. In particular the ARM builder image needs to be updated manually in the repo below:
# https://github.com/raskyld/lemmy-cross-toolchains
- &rust_image "rust:1.83"
- &rust_image "rust:1.81"
- &rust_nightly_image "rustlang/rust:nightly"
- &install_pnpm "corepack enable pnpm"
- &install_pnpm "npm install -g corepack@latest && 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
- cargo install --locked diesel_cli --no-default-features --features postgres
- export PATH="$CARGO_HOME/bin:$PATH"
- &slow_check_paths
- event: pull_request

461
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
[workspace.package]
version = "0.19.6-beta.7"
version = "1.0.0-alpha.2"
edition = "2021"
description = "A link aggregator for the fediverse"
license = "AGPL-3.0"
@ -36,7 +36,6 @@ codegen-units = 1 # Reduce parallel code generation.
debug = 0
[features]
json-log = ["tracing-subscriber/json"]
default = []
[workspace]
@ -49,8 +48,6 @@ members = [
"crates/db_perf",
"crates/db_schema",
"crates/db_views",
"crates/db_views_actor",
"crates/db_views_actor",
"crates/routes",
"crates/federate",
]
@ -82,21 +79,19 @@ map_err_ignore = "deny"
expect_used = "deny"
[workspace.dependencies]
lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" }
lemmy_api_crud = { version = "=0.19.6-beta.7", path = "./crates/api_crud" }
lemmy_apub = { version = "=0.19.6-beta.7", path = "./crates/apub" }
lemmy_utils = { version = "=0.19.6-beta.7", path = "./crates/utils", default-features = false }
lemmy_db_schema = { version = "=0.19.6-beta.7", path = "./crates/db_schema" }
lemmy_api_common = { version = "=0.19.6-beta.7", path = "./crates/api_common" }
lemmy_routes = { version = "=0.19.6-beta.7", path = "./crates/routes" }
lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
activitypub_federation = { version = "0.6.1", default-features = false, features = [
lemmy_api = { version = "=1.0.0-alpha.2", path = "./crates/api" }
lemmy_api_crud = { version = "=1.0.0-alpha.2", path = "./crates/api_crud" }
lemmy_apub = { version = "=1.0.0-alpha.2", path = "./crates/apub" }
lemmy_utils = { version = "=1.0.0-alpha.2", path = "./crates/utils", default-features = false }
lemmy_db_schema = { version = "=1.0.0-alpha.2", path = "./crates/db_schema" }
lemmy_api_common = { version = "=1.0.0-alpha.2", path = "./crates/api_common" }
lemmy_routes = { version = "=1.0.0-alpha.2", path = "./crates/routes" }
lemmy_db_views = { version = "=1.0.0-alpha.2", path = "./crates/db_views" }
lemmy_federate = { version = "=1.0.0-alpha.2", path = "./crates/federate" }
activitypub_federation = { version = "0.6.3", default-features = false, features = [
"actix-web",
] }
diesel = "2.2.6"
diesel = "2.2.7"
diesel_migrations = "2.2.0"
diesel-async = "0.5.2"
serde = { version = "1.0.217", features = ["derive"] }
@ -109,9 +104,9 @@ actix-web = { version = "4.9.0", default-features = false, features = [
"macros",
"rustls-0_23",
] }
tracing = "0.1.41"
tracing = { version = "0.1.41", default-features = false }
tracing-actix-web = { version = "0.7.15", default-features = false }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
url = { version = "2.5.4", features = ["serde"] }
reqwest = { version = "0.12.12", default-features = false, features = [
"blocking",
@ -123,15 +118,14 @@ reqwest-middleware = "0.3.3"
reqwest-tracing = "0.5.5"
clokwerk = "0.4.0"
doku = { version = "0.21.1", features = ["url-2"] }
bcrypt = "0.16.0"
bcrypt = "0.17.0"
chrono = { version = "0.4.39", features = [
"now",
"serde",
], default-features = false }
serde_json = { version = "1.0.135", features = ["preserve_order"] }
serde_json = { version = "1.0.138", features = ["preserve_order"] }
base64 = "0.22.1"
uuid = { version = "1.12.0", features = ["serde"] }
async-trait = "0.1.85"
uuid = { version = "1.13.1", features = ["serde"] }
captcha = "0.0.9"
anyhow = { version = "1.0.95", features = ["backtrace"] }
diesel_ltree = "0.4.0"
@ -140,7 +134,8 @@ tokio = { version = "1.43.0", features = ["full"] }
regex = "1.11.1"
diesel-derive-newtype = "2.1.2"
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
strum = { version = "0.26.3", features = ["derive"] }
enum-map = { version = "2.7" }
strum = { version = "0.27.0", features = ["derive"] }
itertools = "0.14.0"
futures = "0.3.31"
http = "1.2"
@ -150,18 +145,16 @@ ts-rs = { version = "10.1.0", features = [
"no-serde-warnings",
"url-impl",
] }
rustls = { version = "0.23.21", features = ["ring"] }
rustls = { version = "0.23.23", features = ["ring"] }
futures-util = "0.3.31"
tokio-postgres = "0.7.12"
tokio-postgres = "0.7.13"
tokio-postgres-rustls = "0.13.0"
urlencoding = "2.1.3"
enum-map = "2.7"
moka = { version = "0.12.10", features = ["future"] }
i-love-jesus = { version = "0.1.0" }
clap = { version = "4.5.26", features = ["derive", "env"] }
clap = { version = "4.5.29", features = ["derive", "env"] }
pretty_assertions = "1.4.1"
derive-new = "0.7.0"
diesel-bind-if-some = "0.1.0"
tuplex = "0.1.2"
[dependencies]
@ -174,26 +167,19 @@ lemmy_api_common = { workspace = true }
lemmy_routes = { workspace = true }
lemmy_federate = { workspace = true }
activitypub_federation = { workspace = true }
diesel = { workspace = true }
diesel-async = { workspace = true }
actix-web = { workspace = true }
tracing = { workspace = true }
tracing-actix-web = { workspace = true }
tracing-subscriber = { workspace = true }
url = { workspace = true }
reqwest-middleware = { workspace = true }
reqwest-tracing = { workspace = true }
clokwerk = { workspace = true }
serde_json = { workspace = true }
rustls = { workspace = true }
tokio.workspace = true
actix-cors = "0.7.0"
futures-util = { workspace = true }
chrono = { workspace = true }
prometheus = { version = "0.13.4", features = ["process"] }
serial_test = { workspace = true }
clap = { workspace = true }
actix-web-prom = "0.9.0"
mimalloc = "0.1.43"
[dev-dependencies]
pretty_assertions = { workspace = true }
# Speedup RSA key generation
# https://github.com/RustCrypto/RSA/blob/master/README.md#example
[profile.dev.package.num-bigint-dig]
opt-level = 3

View file

@ -6,7 +6,7 @@
"repository": "https://github.com/LemmyNet/lemmy",
"author": "Dessalines",
"license": "AGPL-3.0",
"packageManager": "pnpm@9.15.0",
"packageManager": "pnpm@10.2.1+sha512.398035c7bd696d0ba0b10a688ed558285329d27ea994804a52bad9167d8e3a72bcb993f9699585d3ca25779ac64949ef422757a6c31102c12ab932e5cbe5cc92",
"scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'",
"fix": "prettier --write src && eslint --fix src",
@ -22,16 +22,18 @@
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^22.10.6",
"@typescript-eslint/eslint-plugin": "^8.20.0",
"@typescript-eslint/parser": "^8.20.0",
"eslint": "^9.18.0",
"eslint-plugin-prettier": "^5.1.3",
"@types/joi": "^17.2.3",
"@types/node": "^22.13.1",
"@typescript-eslint/eslint-plugin": "^8.24.0",
"@typescript-eslint/parser": "^8.24.0",
"eslint": "^9.20.0",
"eslint-plugin-prettier": "^5.2.3",
"jest": "^29.5.0",
"lemmy-js-client": "0.20.0-inbox-combined.1",
"prettier": "^3.4.2",
"lemmy-js-client": "0.20.0-remove-aggregate-tables.5",
"prettier": "^3.5.0",
"ts-jest": "^29.1.0",
"tsoa": "^6.6.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
"typescript-eslint": "^8.24.0"
}
}

File diff suppressed because it is too large Load diff

View file

@ -9,17 +9,31 @@ then
fi
export RUST_BACKTRACE=1
export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
# pictrs setup
if [ ! -f "api_tests/pict-rs" ]; then
# This one sometimes goes down
# curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.16/pict-rs-linux-amd64" -o api_tests/pict-rs
curl "https://codeberg.org/asonix/pict-rs/releases/download/v0.5.6/pict-rs-linux-amd64" -o api_tests/pict-rs
chmod +x api_tests/pict-rs
PICTRS_PATH="api_tests/pict-rs"
PICTRS_EXPECTED_HASH="7f7ac2a45ef9b13403ee139b7512135be6b060ff2f6460e0c800e18e1b49d2fd api_tests/pict-rs"
# Pictrs setup. Download file with hash check and up to 3 retries.
if [ ! -f "$PICTRS_PATH" ]; then
count=0
while [ ! -f "$PICTRS_PATH" ] && [ "$count" -lt 3 ]
do
# This one sometimes goes down
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.17-pre.9/pict-rs-linux-amd64" -o "$PICTRS_PATH"
# curl "https://codeberg.org/asonix/pict-rs/releases/download/v0.5.5/pict-rs-linux-amd64" -o "$PICTRS_PATH"
PICTRS_HASH=$(sha256sum "$PICTRS_PATH")
if [[ "$PICTRS_HASH" != "$PICTRS_EXPECTED_HASH" ]]; then
echo "Pictrs binary hash mismatch, was $PICTRS_HASH but expected $PICTRS_EXPECTED_HASH"
rm "$PICTRS_PATH"
let count=count+1
fi
done
chmod +x "$PICTRS_PATH"
fi
./api_tests/pict-rs \
run -a 0.0.0.0:8080 \
--danger-dummy-mode \
@ -72,13 +86,11 @@ LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_gamma.hjson \
target/lemmy_server >$LOG_DIR/lemmy_gamma.out 2>&1 &
echo "start delta"
# An instance with only an allowlist for beta
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_delta.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \
target/lemmy_server >$LOG_DIR/lemmy_delta.out 2>&1 &
echo "start epsilon"
# An instance who has a blocklist, with lemmy-alpha blocked
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_epsilon" \
target/lemmy_server >$LOG_DIR/lemmy_epsilon.out 2>&1 &

View file

@ -69,7 +69,7 @@ function assertCommentFederation(
expect(commentOne?.comment.ap_id).toBe(commentTwo?.comment.ap_id);
expect(commentOne?.comment.content).toBe(commentTwo?.comment.content);
expect(commentOne?.creator.name).toBe(commentTwo?.creator.name);
expect(commentOne?.community.actor_id).toBe(commentTwo?.community.actor_id);
expect(commentOne?.community.ap_id).toBe(commentTwo?.community.ap_id);
expect(commentOne?.comment.published).toBe(commentTwo?.comment.published);
expect(commentOne?.comment.updated).toBe(commentOne?.comment.updated);
expect(commentOne?.comment.deleted).toBe(commentOne?.comment.deleted);
@ -81,19 +81,19 @@ test("Create a comment", async () => {
expect(commentRes.comment_view.comment.content).toBeDefined();
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
expect(commentRes.comment_view.comment.score).toBe(1);
// Make sure that comment is liked on beta
let betaComment = (
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
)
).comment;
expect(betaComment).toBeDefined();
expect(betaComment?.community.local).toBe(true);
expect(betaComment?.creator.local).toBe(false);
expect(betaComment?.counts.score).toBe(1);
expect(betaComment?.comment.score).toBe(1);
assertCommentFederation(betaComment, commentRes.comment_view);
});
@ -293,48 +293,48 @@ test("Unlike a comment", async () => {
let gammaComment1 = (
await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
)
).comment;
expect(gammaComment1).toBeDefined();
expect(gammaComment1?.community.local).toBe(false);
expect(gammaComment1?.creator.local).toBe(false);
expect(gammaComment1?.counts.score).toBe(1);
expect(gammaComment1?.comment.score).toBe(1);
let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
expect(unlike.comment_view.counts.score).toBe(0);
expect(unlike.comment_view.comment.score).toBe(0);
// Make sure that comment is unliked on beta
let betaComment = (
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 0,
c => c.comment?.comment.score === 0,
)
).comment;
expect(betaComment).toBeDefined();
expect(betaComment?.community.local).toBe(true);
expect(betaComment?.creator.local).toBe(false);
expect(betaComment?.counts.score).toBe(0);
expect(betaComment?.comment.score).toBe(0);
// Make sure that comment is unliked on gamma, downstream peer
// This is testing replication from remote-home-remote (alpha-beta-gamma)
let gammaComment = (
await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment),
c => c.comment?.counts.score === 0,
c => c.comment?.comment.score === 0,
)
).comment;
expect(gammaComment).toBeDefined();
expect(gammaComment?.community.local).toBe(false);
expect(gammaComment?.creator.local).toBe(false);
expect(gammaComment?.counts.score).toBe(0);
expect(gammaComment?.comment.score).toBe(0);
});
test("Federated comment like", async () => {
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
);
// Find the comment on beta
let betaComment = (
@ -346,14 +346,14 @@ test("Federated comment like", async () => {
}
let like = await likeComment(beta, 1, betaComment.comment);
expect(like.comment_view.counts.score).toBe(2);
expect(like.comment_view.comment.score).toBe(2);
// Get the post from alpha, check the likes
let postComments = await waitUntil(
() => getComments(alpha, postOnAlphaRes.post_view.post.id),
c => c.comments[0].counts.score === 2,
c => c.comments[0].comment.score === 2,
);
expect(postComments.comments[0].counts.score).toBe(2);
expect(postComments.comments[0].comment.score).toBe(2);
});
test("Reply to a comment from another instance, get notification", async () => {
@ -377,7 +377,7 @@ test("Reply to a comment from another instance, get notification", async () => {
let betaComment = (
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
)
).comment;
@ -397,12 +397,12 @@ test("Reply to a comment from another instance, get notification", async () => {
expect(getCommentParentId(replyRes.comment_view.comment)).toBe(
betaComment.comment.id,
);
expect(replyRes.comment_view.counts.score).toBe(1);
expect(replyRes.comment_view.comment.score).toBe(1);
// Make sure that reply comment is seen on alpha
let commentSearch = await waitUntil(
() => resolveComment(alpha, replyRes.comment_view.comment),
c => c.comment?.counts.score === 1,
c => c.comment?.comment.score === 1,
);
let alphaComment = commentSearch.comment!;
let postComments = await waitUntil(
@ -418,7 +418,7 @@ test("Reply to a comment from another instance, get notification", async () => {
);
expect(alphaComment.community.local).toBe(false);
expect(alphaComment.creator.local).toBe(false);
expect(alphaComment.counts.score).toBe(1);
expect(alphaComment.comment.score).toBe(1);
assertCommentFederation(alphaComment, replyRes.comment_view);
// Did alpha get notified of the reply from beta?
@ -441,7 +441,7 @@ test("Reply to a comment from another instance, get notification", async () => {
expect(alphaReply.comment.content).toBeDefined();
expect(alphaReply.community.local).toBe(false);
expect(alphaReply.creator.local).toBe(false);
expect(alphaReply.counts.score).toBe(1);
expect(alphaReply.comment.score).toBe(1);
// ToDo: interesting alphaRepliesRes.replies[0].comment_reply.id is 1, meaning? how did that come about?
expect(alphaReply.comment.id).toBe(alphaComment.comment.id);
// this is a new notification, getReplies fetch was for read/unread both, confirm it is unread.
@ -515,7 +515,7 @@ test("Mention beta from alpha comment", async () => {
expect(mentionRes.comment_view.comment.content).toBeDefined();
expect(mentionRes.comment_view.community.local).toBe(false);
expect(mentionRes.comment_view.creator.local).toBe(true);
expect(mentionRes.comment_view.counts.score).toBe(1);
expect(mentionRes.comment_view.comment.score).toBe(1);
// get beta's localized copy of the alpha post
let betaPost = await waitForPost(beta, postOnAlphaRes.post_view.post);
@ -528,7 +528,7 @@ test("Mention beta from alpha comment", async () => {
// Make sure that both new comments are seen on beta and have parent/child relationship
let betaPostComments = await waitUntil(
() => getComments(beta, betaPost!.post.id),
c => c.comments[1]?.counts.score === 1,
c => c.comments[1]?.comment.score === 1,
);
expect(betaPostComments.comments.length).toEqual(2);
// the trunk-branch root comment will be older than the mention reply comment, so index 1
@ -542,7 +542,7 @@ test("Mention beta from alpha comment", async () => {
);
expect(betaRootComment.community.local).toBe(true);
expect(betaRootComment.creator.local).toBe(false);
expect(betaRootComment.counts.score).toBe(1);
expect(betaRootComment.comment.score).toBe(1);
assertCommentFederation(betaRootComment, commentRes.comment_view);
let mentionsRes = await waitUntil(
@ -554,7 +554,7 @@ test("Mention beta from alpha comment", async () => {
expect(firstMention.comment.content).toBeDefined();
expect(firstMention.community.local).toBe(true);
expect(firstMention.creator.local).toBe(false);
expect(firstMention.counts.score).toBe(1);
expect(firstMention.comment.score).toBe(1);
// the reply comment with mention should be the most fresh, newest, index 0
expect(firstMention.person_comment_mention.comment_id).toBe(
betaPostComments.comments[0].comment.id,
@ -581,7 +581,7 @@ test("A and G subscribe to B (center) A posts, G mentions B, it gets announced t
// follow community from beta so that it accepts the mention
let betaCommunity = await resolveCommunity(
beta,
alphaCommunity.community.actor_id,
alphaCommunity.community.ap_id,
);
await followCommunity(beta, true, betaCommunity.community!.community.id);
@ -606,17 +606,17 @@ test("A and G subscribe to B (center) A posts, G mentions B, it gets announced t
expect(commentRes.comment_view.comment.content).toBe(commentContent);
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
expect(commentRes.comment_view.comment.score).toBe(1);
// Make sure alpha sees it
let alphaPostComments2 = await waitUntil(
() => getComments(alpha, alphaPost.post_view.post.id),
e => e.comments[0]?.counts.score === 1,
e => e.comments[0]?.comment.score === 1,
);
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
expect(alphaPostComments2.comments[0].community.local).toBe(true);
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
expect(alphaPostComments2.comments[0].comment.score).toBe(1);
assertCommentFederation(
alphaPostComments2.comments[0],
commentRes.comment_view,
@ -688,17 +688,17 @@ test("Check that activity from another instance is sent to third instance", asyn
expect(commentRes.comment_view.comment.content).toBe(commentContent);
expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true);
expect(commentRes.comment_view.counts.score).toBe(1);
expect(commentRes.comment_view.comment.score).toBe(1);
// Make sure alpha sees it
let alphaPostComments2 = await waitUntil(
() => getComments(alpha, alphaPost!.post.id),
e => e.comments[0]?.counts.score === 1,
e => e.comments[0]?.comment.score === 1,
);
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
expect(alphaPostComments2.comments[0].community.local).toBe(false);
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
expect(alphaPostComments2.comments[0].comment.score).toBe(1);
assertCommentFederation(
alphaPostComments2.comments[0],
commentRes.comment_view,

View file

@ -44,9 +44,7 @@ function assertCommunityFederation(
communityOne?: CommunityView,
communityTwo?: CommunityView,
) {
expect(communityOne?.community.actor_id).toBe(
communityTwo?.community.actor_id,
);
expect(communityOne?.community.ap_id).toBe(communityTwo?.community.ap_id);
expect(communityOne?.community.name).toBe(communityTwo?.community.name);
expect(communityOne?.community.title).toBe(communityTwo?.community.title);
expect(communityOne?.community.description).toBe(
@ -198,7 +196,7 @@ test("Admin actions in remote community are not federated to origin", async () =
// gamma follows community and posts in it
let gammaCommunity = (
await resolveCommunity(gamma, communityRes.community.actor_id)
await resolveCommunity(gamma, communityRes.community.ap_id)
).community;
if (!gammaCommunity) {
throw "Missing gamma community";
@ -206,7 +204,7 @@ test("Admin actions in remote community are not federated to origin", async () =
await followCommunity(gamma, true, gammaCommunity.community.id);
gammaCommunity = (
await waitUntil(
() => resolveCommunity(gamma, communityRes.community.actor_id),
() => resolveCommunity(gamma, communityRes.community.ap_id),
g => g.community?.subscribed === "Subscribed",
)
).community;
@ -221,7 +219,7 @@ test("Admin actions in remote community are not federated to origin", async () =
// admin of beta decides to ban gamma from community
let betaCommunity = (
await resolveCommunity(beta, communityRes.community.actor_id)
await resolveCommunity(beta, communityRes.community.ap_id)
).community;
if (!betaCommunity) {
throw "Missing beta community";
@ -230,7 +228,7 @@ test("Admin actions in remote community are not federated to origin", async () =
if (!bannedUserInfo1) {
throw "Missing banned user 1";
}
let bannedUserInfo2 = (await resolvePerson(beta, bannedUserInfo1.actor_id))
let bannedUserInfo2 = (await resolvePerson(beta, bannedUserInfo1.ap_id))
.person;
if (!bannedUserInfo2) {
throw "Missing banned user 2";
@ -379,10 +377,11 @@ test("User blocks instance, communities are hidden", async () => {
expect(listing_ids3).toContain(postRes.post_view.post.ap_id);
});
test("Community follower count is federated", async () => {
// TODO: this test keeps failing randomly in CI
test.skip("Community follower count is federated", async () => {
// Follow the beta community from alpha
let community = await createCommunity(beta);
let communityActorId = community.community_view.community.actor_id;
let communityActorId = community.community_view.community.ap_id;
let resolved = await resolveCommunity(alpha, communityActorId);
if (!resolved.community) {
throw "Missing beta community";
@ -397,7 +396,7 @@ test("Community follower count is federated", async () => {
).community;
// Make sure there is 1 subscriber
expect(followed?.counts.subscribers).toBe(1);
expect(followed?.community.subscribers).toBe(1);
// Follow the community from gamma
resolved = await resolveCommunity(gamma, communityActorId);
@ -414,7 +413,7 @@ test("Community follower count is federated", async () => {
).community;
// Make sure there are 2 subscribers
expect(followed?.counts?.subscribers).toBe(2);
expect(followed?.community?.subscribers).toBe(2);
// Follow the community from delta
resolved = await resolveCommunity(delta, communityActorId);
@ -431,16 +430,16 @@ test("Community follower count is federated", async () => {
).community;
// Make sure there are 3 subscribers
expect(followed?.counts?.subscribers).toBe(3);
expect(followed?.community?.subscribers).toBe(3);
});
test("Dont receive community activities after unsubscribe", async () => {
let communityRes = await createCommunity(alpha);
expect(communityRes.community_view.community.name).toBeDefined();
expect(communityRes.community_view.counts.subscribers).toBe(1);
expect(communityRes.community_view.community.subscribers).toBe(1);
let betaCommunity = (
await resolveCommunity(beta, communityRes.community_view.community.actor_id)
await resolveCommunity(beta, communityRes.community_view.community.ap_id)
).community;
assertCommunityFederation(betaCommunity, communityRes.community_view);
@ -452,7 +451,7 @@ test("Dont receive community activities after unsubscribe", async () => {
alpha,
communityRes.community_view.community.id,
);
expect(communityRes1.community_view.counts.subscribers).toBe(2);
expect(communityRes1.community_view.community.subscribers).toBe(2);
// temporarily block alpha, so that it doesn't know about unfollow
var allow_instance_params: AdminAllowInstanceParams = {
@ -471,7 +470,7 @@ test("Dont receive community activities after unsubscribe", async () => {
alpha,
communityRes.community_view.community.id,
);
expect(communityRes2.community_view.counts.subscribers).toBe(2);
expect(communityRes2.community_view.community.subscribers).toBe(2);
// unblock alpha
allow_instance_params.allow = true;
@ -487,13 +486,13 @@ test("Dont receive community activities after unsubscribe", async () => {
// await longDelay();
let postResBeta = searchPostLocal(beta, postRes.post_view.post);
expect((await postResBeta).posts.length).toBe(0);
expect((await postResBeta).results.length).toBe(0);
});
test("Fetch community, includes posts", async () => {
let communityRes = await createCommunity(alpha);
expect(communityRes.community_view.community.name).toBeDefined();
expect(communityRes.community_view.counts.subscribers).toBe(1);
expect(communityRes.community_view.community.subscribers).toBe(1);
let postRes = await createPost(
alpha,
@ -502,13 +501,12 @@ test("Fetch community, includes posts", async () => {
expect(postRes.post_view.post).toBeDefined();
let resolvedCommunity = await waitUntil(
() =>
resolveCommunity(beta, communityRes.community_view.community.actor_id),
() => resolveCommunity(beta, communityRes.community_view.community.ap_id),
c => c.community?.community.id != undefined,
);
let betaCommunity = resolvedCommunity.community;
expect(betaCommunity?.community.actor_id).toBe(
communityRes.community_view.community.actor_id,
expect(betaCommunity?.community.ap_id).toBe(
communityRes.community_view.community.ap_id,
);
await longDelay();
@ -529,7 +527,7 @@ test("Content in local-only community doesn't federate", async () => {
// cant resolve the community from another instance
await expect(
resolveCommunity(beta, communityRes.actor_id),
resolveCommunity(beta, communityRes.ap_id),
).rejects.toStrictEqual(Error("not_found"));
// create a post, also cant resolve it
@ -544,7 +542,7 @@ test("Remote mods can edit communities", async () => {
let betaCommunity = await resolveCommunity(
beta,
communityRes.community_view.community.actor_id,
communityRes.community_view.community.ap_id,
);
if (!betaCommunity.community) {
throw "Missing beta community";
@ -583,7 +581,7 @@ test("Community name with non-ascii chars", async () => {
let betaCommunity1 = await resolveCommunity(
beta,
communityRes.community_view.community.actor_id,
communityRes.community_view.community.ap_id,
);
expect(betaCommunity1.community!.community.name).toBe(name);

View file

@ -27,21 +27,21 @@ test("Follow local community", async () => {
// Make sure the follow response went through
expect(follow.community_view.community.local).toBe(true);
expect(follow.community_view.subscribed).toBe("Subscribed");
expect(follow.community_view.counts.subscribers).toBe(
community.counts.subscribers + 1,
expect(follow.community_view.community.subscribers).toBe(
community.community.subscribers + 1,
);
expect(follow.community_view.counts.subscribers_local).toBe(
community.counts.subscribers_local + 1,
expect(follow.community_view.community.subscribers_local).toBe(
community.community.subscribers_local + 1,
);
// Test an unfollow
let unfollow = await followCommunity(user, false, community.community.id);
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
expect(unfollow.community_view.counts.subscribers).toBe(
community.counts.subscribers,
expect(unfollow.community_view.community.subscribers).toBe(
community.community.subscribers,
);
expect(unfollow.community_view.counts.subscribers_local).toBe(
community.counts.subscribers_local,
expect(unfollow.community_view.community.subscribers_local).toBe(
community.community.subscribers_local,
);
});
@ -51,7 +51,7 @@ test("Follow federated community", async () => {
const betaCommunityInitial = (
await waitUntil(
() => resolveBetaCommunity(alpha),
c => !!c.community && c.community?.counts.subscribers >= 1,
c => !!c.community && c.community?.community.subscribers >= 1,
)
).community;
if (!betaCommunityInitial) {
@ -74,14 +74,14 @@ test("Follow federated community", async () => {
expect(betaCommunity?.community.local).toBe(false);
expect(betaCommunity?.community.name).toBe("main");
expect(betaCommunity?.subscribed).toBe("Subscribed");
expect(betaCommunity?.counts.subscribers_local).toBe(
betaCommunityInitial.counts.subscribers_local + 1,
expect(betaCommunity?.community.subscribers_local).toBe(
betaCommunityInitial.community.subscribers_local + 1,
);
// check that unfollow was federated
let communityOnBeta1 = await resolveBetaCommunity(beta);
expect(communityOnBeta1.community?.counts.subscribers).toBe(
betaCommunityInitial.counts.subscribers + 1,
expect(communityOnBeta1.community?.community.subscribers).toBe(
betaCommunityInitial.community.subscribers + 1,
);
// Check it from local
@ -113,11 +113,11 @@ test("Follow federated community", async () => {
let communityOnBeta2 = await waitUntil(
() => resolveBetaCommunity(beta),
c =>
c.community?.counts.subscribers ===
betaCommunityInitial.counts.subscribers,
c.community?.community.subscribers ===
betaCommunityInitial.community.subscribers,
);
expect(communityOnBeta2.community?.counts.subscribers).toBe(
betaCommunityInitial.counts.subscribers,
expect(communityOnBeta2.community?.community.subscribers).toBe(
betaCommunityInitial.community.subscribers,
);
expect(communityOnBeta2.community?.counts.subscribers_local).toBe(1);
expect(communityOnBeta2.community?.community.subscribers_local).toBe(1);
});

View file

@ -75,7 +75,7 @@ test("Upload image and delete it", async () => {
expect(listAllMediaRes.images.length).toBe(previousThumbnails);
// Make sure the uploader is correct
expect(listMediaRes.images[0].person.actor_id).toBe(
expect(listMediaRes.images[0].person.ap_id).toBe(
`http://lemmy-alpha:8541/u/lemmy_alpha`,
);
@ -268,7 +268,7 @@ test("No image proxying if setting is disabled", async () => {
let community = await createCommunity(alpha);
let betaCommunity = await resolveCommunity(
beta,
community.community_view.community.actor_id,
community.community_view.community.ap_id,
);
await followCommunity(beta, true, betaCommunity.community!.community.id);

View file

@ -39,16 +39,20 @@ import {
listReports,
getMyUser,
listInbox,
getModlog,
} from "./shared";
import { PostView } from "lemmy-js-client/dist/types/PostView";
import { AdminBlockInstanceParams } from "lemmy-js-client/dist/types/AdminBlockInstanceParams";
import {
AddModToCommunity,
EditSite,
EditPost,
PersonPostMentionView,
PostReport,
PostReportView,
ReportCombinedView,
ResolveObject,
ResolvePostReport,
} from "lemmy-js-client";
let betaCommunity: CommunityView | undefined;
@ -57,6 +61,11 @@ beforeAll(async () => {
await setupLogins();
betaCommunity = (await resolveBetaCommunity(alpha)).community;
expect(betaCommunity).toBeDefined();
// Hack: Force outgoing federation queue for beta to be created on epsilon,
// otherwise report test fails
let person = await resolvePerson(epsilon, "@lemmy_beta@lemmy-beta:8551");
expect(person.person).toBeDefined();
});
afterAll(unfollows);
@ -89,7 +98,7 @@ async function assertPostFederation(
expect(postOne?.post.embed_description).toBe(postTwo?.post.embed_description);
expect(postOne?.post.embed_video_url).toBe(postTwo?.post.embed_video_url);
expect(postOne?.post.published).toBe(postTwo?.post.published);
expect(postOne?.community.actor_id).toBe(postTwo?.community.actor_id);
expect(postOne?.community.ap_id).toBe(postTwo?.community.ap_id);
expect(postOne?.post.locked).toBe(postTwo?.post.locked);
expect(postOne?.post.removed).toBe(postTwo?.post.removed);
expect(postOne?.post.deleted).toBe(postTwo?.post.deleted);
@ -116,19 +125,19 @@ test("Create a post", async () => {
expect(postRes.post_view.post).toBeDefined();
expect(postRes.post_view.community.local).toBe(false);
expect(postRes.post_view.creator.local).toBe(true);
expect(postRes.post_view.counts.score).toBe(1);
expect(postRes.post_view.post.score).toBe(1);
// Make sure that post is liked on beta
const betaPost = await waitForPost(
beta,
postRes.post_view.post,
res => res?.counts.score === 1,
res => res?.post.score === 1,
);
expect(betaPost).toBeDefined();
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(1);
expect(betaPost?.post.score).toBe(1);
await assertPostFederation(betaPost, postRes.post_view);
// Delta only follows beta, so it should not see an alpha ap_id
@ -156,23 +165,23 @@ test("Unlike a post", async () => {
}
let postRes = await createPost(alpha, betaCommunity.community.id);
let unlike = await likePost(alpha, 0, postRes.post_view.post);
expect(unlike.post_view.counts.score).toBe(0);
expect(unlike.post_view.post.score).toBe(0);
// Try to unlike it again, make sure it stays at 0
let unlike2 = await likePost(alpha, 0, postRes.post_view.post);
expect(unlike2.post_view.counts.score).toBe(0);
expect(unlike2.post_view.post.score).toBe(0);
// Make sure that post is unliked on beta
const betaPost = await waitForPost(
beta,
postRes.post_view.post,
post => post?.counts.score === 0,
post => post?.post.score === 0,
);
expect(betaPost).toBeDefined();
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(0);
expect(betaPost?.post.score).toBe(0);
await assertPostFederation(betaPost, postRes.post_view);
});
@ -253,7 +262,7 @@ test("Collection of featured posts gets federated", async () => {
// fetch the community, ensure that post is also fetched and marked as featured
let betaCommunity = await resolveCommunity(
beta,
community.community_view.community.actor_id,
community.community_view.community.ap_id,
);
expect(betaCommunity).toBeDefined();
@ -359,7 +368,7 @@ test("Remove a post from admin and community on different instance", async () =>
}
let gammaCommunity = (
await resolveCommunity(gamma, betaCommunity.community.actor_id)
await resolveCommunity(gamma, betaCommunity.community.ap_id)
).community?.community;
if (!gammaCommunity) {
throw "Missing gamma community";
@ -398,7 +407,7 @@ test("Remove a post from admin and community on same instance", async () => {
await followBeta(alpha);
let gammaCommunity = await resolveCommunity(
gamma,
betaCommunity.community.actor_id,
betaCommunity.community.ap_id,
);
let postRes = await createPost(gamma, gammaCommunity.community!.community.id);
expect(postRes.post_view.post).toBeDefined();
@ -460,7 +469,7 @@ test("Enforce site ban federation for local user", async () => {
// create a test user
let alphaUserHttp = await registerUser(alpha, alphaUrl);
let alphaUserPerson = (await getMyUser(alphaUserHttp)).local_user_view.person;
let alphaUserActorId = alphaUserPerson?.actor_id;
let alphaUserActorId = alphaUserPerson?.ap_id;
if (!alphaUserActorId) {
throw "Missing alpha user actor id";
}
@ -540,7 +549,7 @@ test("Enforce site ban federation for federated user", async () => {
// create a test user
let alphaUserHttp = await registerUser(alpha, alphaUrl);
let alphaUserPerson = (await getMyUser(alphaUserHttp)).local_user_view.person;
let alphaUserActorId = alphaUserPerson?.actor_id;
let alphaUserActorId = alphaUserPerson?.ap_id;
if (!alphaUserActorId) {
throw "Missing alpha user actor id";
}
@ -644,14 +653,19 @@ test("Enforce community ban for federated user", async () => {
);
expect(unBanAlpha.banned).toBe(false);
// Need to re-follow the community
await followBeta(alpha);
// Check that unban was federated to alpha
await waitUntil(
() => getModlog(alpha),
m =>
m.modlog[0].type_ == "ModBanFromCommunity" &&
m.modlog[0].mod_ban_from_community.banned == false,
);
let postRes3 = await createPost(alpha, betaCommunity.community.id);
expect(postRes3.post_view.post).toBeDefined();
expect(postRes3.post_view.community.local).toBe(false);
expect(postRes3.post_view.creator.local).toBe(true);
expect(postRes3.post_view.counts.score).toBe(1);
expect(postRes3.post_view.post.score).toBe(1);
// Make sure that post makes it to beta community
let postRes4 = await waitForPost(beta, postRes3.post_view.post);
@ -679,16 +693,26 @@ test("Report a post", async () => {
// Create post from alpha
let alphaCommunity = (await resolveBetaCommunity(alpha)).community!;
await followBeta(alpha);
let postRes = await createPost(alpha, alphaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
let alphaPost = await createPost(alpha, alphaCommunity.community.id);
expect(alphaPost.post_view.post).toBeDefined();
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
if (!alphaPost) {
throw "Missing alpha post";
}
// add remote mod on epsilon
await followBeta(epsilon);
let betaCommunity = (await resolveBetaCommunity(beta)).community!;
let epsilonUser = (
await resolvePerson(beta, "@lemmy_epsilon@lemmy-epsilon:8581")
).person!;
let mod_params: AddModToCommunity = {
community_id: betaCommunity.community.id,
person_id: epsilonUser.person.id,
added: true,
};
let res = await beta.addModToCommunity(mod_params);
expect(res.moderators.length).toBe(2);
// Send report from gamma
let gammaPost = (await resolvePost(gamma, alphaPost.post)).post!;
let gammaPost = (await resolvePost(gamma, alphaPost.post_view.post)).post!;
let gammaReport = (
await reportPost(gamma, gammaPost.post.id, randomString(10))
).post_report_view.post_report;
@ -714,11 +738,12 @@ test("Report a post", async () => {
expect(betaReport.reason).toBe(gammaReport.reason);
await unfollowRemotes(alpha);
// Report was federated to poster's instance
// Report was federated to poster's instance. Alpha is not a community mod and doesnt see
// the report by default, so we need to pass show_mod_reports = true.
let alphaReport = (
(await waitUntil(
() =>
listReports(alpha).then(p =>
listReports(alpha, true).then(p =>
p.reports.find(r => {
return checkPostReportName(r, gammaReport);
}),
@ -732,6 +757,45 @@ test("Report a post", async () => {
//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);
// Report was federated to remote mod instance
let epsilonReport = (
(await waitUntil(
() =>
listReports(epsilon).then(p =>
p.reports.find(r => {
return checkPostReportName(r, gammaReport);
}),
),
res => !!res,
))! as PostReportView
).post_report;
expect(epsilonReport).toBeDefined();
expect(epsilonReport.resolved).toBe(false);
expect(epsilonReport.original_post_name).toBe(gammaReport.original_post_name);
// Resolve report as remote mod
let resolve_params: ResolvePostReport = {
report_id: epsilonReport.id,
resolved: true,
};
let resolve = await epsilon.resolvePostReport(resolve_params);
expect(resolve.post_report_view.post_report.resolved).toBeTruthy();
// Report should be marked resolved on community instance
let resolvedReport = (
(await waitUntil(
() =>
listReports(beta).then(p =>
p.reports.find(r => {
return checkPostReportName(r, gammaReport) && r.resolver != null;
}),
),
res => !!res,
))! as PostReportView
).post_report;
expect(resolvedReport).toBeDefined();
expect(resolvedReport.resolved).toBe(true);
});
test("Fetch post via redirect", async () => {
@ -742,7 +806,7 @@ test("Fetch post via redirect", async () => {
const betaPost = await waitForPost(
beta,
alphaPost.post_view.post,
res => res?.counts.score === 1,
res => res?.post.score === 1,
);
expect(betaPost).toBeDefined();
@ -815,7 +879,7 @@ test("Mention beta from alpha post body", async () => {
expect(postOnAlphaRes.post_view.post.body).toBeDefined();
expect(postOnAlphaRes.post_view.community.local).toBe(false);
expect(postOnAlphaRes.post_view.creator.local).toBe(true);
expect(postOnAlphaRes.post_view.counts.score).toBe(1);
expect(postOnAlphaRes.post_view.post.score).toBe(1);
// get beta's localized copy of the alpha post
let betaPost = await waitForPost(beta, postOnAlphaRes.post_view.post);
@ -835,7 +899,7 @@ test("Mention beta from alpha post body", async () => {
expect(firstMention.post.body).toBeDefined();
expect(firstMention.community.local).toBe(true);
expect(firstMention.creator.local).toBe(false);
expect(firstMention.counts.score).toBe(1);
expect(firstMention.post.score).toBe(1);
expect(firstMention.person_post_mention.post_id).toBe(betaPost.post.id);
});
@ -852,7 +916,6 @@ test("Rewrite markdown links", async () => {
"https://example.com/",
`[link](${postRes1.post_view.post.ap_id})`,
);
console.log(postRes2.post_view.post.body);
expect(postRes2.post_view.post).toBeDefined();
// fetch both posts from another instance
@ -865,6 +928,50 @@ test("Rewrite markdown links", async () => {
);
});
test("Don't allow NSFW posts on instances that disable it", async () => {
// Disallow NSFW on gamma
let editSiteForm: EditSite = {
disallow_nsfw_content: true,
};
await gamma.editSite(editSiteForm);
// Wait for cache on Gamma's LocalSite
await delay(1_000);
if (!betaCommunity) {
throw "Missing beta community";
}
// Make a NSFW post
let postRes = await createPost(beta, betaCommunity.community.id);
let form: EditPost = {
nsfw: true,
post_id: postRes.post_view.post.id,
};
let updatePost = await beta.editPost(form);
// Gamma reject resolving the post
await expect(
resolvePost(gamma, updatePost.post_view.post),
).rejects.toStrictEqual(Error("not_found"));
// Local users can't create NSFW post on Gamma
let gammaCommunity = (
await resolveCommunity(gamma, betaCommunity.community.ap_id)
).community?.community;
if (!gammaCommunity) {
throw "Missing gamma community";
}
let gammaPost = await createPost(gamma, gammaCommunity.id);
let form2: EditPost = {
nsfw: true,
post_id: gammaPost.post_view.post.id,
};
await expect(gamma.editPost(form2)).rejects.toStrictEqual(
Error("nsfw_not_allowed"),
);
});
function checkPostReportName(rcv: ReportCombinedView, report: PostReport) {
switch (rcv.type_) {
case "Post":

View file

@ -47,7 +47,7 @@ test("Follow a private community", async () => {
// follow as new user
const user = await registerUser(beta, betaUrl);
const betaCommunity = (
await resolveCommunity(user, community.community_view.community.actor_id)
await resolveCommunity(user, community.community_view.community.ap_id)
).community;
expect(betaCommunity).toBeDefined();
expect(betaCommunity?.community.visibility).toBe("Private");
@ -134,7 +134,7 @@ test("Only followers can view and interact with private community content", asyn
// user is not following the community and cannot view nor create posts
const user = await registerUser(beta, betaUrl);
const betaCommunity = (
await resolveCommunity(user, community.community_view.community.actor_id)
await resolveCommunity(user, community.community_view.community.ap_id)
).community!.community;
await expect(resolvePost(user, post0.post_view.post)).rejects.toStrictEqual(
Error("not_found"),
@ -179,7 +179,7 @@ test("Reject follower", async () => {
// user is not following the community and cannot view nor create posts
const user = await registerUser(beta, betaUrl);
const betaCommunity1 = (
await resolveCommunity(user, community.community_view.community.actor_id)
await resolveCommunity(user, community.community_view.community.ap_id)
).community!.community;
// follow the community and reject
@ -216,7 +216,7 @@ test("Follow a private community and receive activities", async () => {
// follow with users from beta and gamma
const betaCommunity = (
await resolveCommunity(beta, community.community_view.community.actor_id)
await resolveCommunity(beta, community.community_view.community.ap_id)
).community;
expect(betaCommunity).toBeDefined();
const betaCommunityId = betaCommunity!.community.id;
@ -228,7 +228,7 @@ test("Follow a private community and receive activities", async () => {
await approveFollower(alpha, alphaCommunityId);
const gammaCommunityId = (
await resolveCommunity(gamma, community.community_view.community.actor_id)
await resolveCommunity(gamma, community.community_view.community.ap_id)
).community!.community.id;
const follow_form_gamma: FollowCommunity = {
community_id: gammaCommunityId,
@ -281,7 +281,7 @@ test("Fetch remote content in private community", async () => {
const alphaCommunityId = community.community_view.community.id;
const betaCommunityId = (
await resolveCommunity(beta, community.community_view.community.actor_id)
await resolveCommunity(beta, community.community_view.community.ap_id)
).community!.community.id;
const follow_form_beta: FollowCommunity = {
community_id: betaCommunityId,
@ -312,7 +312,7 @@ test("Fetch remote content in private community", async () => {
// create gamma user
const gammaCommunityId = (
await resolveCommunity(gamma, community.community_view.community.actor_id)
await resolveCommunity(gamma, community.community_view.community.ap_id)
).community!.community.id;
const follow_form: FollowCommunity = {
community_id: gammaCommunityId,

View file

@ -27,6 +27,8 @@ import {
ListInboxResponse,
ListInbox,
InboxDataType,
GetModlogResponse,
GetModlog,
} from "lemmy-js-client";
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
@ -83,6 +85,7 @@ import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
import { GetPersonDetailsResponse } from "lemmy-js-client/dist/types/GetPersonDetailsResponse";
import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails";
import { ListingType } from "lemmy-js-client/dist/types/ListingType";
import { GetCommunityPendingFollowsCountI } from "lemmy-js-client/dist/other_types";
export const fetchFunction = fetch;
export const imageFetchLimit = 50;
@ -199,7 +202,7 @@ export async function setupLogins() {
}
}
async function allowInstance(api: LemmyHttp, instance: string) {
export async function allowInstance(api: LemmyHttp, instance: string) {
const params: AdminAllowInstanceParams = {
instance,
allow: true,
@ -324,9 +327,8 @@ export async function searchPostLocal(
post: Post,
): Promise<SearchResponse> {
let form: Search = {
q: post.name,
search_term: post.name,
type_: "Posts",
sort: "TopAll",
listing_type: "All",
};
return api.search(form);
@ -339,7 +341,7 @@ export async function waitForPost(
checker: (t: PostView | undefined) => boolean = p => !!p,
) {
return waitUntil<PostView>(
() => searchPostLocal(api, post).then(p => p.posts[0]),
() => searchPostLocal(api, post).then(p => p.results[0] as PostView),
checker,
);
}
@ -801,8 +803,9 @@ export async function reportPost(
export async function listReports(
api: LemmyHttp,
show_community_rule_violations: boolean = false,
): Promise<ListReportsResponse> {
let form: ListReports = {};
let form: ListReports = { show_community_rule_violations };
return api.listReports(form);
}
@ -883,7 +886,8 @@ export function getCommunityPendingFollowsCount(
api: LemmyHttp,
community_id: CommunityId,
): Promise<GetCommunityPendingFollowsCountResponse> {
return api.getCommunityPendingFollowsCount(community_id);
let form: GetCommunityPendingFollowsCountI = { community_id };
return api.getCommunityPendingFollowsCount(form);
}
export function approveCommunityPendingFollow(
@ -899,6 +903,10 @@ export function approveCommunityPendingFollow(
};
return api.approveCommunityPendingFollow(form);
}
export function getModlog(api: LemmyHttp): Promise<GetModlogResponse> {
let form: GetModlog = {};
return api.getModlog(form);
}
export function delay(millis = 500) {
return new Promise(resolve => setTimeout(resolve, millis));

View file

@ -41,7 +41,7 @@ function assertUserFederation(userOne?: PersonView, userTwo?: PersonView) {
expect(userOne?.person.name).toBe(userTwo?.person.name);
expect(userOne?.person.display_name).toBe(userTwo?.person.display_name);
expect(userOne?.person.bio).toBe(userTwo?.person.bio);
expect(userOne?.person.actor_id).toBe(userTwo?.person.actor_id);
expect(userOne?.person.ap_id).toBe(userTwo?.person.ap_id);
expect(userOne?.person.avatar).toBe(userTwo?.person.avatar);
expect(userOne?.person.banner).toBe(userTwo?.person.banner);
expect(userOne?.person.published).toBe(userTwo?.person.published);
@ -181,7 +181,7 @@ test("Create user with accept-language", async () => {
.map(l => l.code);
// should have languages from accept header, as well as "undetermined"
// which is automatically enabled by backend
expect(langs).toStrictEqual(["und", "de", "en", "fr"]);
expect(langs).toStrictEqual(["de", "en", "fr"]);
});
test("Set a new avatar, old avatar is deleted", async () => {

View file

@ -59,27 +59,25 @@
upload_timeout: 30
# Resize post thumbnails to this maximum width/height.
max_thumbnail_size: 512
# Maximum size for user avatar, community icon and site icon.
# Maximum size for user avatar, community icon and site icon. Larger images are downscaled.
max_avatar_size: 512
# Maximum size for user, community and site banner. Larger images are downscaled to fit
# into a square of this size.
# Maximum size for user, community and site banner. Larger images are downscaled.
max_banner_size: 1024
# Maximum size for other uploads (e.g. post images or markdown embed images). Larger
# images are downscaled.
max_upload_size: 1024
# Whether users can upload videos as post image or markdown embed.
allow_video_uploads: true
# Prevent users from uploading images for posts or embedding in markdown. Avatars, icons and
# banners can still be uploaded.
image_upload_disabled: false
}
# Email sending configuration. All options except login/password are mandatory
email: {
# Hostname and port of the smtp server
smtp_server: "localhost:25"
# Login name for smtp server
smtp_login: "string"
# Password to login to the smtp server
smtp_password: "string"
# https://docs.rs/lettre/0.11.14/lettre/transport/smtp/struct.AsyncSmtpTransport.html#method.from_url
connection: "smtps://user:pass@hostname:port"
# Address to send emails from, eg "noreply@your-instance.com"
smtp_from_address: "noreply@example.com"
# Whether or not smtp connections should use tls. Can be none, tls, or starttls
tls_type: "none"
}
# Parameters for automatic configuration of new instance (only used at first start)
setup: {
@ -110,7 +108,13 @@
bind: "127.0.0.1"
port: 10002
}
# Sets a response Access-Control-Allow-Origin CORS header
# Sets a response Access-Control-Allow-Origin CORS header. Can also be set via environment:
# `LEMMY_CORS_ORIGIN=example.org,site.com`
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
cors_origin: "lemmy.tld"
cors_origin: [
"lemmy.tld"
/* ... */
]
# Print logs in JSON format. You can also disable ANSI colors in logs with env var `NO_COLOR`.
json_logging: false
}

View file

@ -21,8 +21,6 @@ workspace = true
lemmy_utils = { workspace = true }
lemmy_db_schema = { workspace = true, features = ["full"] }
lemmy_db_views = { workspace = true, features = ["full"] }
lemmy_db_views_moderator = { workspace = true, features = ["full"] }
lemmy_db_views_actor = { workspace = true, features = ["full"] }
lemmy_api_common = { workspace = true, features = ["full"] }
activitypub_federation = { workspace = true }
bcrypt = { workspace = true }
@ -33,10 +31,10 @@ anyhow = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
url = { workspace = true }
regex = { workspace = true }
hound = "3.5.1"
sitemap-rs = "0.2.2"
totp-rs = { version = "5.6.0", features = ["gen_secret", "otpauth"] }
actix-web-httpauth = "0.8.2"
[dev-dependencies]
serial_test = { workspace = true }

View file

@ -11,7 +11,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn distinguish_comment(
data: Json<DistinguishComment>,
context: Data<LemmyContext>,

View file

@ -20,7 +20,6 @@ use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use std::ops::Deref;
#[tracing::instrument(skip(context))]
pub async fn like_comment(
data: Json<CreateCommentLike>,
context: Data<LemmyContext>,

View file

@ -8,7 +8,6 @@ use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
use lemmy_utils::error::LemmyResult;
/// Lists likes for a comment
#[tracing::instrument(skip(context))]
pub async fn list_comment_likes(
data: Query<ListCommentLikes>,
context: Data<LemmyContext>,

View file

@ -10,7 +10,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn save_comment(
data: Json<SaveComment>,
context: Data<LemmyContext>,

View file

@ -14,11 +14,9 @@ use lemmy_db_schema::{
},
traits::{Crud, Joinable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_db_views::structs::{CommunityModeratorView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn add_mod_to_community(
data: Json<AddModToCommunity>,
context: Data<LemmyContext>,

View file

@ -24,14 +24,12 @@ use lemmy_db_schema::{
},
traits::{Bannable, Crud, Followable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_db_views::structs::{LocalUserView, PersonView};
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::validation::is_valid_body_field,
};
#[tracing::instrument(skip(context))]
pub async fn ban_from_community(
data: Json<BanFromCommunity>,
context: Data<LemmyContext>,

View file

@ -12,11 +12,9 @@ use lemmy_db_schema::{
},
traits::{Blockable, Followable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_db_views::structs::{CommunityView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn user_block_community(
data: Json<BlockCommunity>,
context: Data<LemmyContext>,

View file

@ -14,11 +14,9 @@ use lemmy_db_schema::{
traits::{Crud, Followable},
CommunityVisibility,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
use lemmy_db_views::structs::{CommunityPersonBanView, CommunityView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn follow_community(
data: Json<FollowCommunity>,
context: Data<LemmyContext>,

View file

@ -17,7 +17,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn hide_community(
data: Json<HideCommunity>,
context: Data<LemmyContext>,

View file

@ -4,8 +4,7 @@ use lemmy_api_common::{
context::LemmyContext,
utils::is_mod_or_admin,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityFollowerView;
use lemmy_db_views::structs::{CommunityFollowerView, LocalUserView};
use lemmy_utils::error::LemmyResult;
pub async fn get_pending_follows_count(

View file

@ -4,8 +4,7 @@ use lemmy_api_common::{
context::LemmyContext,
utils::check_community_mod_of_any_or_admin_action,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityFollowerView;
use lemmy_db_views::structs::{CommunityFollowerView, LocalUserView};
use lemmy_utils::error::LemmyResult;
pub async fn get_pending_follows_list(

View file

@ -10,11 +10,9 @@ use lemmy_db_schema::source::{
community::Community,
local_site::LocalSite,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_db_views::structs::{CommunityView, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn get_random_community(
data: Query<GetRandomCommunity>,
context: Data<LemmyContext>,

View file

@ -12,8 +12,7 @@ use lemmy_db_schema::{
},
traits::{Crud, Joinable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
use lemmy_db_views::structs::{CommunityModeratorView, CommunityView, LocalUserView};
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
location_info,
@ -21,7 +20,7 @@ use lemmy_utils::{
// TODO: we dont do anything for federation here, it should be updated the next time the community
// gets fetched. i hope we can get rid of the community creator role soon.
#[tracing::instrument(skip(context))]
pub async fn transfer_community(
data: Json<TransferCommunity>,
context: Data<LemmyContext>,

View file

@ -1,14 +1,11 @@
use activitypub_federation::config::Data;
use actix_web::{http::header::Header, HttpRequest};
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
use captcha::Captcha;
use lemmy_api_common::{
claims::Claims,
community::BanFromCommunity,
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_expire_time, check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME},
utils::check_expire_time,
};
use lemmy_db_schema::{
source::{
@ -18,7 +15,6 @@ use lemmy_db_schema::{
CommunityPersonBan,
CommunityPersonBanForm,
},
local_site::LocalSite,
mod_log::moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
person::Person,
},
@ -26,9 +22,10 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::slurs::check_slurs,
};
use regex::Regex;
use std::io::Cursor;
use totp_rs::{Secret, TOTP};
@ -82,9 +79,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
}
/// Check size of report
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> LemmyResult<()> {
let slur_regex = &local_site_to_slur_regex(local_site);
pub(crate) fn check_report_reason(reason: &str, slur_regex: &Regex) -> LemmyResult<()> {
check_slurs(reason, slur_regex)?;
if reason.is_empty() {
Err(LemmyErrorType::ReportReasonRequired)?
@ -95,21 +90,6 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Lemmy
}
}
pub fn read_auth_token(req: &HttpRequest) -> LemmyResult<Option<String>> {
// Try reading jwt from auth header
if let Ok(header) = Authorization::<Bearer>::parse(req) {
Ok(Some(header.as_ref().token().to_string()))
}
// If that fails, try to read from cookie
else if let Some(cookie) = &req.cookie(AUTH_COOKIE_NAME) {
Ok(Some(cookie.value().to_string()))
}
// Otherwise, there's no auth
else {
Ok(None)
}
}
pub(crate) fn check_totp_2fa_valid(
local_user_view: &LocalUserView,
totp_token: &Option<String>,
@ -164,7 +144,6 @@ fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> LemmyResult<T
/// So when doing a site ban for a non-local user, you need to federate/send a
/// community ban for every local community they've participated in.
/// See https://github.com/LemmyNet/lemmy/issues/4118
#[tracing::instrument(skip_all)]
pub(crate) async fn ban_nonlocal_user_from_local_communities(
local_user_view: &LocalUserView,
target: &Person,
@ -243,20 +222,6 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn local_user_view_from_jwt(
jwt: &str,
context: &LemmyContext,
) -> LemmyResult<LocalUserView> {
let local_user_id = Claims::validate(jwt, context)
.await
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
check_user_valid(&local_user_view.person)?;
Ok(local_user_view)
}
#[cfg(test)]
mod tests {

View file

@ -11,11 +11,9 @@ use lemmy_db_schema::{
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_db_views::structs::{LocalUserView, PersonView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn add_admin(
data: Json<AddAdmin>,
context: Data<LemmyContext>,

View file

@ -16,14 +16,12 @@ use lemmy_db_schema::{
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_db_views::structs::{LocalUserView, PersonView};
use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::validation::is_valid_body_field,
};
#[tracing::instrument(skip(context))]
pub async fn ban_from_site(
data: Json<BanPerson>,
context: Data<LemmyContext>,

View file

@ -7,11 +7,9 @@ use lemmy_db_schema::{
source::person_block::{PersonBlock, PersonBlockForm},
traits::Blockable,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_db_views::structs::{LocalUserView, PersonView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn user_block_person(
data: Json<BlockPerson>,
context: Data<LemmyContext>,

View file

@ -13,7 +13,6 @@ use lemmy_db_schema::source::{local_user::LocalUser, login_token::LoginToken};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn change_password(
data: Json<ChangePassword>,
req: HttpRequest,

View file

@ -12,7 +12,6 @@ use lemmy_db_schema::source::{
};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn change_password_after_reset(
data: Json<PasswordChangeAfterReset>,
context: Data<LemmyContext>,

View file

@ -11,7 +11,6 @@ use lemmy_utils::error::{LemmyErrorType, LemmyResult};
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
/// to enable it. This can only be called if 2FA is currently disabled.
#[tracing::instrument(skip(context))]
pub async fn generate_totp_secret(
local_user_view: LocalUserView,
context: Data<LemmyContext>,

View file

@ -20,7 +20,6 @@ use lemmy_db_schema::source::{
};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn get_captcha(context: Data<LemmyContext>) -> LemmyResult<HttpResponse> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let mut res = HttpResponseBuilder::new(StatusCode::OK);

View file

@ -1,7 +1,6 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_db_views::structs::{LocalUserView, PersonView};
use lemmy_utils::error::LemmyResult;
pub async fn list_banned_users(

View file

@ -6,7 +6,6 @@ use lemmy_api_common::{
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_media(
data: Query<ListMedia>,
context: Data<LemmyContext>,

View file

@ -5,13 +5,13 @@ use lemmy_api_common::{
person::{ListPersonSaved, ListPersonSavedResponse},
utils::check_private_instance,
};
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views::{
person_saved_combined_view::PersonSavedCombinedQuery,
structs::{LocalUserView, SiteView},
combined::person_saved_combined_view::PersonSavedCombinedQuery,
structs::{LocalUserView, PersonSavedCombinedView, SiteView},
};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_person_saved(
data: Query<ListPersonSaved>,
context: Data<LemmyContext>,
@ -21,22 +21,21 @@ pub async fn list_person_saved(
check_private_instance(&Some(local_user_view.clone()), &local_site.local_site)?;
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(PersonSavedCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let type_ = data.type_;
let saved = PersonSavedCombinedQuery {
type_,
page_after,
page_back,
type_: data.type_,
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool(), &local_user_view)
.await?;
Ok(Json(ListPersonSavedResponse { saved }))
let next_page = saved.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListPersonSavedResponse { saved, next_page }))
}

View file

@ -13,7 +13,6 @@ use lemmy_api_common::{
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn login(
data: Json<Login>,
req: HttpRequest,

View file

@ -1,12 +1,14 @@
use crate::read_auth_token;
use activitypub_federation::config::Data;
use actix_web::{cookie::Cookie, HttpRequest, HttpResponse};
use lemmy_api_common::{context::LemmyContext, utils::AUTH_COOKIE_NAME, SuccessResponse};
use lemmy_api_common::{
context::LemmyContext,
utils::{read_auth_token, AUTH_COOKIE_NAME},
SuccessResponse,
};
use lemmy_db_schema::source::login_token::LoginToken;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn logout(
req: HttpRequest,
// require login

View file

@ -14,6 +14,7 @@ pub mod login;
pub mod logout;
pub mod notifications;
pub mod report_count;
pub mod resend_verification_email;
pub mod reset_password;
pub mod save_settings;
pub mod update_totp;

View file

@ -3,38 +3,37 @@ use lemmy_api_common::{
context::LemmyContext,
person::{ListInbox, ListInboxResponse},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::inbox_combined_view::InboxCombinedQuery;
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views::{
combined::inbox_combined_view::InboxCombinedQuery,
structs::{InboxCombinedView, LocalUserView},
};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_inbox(
data: Query<ListInbox>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<ListInboxResponse>> {
let unread_only = data.unread_only;
let type_ = data.type_;
let person_id = local_user_view.person.id;
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(InboxCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let inbox = InboxCombinedQuery {
type_,
unread_only,
show_bot_accounts,
page_after,
page_back,
type_: data.type_,
unread_only: data.unread_only,
show_bot_accounts: Some(local_user_view.local_user.show_bot_accounts),
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool(), person_id)
.await?;
Ok(Json(ListInboxResponse { inbox }))
let next_page = inbox.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListInboxResponse { inbox, next_page }))
}

View file

@ -9,7 +9,6 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_all_notifications_read(
context: Data<LemmyContext>,
local_user_view: LocalUserView,

View file

@ -11,7 +11,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_comment_mention_as_read(
data: Json<MarkPersonCommentMentionAsRead>,
context: Data<LemmyContext>,

View file

@ -11,7 +11,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_post_mention_as_read(
data: Json<MarkPersonPostMentionAsRead>,
context: Data<LemmyContext>,

View file

@ -7,7 +7,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_reply_as_read(
data: Json<MarkCommentReplyAsRead>,
context: Data<LemmyContext>,

View file

@ -1,10 +1,8 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::InboxCombinedViewInternal;
use lemmy_db_views::structs::{InboxCombinedViewInternal, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn unread_count(
context: Data<LemmyContext>,
local_user_view: LocalUserView,

View file

@ -7,7 +7,6 @@ use lemmy_api_common::{
use lemmy_db_views::structs::{LocalUserView, ReportCombinedViewInternal};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn report_count(
data: Query<GetReportCount>,
context: Data<LemmyContext>,

View file

@ -0,0 +1,30 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
person::ResendVerificationEmail,
utils::send_verification_email_if_required,
SuccessResponse,
};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::LemmyResult;
pub async fn resend_verification_email(
data: Json<ResendVerificationEmail>,
context: Data<LemmyContext>,
) -> LemmyResult<Json<SuccessResponse>> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let email = data.email.to_string();
// Fetch that email
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email).await?;
send_verification_email_if_required(
&context,
&site_view.local_site,
&local_user_view.local_user,
&local_user_view.person,
)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -9,7 +9,6 @@ use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::LemmyResult;
use tracing::error;
#[tracing::instrument(skip(context))]
pub async fn reset_password(
data: Json<PasswordReset>,
context: Data<LemmyContext>,

View file

@ -3,23 +3,17 @@ use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
person::SaveUserSettings,
utils::{
get_url_blocklist,
local_site_to_slur_regex,
process_markdown_opt,
send_verification_email,
},
utils::{get_url_blocklist, process_markdown_opt, send_verification_email, slur_regex},
SuccessResponse,
};
use lemmy_db_schema::{
source::{
actor_language::LocalUserLanguage,
local_user::{LocalUser, LocalUserUpdateForm},
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeUpdateForm},
person::{Person, PersonUpdateForm},
},
traits::Crud,
utils::diesel_string_update,
utils::{diesel_opt_number_update, diesel_string_update},
};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{
@ -28,7 +22,6 @@ use lemmy_utils::{
};
use std::ops::Deref;
#[tracing::instrument(skip(context))]
pub async fn save_user_settings(
data: Json<SaveUserSettings>,
context: Data<LemmyContext>,
@ -36,7 +29,7 @@ pub async fn save_user_settings(
) -> LemmyResult<Json<SuccessResponse>> {
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 = slur_regex(&context).await?;
let url_blocklist = get_url_blocklist(&context).await?;
let bio = diesel_string_update(
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context)
@ -55,7 +48,9 @@ pub async fn save_user_settings(
if previous_email.deref() != email {
LocalUser::check_is_email_taken(&mut context.pool(), email).await?;
send_verification_email(
&local_user_view,
&site_view.local_site,
&local_user_view.local_user,
&local_user_view.person,
email,
&mut context.pool(),
context.settings(),
@ -91,6 +86,8 @@ pub async fn save_user_settings(
let person_id = local_user_view.person.id;
let default_listing_type = data.default_listing_type;
let default_post_sort_type = data.default_post_sort_type;
let default_post_time_range_seconds =
diesel_opt_number_update(data.default_post_time_range_seconds);
let default_comment_sort_type = data.default_comment_sort_type;
let person_form = PersonUpdateForm {
@ -120,6 +117,7 @@ pub async fn save_user_settings(
blur_nsfw: data.blur_nsfw,
show_bot_accounts: data.show_bot_accounts,
default_post_sort_type,
default_post_time_range_seconds,
default_comment_sort_type,
default_listing_type,
theme: data.theme.clone(),
@ -133,20 +131,15 @@ pub async fn save_user_settings(
collapse_bot_comments: data.collapse_bot_comments,
auto_mark_fetched_posts_as_read: data.auto_mark_fetched_posts_as_read,
hide_media: data.hide_media,
// Update the vote display modes
show_score: data.show_scores,
show_upvotes: data.show_upvotes,
show_downvotes: data.show_downvotes,
show_upvote_percentage: data.show_upvote_percentage,
..Default::default()
};
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;
// Update the vote display modes
let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm {
score: data.show_scores,
upvotes: data.show_upvotes,
downvotes: data.show_downvotes,
upvote_percentage: data.show_upvote_percentage,
};
LocalUserVoteDisplayMode::update(&mut context.pool(), local_user_id, &vote_display_modes_form)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -16,7 +16,6 @@ use lemmy_utils::error::LemmyResult;
///
/// Disabling is only possible if 2FA was previously enabled. Again it is necessary to pass a valid
/// token.
#[tracing::instrument(skip(context))]
pub async fn update_totp(
data: Json<UpdateTotp>,
local_user_view: LocalUserView,

View file

@ -8,7 +8,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn user_block_instance(
data: Json<UserBlockInstanceParams>,
local_user_view: LocalUserView,

View file

@ -1,14 +1,16 @@
use crate::{local_user_view_from_jwt, read_auth_token};
use actix_web::{
web::{Data, Json},
HttpRequest,
};
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
use lemmy_api_common::{
context::LemmyContext,
utils::{local_user_view_from_jwt, read_auth_token},
SuccessResponse,
};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
/// Returns an error message if the auth token is invalid for any reason. Necessary because other
/// endpoints silently treat any call with invalid auth as unauthenticated.
#[tracing::instrument(skip(context))]
pub async fn validate_auth(
req: HttpRequest,
context: Data<LemmyContext>,

View file

@ -19,7 +19,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn feature_post(
data: Json<FeaturePost>,
context: Data<LemmyContext>,

View file

@ -8,7 +8,6 @@ use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use url::Url;
#[tracing::instrument(skip(context))]
pub async fn get_link_metadata(
data: Query<GetSiteMetadata>,
context: Data<LemmyContext>,
@ -16,7 +15,7 @@ pub async fn get_link_metadata(
_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?;
let metadata = fetch_link_metadata(&url, &context, false).await?;
Ok(Json(GetSiteMetadataResponse { metadata }))
}

View file

@ -7,7 +7,6 @@ use lemmy_db_schema::source::post::PostHide;
use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn hide_post(
data: Json<HidePost>,
context: Data<LemmyContext>,

View file

@ -19,7 +19,6 @@ use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use std::ops::Deref;
#[tracing::instrument(skip(context))]
pub async fn like_post(
data: Json<CreatePostLike>,
context: Data<LemmyContext>,

View file

@ -9,7 +9,6 @@ use lemmy_db_views::structs::{LocalUserView, VoteView};
use lemmy_utils::error::LemmyResult;
/// Lists likes for a post
#[tracing::instrument(skip(context))]
pub async fn list_post_likes(
data: Query<ListPostLikes>,
context: Data<LemmyContext>,

View file

@ -17,7 +17,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn lock_post(
data: Json<LockPost>,
context: Data<LemmyContext>,

View file

@ -4,7 +4,6 @@ use lemmy_db_schema::source::post::PostRead;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
#[tracing::instrument(skip(context))]
pub async fn mark_posts_as_read(
data: Json<MarkManyPostsAsRead>,
context: Data<LemmyContext>,

View file

@ -7,7 +7,6 @@ use lemmy_db_schema::source::post::{PostRead, PostReadForm};
use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn mark_post_as_read(
data: Json<MarkPostAsRead>,
context: Data<LemmyContext>,

View file

@ -10,7 +10,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn save_post(
data: Json<SavePost>,
context: Data<LemmyContext>,

View file

@ -11,7 +11,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_pm_as_read(
data: Json<MarkPrivateMessageAsRead>,
context: Data<LemmyContext>,

View file

@ -9,6 +9,7 @@ use lemmy_api_common::{
check_comment_deleted_or_removed,
check_community_user_action,
send_new_report_email_to_admins,
slur_regex,
},
};
use lemmy_db_schema::{
@ -22,16 +23,14 @@ use lemmy_db_views::structs::{CommentReportView, CommentView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Creates a comment report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
pub async fn create_comment_report(
data: Json<CreateCommentReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<CommentReportResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = data.reason.trim().to_string();
check_report_reason(&reason, &local_site)?;
let slur_regex = slur_regex(&context).await?;
check_report_reason(&reason, &slur_regex)?;
let person_id = local_user_view.person.id;
let comment_id = data.comment_id;
@ -57,6 +56,7 @@ pub async fn create_comment_report(
comment_id,
original_comment_text: comment_view.comment.content,
reason,
violates_instance_rules: data.violates_instance_rules.unwrap_or_default(),
};
let report = CommentReport::report(&mut context.pool(), &report_form)
@ -67,6 +67,7 @@ pub async fn create_comment_report(
CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
// Email the admins
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.reports_email_admins {
send_new_report_email_to_admins(
&comment_report_view.creator.name,

View file

@ -1,7 +1,9 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
reports::comment::{CommentReportResponse, ResolveCommentReport},
send_activity::{ActivityChannel, SendActivityData},
utils::check_community_mod_action,
};
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
@ -9,7 +11,6 @@ use lemmy_db_views::structs::{CommentReportView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Resolves or unresolves a comment report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
pub async fn resolve_comment_report(
data: Json<ResolveCommentReport>,
context: Data<LemmyContext>,
@ -42,6 +43,16 @@ pub async fn resolve_comment_report(
let comment_report_view =
CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::SendResolveReport {
object_id: comment_report_view.comment.ap_id.inner().clone(),
actor: local_user_view.person,
report_creator: report.creator,
community: comment_report_view.community.clone(),
},
&context,
)?;
Ok(Json(CommentReportResponse {
comment_report_view,
}))

View file

@ -0,0 +1,71 @@
use crate::check_report_reason;
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
reports::community::{CommunityReportResponse, CreateCommunityReport},
utils::{send_new_report_email_to_admins, slur_regex},
};
use lemmy_db_schema::{
source::{
community::Community,
community_report::{CommunityReport, CommunityReportForm},
local_site::LocalSite,
},
traits::{Crud, Reportable},
};
use lemmy_db_views::structs::{CommunityReportView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
pub async fn create_community_report(
data: Json<CreateCommunityReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityReportResponse>> {
let reason = data.reason.trim().to_string();
let slur_regex = slur_regex(&context).await?;
check_report_reason(&reason, &slur_regex)?;
let person_id = local_user_view.person.id;
let community_id = data.community_id;
let community = Community::read(&mut context.pool(), community_id).await?;
let report_form = CommunityReportForm {
creator_id: person_id,
community_id,
original_community_banner: community.banner,
original_community_description: community.description,
original_community_icon: community.icon,
original_community_name: community.name,
original_community_sidebar: community.sidebar,
original_community_title: community.title,
reason,
};
let report = CommunityReport::report(&mut context.pool(), &report_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let community_report_view =
CommunityReportView::read(&mut context.pool(), report.id, person_id).await?;
// Email the admins
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.reports_email_admins {
send_new_report_email_to_admins(
&community_report_view.creator.name,
// The argument here is normally the reported content's creator, but a community doesn't have
// a single person to be considered the creator or the person responsible for the bad thing,
// so the community name is used instead
&community_report_view.community.name,
&mut context.pool(),
context.settings(),
)
.await?;
}
// TODO: consider federating this
Ok(Json(CommunityReportResponse {
community_report_view,
}))
}

View file

@ -0,0 +1,2 @@
pub mod create;
pub mod resolve;

View file

@ -0,0 +1,36 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
reports::community::{CommunityReportResponse, ResolveCommunityReport},
utils::is_admin,
};
use lemmy_db_schema::{source::community_report::CommunityReport, traits::Reportable};
use lemmy_db_views::structs::{CommunityReportView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
pub async fn resolve_community_report(
data: Json<ResolveCommunityReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityReportResponse>> {
is_admin(&local_user_view)?;
let report_id = data.report_id;
let person_id = local_user_view.person.id;
if data.resolved {
CommunityReport::resolve(&mut context.pool(), report_id, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
} else {
CommunityReport::unresolve(&mut context.pool(), report_id, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
}
let community_report_view =
CommunityReportView::read(&mut context.pool(), report_id, person_id).await?;
Ok(Json(CommunityReportResponse {
community_report_view,
}))
}

View file

@ -1,4 +1,5 @@
pub mod comment_report;
pub mod community_report;
pub mod post_report;
pub mod private_message_report;
pub mod report_combined;

View file

@ -9,6 +9,7 @@ use lemmy_api_common::{
check_community_user_action,
check_post_deleted_or_removed,
send_new_report_email_to_admins,
slur_regex,
},
};
use lemmy_db_schema::{
@ -22,16 +23,14 @@ use lemmy_db_views::structs::{LocalUserView, PostReportView, PostView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Creates a post report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
pub async fn create_post_report(
data: Json<CreatePostReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<PostReportResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = data.reason.trim().to_string();
check_report_reason(&reason, &local_site)?;
let slur_regex = slur_regex(&context).await?;
check_report_reason(&reason, &slur_regex)?;
let person_id = local_user_view.person.id;
let post_id = data.post_id;
@ -53,6 +52,7 @@ pub async fn create_post_report(
original_post_url: post_view.post.url,
original_post_body: post_view.post.body,
reason,
violates_instance_rules: data.violates_instance_rules.unwrap_or_default(),
};
let report = PostReport::report(&mut context.pool(), &report_form)
@ -62,6 +62,7 @@ pub async fn create_post_report(
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?;
// Email the admins
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.reports_email_admins {
send_new_report_email_to_admins(
&post_report_view.creator.name,

View file

@ -1,7 +1,9 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
reports::post::{PostReportResponse, ResolvePostReport},
send_activity::{ActivityChannel, SendActivityData},
utils::check_community_mod_action,
};
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
@ -9,7 +11,6 @@ use lemmy_db_views::structs::{LocalUserView, PostReportView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Resolves or unresolves a post report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
pub async fn resolve_post_report(
data: Json<ResolvePostReport>,
context: Data<LemmyContext>,
@ -33,6 +34,7 @@ pub async fn resolve_post_report(
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
} else {
// TODO: not federated
PostReport::unresolve(&mut context.pool(), report_id, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
@ -40,5 +42,15 @@ pub async fn resolve_post_report(
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::SendResolveReport {
object_id: post_report_view.post.ap_id.inner().clone(),
actor: local_user_view.person,
report_creator: report.creator,
community: post_report_view.community.clone(),
},
&context,
)?;
Ok(Json(PostReportResponse { post_report_view }))
}

View file

@ -3,7 +3,7 @@ use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
reports::private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse},
utils::send_new_report_email_to_admins,
utils::{send_new_report_email_to_admins, slur_regex},
};
use lemmy_db_schema::{
source::{
@ -16,16 +16,14 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn create_pm_report(
data: Json<CreatePrivateMessageReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<PrivateMessageReportResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = data.reason.trim().to_string();
check_report_reason(&reason, &local_site)?;
let slur_regex = slur_regex(&context).await?;
check_report_reason(&reason, &slur_regex)?;
let person_id = local_user_view.person.id;
let private_message_id = data.private_message_id;
@ -51,6 +49,7 @@ pub async fn create_pm_report(
PrivateMessageReportView::read(&mut context.pool(), report.id).await?;
// Email the admins
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.reports_email_admins {
send_new_report_email_to_admins(
&private_message_report_view.creator.name,

View file

@ -8,7 +8,6 @@ use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, trai
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn resolve_pm_report(
data: Json<ResolvePrivateMessageReport>,
context: Data<LemmyContext>,

View file

@ -4,38 +4,47 @@ use lemmy_api_common::{
reports::combined::{ListReports, ListReportsResponse},
utils::check_community_mod_of_any_or_admin_action,
};
use lemmy_db_views::{report_combined_view::ReportCombinedQuery, structs::LocalUserView};
use lemmy_db_schema::traits::PaginationCursorBuilder;
use lemmy_db_views::{
combined::report_combined_view::ReportCombinedQuery,
structs::{LocalUserView, ReportCombinedView},
};
use lemmy_utils::error::LemmyResult;
/// Lists reports for a community if an id is supplied
/// or returns all reports for communities a user moderates
#[tracing::instrument(skip(context))]
pub async fn list_reports(
data: Query<ListReports>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<ListReportsResponse>> {
let community_id = data.community_id;
let unresolved_only = data.unresolved_only;
let my_reports_only = data.my_reports_only;
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
// Only check mod or admin status when not viewing my reports
if !my_reports_only.unwrap_or_default() {
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
}
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(ReportCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let reports = ReportCombinedQuery {
community_id,
unresolved_only,
page_after,
page_back,
community_id: data.community_id,
post_id: data.post_id,
type_: data.type_,
unresolved_only: data.unresolved_only,
cursor_data,
page_back: data.page_back,
show_community_rule_violations: data.show_community_rule_violations,
my_reports_only,
}
.list(&mut context.pool(), &local_user_view)
.await?;
Ok(Json(ListReportsResponse { reports }))
let next_page = reports.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(ListReportsResponse { reports, next_page }))
}

View file

@ -18,7 +18,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn admin_allow_instance(
data: Json<AdminAllowInstanceParams>,
local_user_view: LocalUserView,

View file

@ -18,7 +18,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn admin_block_instance(
data: Json<AdminBlockInstanceParams>,
local_user_view: LocalUserView,

View file

@ -7,7 +7,6 @@ use lemmy_api_common::{
use lemmy_db_views::structs::SiteView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn get_federated_instances(
context: Data<LemmyContext>,
) -> LemmyResult<Json<GetFederatedInstancesResponse>> {

View file

@ -12,14 +12,12 @@ use lemmy_db_schema::{
},
traits::Crud,
};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_db_views_actor::structs::PersonView;
use lemmy_db_views::structs::{LocalUserView, PersonView, SiteView};
use lemmy_utils::{
error::{LemmyErrorType, LemmyResult},
VERSION,
};
#[tracing::instrument(skip(context))]
pub async fn leave_admin(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
@ -71,8 +69,8 @@ pub async fn leave_admin(
version: VERSION.to_string(),
all_languages,
discussion_languages,
oauth_providers: Some(oauth_providers),
admin_oauth_providers: None,
oauth_providers,
admin_oauth_providers: vec![],
blocked_urls,
tagline,
my_user: None,

View file

@ -7,7 +7,6 @@ use lemmy_api_common::{
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_all_media(
data: Query<ListMedia>,
context: Data<LemmyContext>,

View file

@ -4,12 +4,13 @@ use lemmy_api_common::{
site::{GetModlog, GetModlogResponse},
utils::{check_community_mod_of_any_or_admin_action, check_private_instance},
};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_moderator::{self, modlog_combined_view::ModlogCombinedQuery};
use lemmy_db_schema::{source::local_site::LocalSite, traits::PaginationCursorBuilder};
use lemmy_db_views::{
combined::modlog_combined_view::ModlogCombinedQuery,
structs::{LocalUserView, ModlogCombinedView},
};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn get_mod_log(
data: Query<GetModlog>,
context: Data<LemmyContext>,
@ -19,11 +20,8 @@ pub async fn get_mod_log(
check_private_instance(&local_user_view, &local_site)?;
let type_ = data.type_;
let community_id = data.community_id;
let is_mod_or_admin = if let Some(local_user_view) = local_user_view {
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool())
let is_mod_or_admin = if let Some(local_user_view) = &local_user_view {
check_community_mod_of_any_or_admin_action(local_user_view, &mut context.pool())
.await
.is_ok()
} else {
@ -36,31 +34,30 @@ pub async fn get_mod_log(
} else {
data.mod_person_id
};
let other_person_id = data.other_person_id;
let post_id = data.post_id;
let comment_id = data.comment_id;
// parse pagination token
let page_after = if let Some(pa) = &data.page_cursor {
Some(pa.read(&mut context.pool()).await?)
let cursor_data = if let Some(cursor) = &data.page_cursor {
Some(ModlogCombinedView::from_cursor(cursor, &mut context.pool()).await?)
} else {
None
};
let page_back = data.page_back;
let modlog = ModlogCombinedQuery {
type_,
community_id,
type_: data.type_,
listing_type: data.listing_type,
community_id: data.community_id,
mod_person_id,
other_person_id,
post_id,
comment_id,
other_person_id: data.other_person_id,
local_user: local_user_view.as_ref().map(|u| &u.local_user),
post_id: data.post_id,
comment_id: data.comment_id,
hide_modlog_names: Some(hide_modlog_names),
page_after,
page_back,
cursor_data,
page_back: data.page_back,
}
.list(&mut context.pool())
.await?;
Ok(Json(GetModlogResponse { modlog }))
let next_page = modlog.last().map(PaginationCursorBuilder::to_cursor);
Ok(Json(GetModlogResponse { modlog, next_page }))
}

View file

@ -18,7 +18,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_comment(
data: Json<PurgeComment>,
context: Data<LemmyContext>,

View file

@ -17,11 +17,9 @@ use lemmy_db_schema::{
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_db_views::structs::{CommunityModeratorView, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_community(
data: Json<PurgeCommunity>,
context: Data<LemmyContext>,

View file

@ -19,7 +19,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_person(
data: Json<PurgePerson>,
context: Data<LemmyContext>,

View file

@ -2,10 +2,9 @@ use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
request::purge_image_from_pictrs,
send_activity::{ActivityChannel, SendActivityData},
site::PurgePost,
utils::is_admin,
utils::{is_admin, purge_post_images},
SuccessResponse,
};
use lemmy_db_schema::{
@ -19,7 +18,6 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_post(
data: Json<PurgePost>,
context: Data<LemmyContext>,
@ -39,14 +37,7 @@ pub async fn purge_post(
)
.await?;
// Purge image
if let Some(url) = &post.url {
purge_image_from_pictrs(url, &context).await.ok();
}
// Purge thumbnail
if let Some(thumbnail_url) = &post.thumbnail_url {
purge_image_from_pictrs(thumbnail_url, &context).await.ok();
}
purge_post_images(post.url.clone(), post.thumbnail_url.clone(), &context).await;
Post::delete(&mut context.pool(), data.post_id).await?;

View file

@ -7,7 +7,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::{
registration_application_view::RegistrationApplicationQuery,
registration_applications::registration_application_view::RegistrationApplicationQuery,
structs::LocalUserView,
};
use lemmy_utils::error::LemmyResult;

View file

@ -19,10 +19,7 @@ workspace = true
[features]
full = [
"tracing",
"rosetta-i18n",
"lemmy_db_views/full",
"lemmy_db_views_actor/full",
"lemmy_db_views_moderator/full",
"lemmy_utils/full",
"activitypub_federation",
"encoding_rs",
@ -37,12 +34,12 @@ full = [
"jsonwebtoken",
"mime",
"moka",
"actix-web-httpauth",
"webmention",
]
[dependencies]
lemmy_db_views = { workspace = true }
lemmy_db_views_moderator = { workspace = true }
lemmy_db_views_actor = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_utils = { workspace = true }
activitypub_federation = { workspace = true, optional = true }
@ -53,7 +50,6 @@ chrono = { workspace = true }
tracing = { workspace = true, optional = true }
reqwest-middleware = { workspace = true, optional = true }
regex = { workspace = true }
rosetta-i18n = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
uuid = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
@ -61,17 +57,19 @@ reqwest = { workspace = true, optional = true }
ts-rs = { workspace = true, optional = true }
moka = { workspace = true, optional = true }
anyhow.workspace = true
actix-web = { workspace = true, optional = true }
enum-map = { workspace = true }
actix-web = { workspace = true, optional = true }
urlencoding = { workspace = true }
mime = { version = "0.3.17", optional = true }
mime_guess = "2.0.5"
infer = "0.16.0"
infer = "0.19.0"
webpage = { version = "2.0", default-features = false, optional = true, features = [
"serde",
] }
encoding_rs = { version = "0.8.35", optional = true }
jsonwebtoken = { version = "9.3.0", optional = true }
jsonwebtoken = { version = "9.3.1", optional = true }
actix-web-httpauth = { version = "0.8.2", optional = true }
webmention = { version = "0.6.0", optional = true }
[dev-dependencies]
serial_test = { workspace = true }

View file

@ -3,12 +3,7 @@ use crate::{
community::CommunityResponse,
context::LemmyContext,
post::PostResponse,
utils::{
check_person_instance_community_block,
get_interface_language,
is_mod_or_admin,
send_email_to_user,
},
utils::{check_person_instance_community_block, is_mod_or_admin, send_email_to_user},
};
use actix_web::web::Json;
use lemmy_db_schema::{
@ -25,8 +20,7 @@ use lemmy_db_schema::{
},
traits::Crud,
};
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_db_views::structs::{CommentView, CommunityView, LocalUserView, PostView};
use lemmy_utils::{
error::LemmyResult,
utils::{markdown::markdown_to_html, mention::MentionData},
@ -92,7 +86,7 @@ pub async fn build_post_response(
}
// TODO: this function is a mess and should be split up to handle email separately
#[tracing::instrument(skip_all)]
pub async fn send_local_notifs(
mentions: Vec<MentionData>,
post_or_comment_id: PostOrCommentId,
@ -102,7 +96,6 @@ pub async fn send_local_notifs(
local_user_view: Option<&LocalUserView>,
) -> LemmyResult<Vec<LocalUserId>> {
let mut recipient_ids = Vec::new();
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
let (comment_opt, post, community) = match post_or_comment_id {
PostOrCommentId::Post(post_id) => {
@ -142,6 +135,8 @@ pub async fn send_local_notifs(
}
};
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
// Send the local mentions
for mention in mentions
.iter()
@ -157,7 +152,7 @@ pub async fn send_local_notifs(
recipient_ids.push(mention_user_view.local_user.id);
// Make the correct reply form depending on whether its a post or comment mention
let comment_content_or_post_body = if let Some(comment) = &comment_opt {
let (link, comment_content_or_post_body) = if let Some(comment) = &comment_opt {
let person_comment_mention_form = PersonCommentMentionInsertForm {
recipient_id: mention_user_view.person.id,
comment_id: comment.id,
@ -169,7 +164,10 @@ pub async fn send_local_notifs(
PersonCommentMention::create(&mut context.pool(), &person_comment_mention_form)
.await
.ok();
comment.content.clone()
(
comment.local_url(context.settings())?,
comment.content.clone(),
)
} else {
let person_post_mention_form = PersonPostMentionInsertForm {
recipient_id: mention_user_view.person.id,
@ -181,17 +179,20 @@ pub async fn send_local_notifs(
PersonPostMention::create(&mut context.pool(), &person_post_mention_form)
.await
.ok();
post.body.clone().unwrap_or_default()
(
post.local_url(context.settings())?,
post.body.clone().unwrap_or_default(),
)
};
// Send an email to those local users that have notifications on
if do_send_email {
let lang = get_interface_language(&mention_user_view);
let lang = &mention_user_view.local_user.interface_i18n_language();
let content = markdown_to_html(&comment_content_or_post_body);
send_email_to_user(
&mention_user_view,
&lang.notification_mentioned_by_subject(&person.name),
&lang.notification_mentioned_by_body(&content, &inbox_link, &person.name),
&lang.notification_mentioned_by_body(&link, &content, &inbox_link, &person.name),
context.settings(),
)
.await
@ -239,12 +240,19 @@ pub async fn send_local_notifs(
.ok();
if do_send_email {
let lang = get_interface_language(&parent_user_view);
let lang = &parent_user_view.local_user.interface_i18n_language();
let content = markdown_to_html(&comment.content);
send_email_to_user(
&parent_user_view,
&lang.notification_comment_reply_subject(&person.name),
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
&lang.notification_comment_reply_body(
comment.local_url(context.settings())?,
&content,
&inbox_link,
&parent_comment.content,
&post.name,
&person.name,
),
context.settings(),
)
.await
@ -285,12 +293,18 @@ pub async fn send_local_notifs(
.ok();
if do_send_email {
let lang = get_interface_language(&parent_user_view);
let lang = &parent_user_view.local_user.interface_i18n_language();
let content = markdown_to_html(&comment.content);
send_email_to_user(
&parent_user_view,
&lang.notification_post_reply_subject(&person.name),
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
&lang.notification_post_reply_body(
comment.local_url(context.settings())?,
&content,
&inbox_link,
&post.name,
&person.name,
),
context.settings(),
)
.await

View file

@ -3,7 +3,7 @@ use lemmy_db_schema::{
CommentSortType,
ListingType,
};
use lemmy_db_views::structs::{CommentView, VoteView};
use lemmy_db_views::structs::{CommentSlimView, CommentView, VoteView};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@ -117,6 +117,10 @@ pub struct GetComments {
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<CommentSortType>,
#[cfg_attr(feature = "full", ts(optional))]
/// Filter to within a given time range, in seconds.
/// IE 60 would give results for the past minute.
pub time_range_seconds: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub max_depth: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>,
@ -144,6 +148,14 @@ pub struct GetCommentsResponse {
pub comments: Vec<CommentView>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// A slimmer comment list response, without the post or community.
pub struct GetCommentsSlimResponse {
pub comments: Vec<CommentSlimView>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]

View file

@ -4,7 +4,7 @@ use lemmy_db_schema::{
CommunityVisibility,
ListingType,
};
use lemmy_db_views_actor::structs::{
use lemmy_db_views::structs::{
CommunityModeratorView,
CommunitySortType,
CommunityView,
@ -97,6 +97,10 @@ pub struct ListCommunities {
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<CommunitySortType>,
#[cfg_attr(feature = "full", ts(optional))]
/// Filter to within a given time range, in seconds.
/// IE 60 would give results for the past minute.
pub time_range_seconds: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub show_nsfw: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>,

View file

@ -24,8 +24,6 @@ pub mod utils;
pub extern crate lemmy_db_schema;
pub extern crate lemmy_db_views;
pub extern crate lemmy_db_views_actor;
pub extern crate lemmy_db_views_moderator;
pub extern crate lemmy_utils;
pub use lemmy_utils::error::LemmyErrorType;
@ -35,7 +33,7 @@ use std::{cmp::min, time::Duration};
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(ts_rs::TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Saves settings for your user.
/// A response that completes successfully.
pub struct SuccessResponse {
pub success: bool,
}

View file

@ -3,6 +3,7 @@ use lemmy_db_schema::{
CommentReplyId,
CommunityId,
LanguageId,
PaginationCursor,
PersonCommentMentionId,
PersonId,
PersonPostMentionId,
@ -17,15 +18,11 @@ use lemmy_db_schema::{
PostSortType,
};
use lemmy_db_views::structs::{
LocalImageView,
PersonContentCombinedPaginationCursor,
PersonContentCombinedView,
PersonSavedCombinedPaginationCursor,
};
use lemmy_db_views_actor::structs::{
CommunityModeratorView,
InboxCombinedPaginationCursor,
InboxCombinedView,
LocalImageView,
PersonContentCombinedView,
PersonSavedCombinedView,
PersonView,
};
use serde::{Deserialize, Serialize};
@ -122,6 +119,9 @@ pub struct SaveUserSettings {
/// The default post sort, usually "active"
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_sort_type: Option<PostSortType>,
/// A default time range limit to apply to post sorts, in seconds. 0 means none.
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_time_range_seconds: Option<i32>,
/// The default comment sort, usually "hot"
#[cfg_attr(feature = "full", ts(optional))]
pub default_comment_sort_type: Option<CommentSortType>,
@ -263,7 +263,7 @@ pub struct ListPersonContent {
#[cfg_attr(feature = "full", ts(optional))]
pub username: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PersonContentCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -275,6 +275,9 @@ pub struct ListPersonContent {
/// A person's content response.
pub struct ListPersonContentResponse {
pub content: Vec<PersonContentCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[skip_serializing_none]
@ -286,7 +289,7 @@ pub struct ListPersonSaved {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<PersonContentType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PersonSavedCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -297,7 +300,10 @@ pub struct ListPersonSaved {
#[cfg_attr(feature = "full", ts(export))]
/// A person's saved content response.
pub struct ListPersonSavedResponse {
pub saved: Vec<PersonContentCombinedView>,
pub saved: Vec<PersonSavedCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq)]
@ -385,7 +391,7 @@ pub struct ListInbox {
#[cfg_attr(feature = "full", ts(optional))]
pub unread_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<InboxCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -396,6 +402,9 @@ pub struct ListInbox {
/// Get your inbox (replies, comment mentions, post mentions, and messages)
pub struct ListInboxResponse {
pub inbox: Vec<InboxCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
@ -534,3 +543,11 @@ pub struct ListMediaResponse {
pub struct ListLoginsResponse {
pub logins: Vec<LoginToken>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Make a request to resend your verification email.
pub struct ResendVerificationEmail {
pub email: SensitiveString,
}

View file

@ -4,8 +4,7 @@ use lemmy_db_schema::{
PostFeatureType,
PostSortType,
};
use lemmy_db_views::structs::{PaginationCursor, PostView, VoteView};
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
use lemmy_db_views::structs::{CommunityView, PostPaginationCursor, PostView, VoteView};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@ -72,7 +71,6 @@ pub struct GetPost {
pub struct GetPostResponse {
pub post_view: PostView,
pub community_view: CommunityView,
pub moderators: Vec<CommunityModeratorView>,
/// A list of cross-posts, or other times / communities this link has been posted to.
pub cross_posts: Vec<PostView>,
}
@ -87,6 +85,11 @@ pub struct GetPosts {
pub type_: Option<ListingType>,
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<PostSortType>,
#[cfg_attr(feature = "full", ts(optional))]
/// Filter to within a given time range, in seconds.
/// IE 60 would give results for the past minute.
/// Use Zero to override the local_site and local_user time_range.
pub time_range_seconds: Option<i32>,
/// DEPRECATED, use page_cursor
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>,
@ -122,7 +125,7 @@ pub struct GetPosts {
/// If true, then only show posts with no comments
pub no_comments_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PaginationCursor>,
pub page_cursor: Option<PostPaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
}
@ -136,7 +139,7 @@ pub struct GetPostsResponse {
pub posts: Vec<PostView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
pub next_page: Option<PostPaginationCursor>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]

View file

@ -1,5 +1,5 @@
use lemmy_db_schema::newtypes::{PersonId, PrivateMessageId};
use lemmy_db_views_actor::structs::PrivateMessageView;
use lemmy_db_views::structs::PrivateMessageView;
use serde::{Deserialize, Serialize};
#[cfg(feature = "full")]
use ts_rs::TS;

View file

@ -1,5 +1,8 @@
use lemmy_db_schema::newtypes::CommunityId;
use lemmy_db_views::structs::{ReportCombinedPaginationCursor, ReportCombinedView};
use lemmy_db_schema::{
newtypes::{CommunityId, PaginationCursor, PostId},
ReportType,
};
use lemmy_db_views::structs::ReportCombinedView;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
@ -14,13 +17,25 @@ pub struct ListReports {
/// Only shows the unresolved reports
#[cfg_attr(feature = "full", ts(optional))]
pub unresolved_only: Option<bool>,
/// Filter the type of report.
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<ReportType>,
/// Filter by the post id. Can return either comment or post reports.
#[cfg_attr(feature = "full", ts(optional))]
pub post_id: Option<PostId>,
/// if no community is given, it returns reports for all communities moderated by the auth user
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<ReportCombinedPaginationCursor>,
pub page_cursor: Option<PaginationCursor>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_back: Option<bool>,
/// Only for admins: also show reports with `violates_instance_rules=false`
#[cfg_attr(feature = "full", ts(optional))]
pub show_community_rule_violations: Option<bool>,
/// If true, view all your created reports. Works for non-admins/mods also.
#[cfg_attr(feature = "full", ts(optional))]
pub my_reports_only: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -29,4 +44,7 @@ pub struct ListReports {
/// The post reports response.
pub struct ListReportsResponse {
pub reports: Vec<ReportCombinedView>,
/// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>,
}

Some files were not shown because too many files have changed in this diff Show more