Merge branch 'main' into language-checks

This commit is contained in:
Felix Ableitner 2024-11-12 11:02:59 +01:00
commit dc6ee00d86
252 changed files with 5856 additions and 2948 deletions

View file

@ -73,12 +73,12 @@ steps:
when: when:
- event: pull_request - event: pull_request
cargo_machete: cargo_shear:
image: *rust_nightly_image image: *rust_nightly_image
commands: commands:
- *install_binstall - *install_binstall
- cargo binstall -y cargo-machete - cargo binstall -y cargo-shear
- cargo machete - cargo shear
when: when:
- event: pull_request - event: pull_request
@ -122,7 +122,6 @@ steps:
environment: environment:
CARGO_HOME: .cargo_home CARGO_HOME: .cargo_home
commands: commands:
- export LEMMY_CONFIG_LOCATION=./config/config.hjson
- ./scripts/update_config_defaults.sh config/defaults_current.hjson - ./scripts/update_config_defaults.sh config/defaults_current.hjson
- diff config/defaults.hjson config/defaults_current.hjson - diff config/defaults.hjson config/defaults_current.hjson
when: *slow_check_paths when: *slow_check_paths
@ -134,8 +133,8 @@ steps:
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
commands: commands:
- <<: *install_diesel_cli - <<: *install_diesel_cli
- cp crates/db_schema/src/schema.rs tmp.schema
- diesel migration run - diesel migration run
- diesel print-schema --config-file=diesel.toml > tmp.schema
- diff tmp.schema crates/db_schema/src/schema.rs - diff tmp.schema crates/db_schema/src/schema.rs
when: *slow_check_paths when: *slow_check_paths
@ -147,7 +146,6 @@ steps:
CARGO_HOME: .cargo_home CARGO_HOME: .cargo_home
commands: commands:
# same as scripts/db_perf.sh but without creating a new database server # same as scripts/db_perf.sh but without creating a new database server
- export LEMMY_CONFIG_LOCATION=config/config.hjson
- cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1 - cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1
when: *slow_check_paths when: *slow_check_paths
@ -176,11 +174,20 @@ steps:
RUST_BACKTRACE: "1" RUST_BACKTRACE: "1"
CARGO_HOME: .cargo_home CARGO_HOME: .cargo_home
LEMMY_TEST_FAST_FEDERATION: "1" LEMMY_TEST_FAST_FEDERATION: "1"
LEMMY_CONFIG_LOCATION: ../../config/config.hjson
commands: commands:
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
- cargo test --workspace --no-fail-fast - cargo test --workspace --no-fail-fast
when: *slow_check_paths when: *slow_check_paths
check_ts_bindings:
image: *rust_image
environment:
CARGO_HOME: .cargo_home
commands:
- ./scripts/ts_bindings_check.sh
when:
- event: pull_request
check_diesel_migration: check_diesel_migration:
# TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server # TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server
image: *rust_image image: *rust_image
@ -215,7 +222,7 @@ steps:
when: *slow_check_paths when: *slow_check_paths
run_federation_tests: run_federation_tests:
image: node:20-bookworm-slim image: node:22-bookworm-slim
environment: environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432 LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
DO_WRITE_HOSTS_FILE: "1" DO_WRITE_HOSTS_FILE: "1"

80
Cargo.lock generated
View file

@ -2,12 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]] [[package]]
name = "accept-language" name = "accept-language"
version = "3.1.0" version = "3.1.0"
@ -1287,6 +1281,15 @@ dependencies = [
"tokio-postgres", "tokio-postgres",
] ]
[[package]]
name = "diesel-bind-if-some"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ed8ce9db476124d2eaf4c9db45dc6581b8e8c4c4d47d5e0f39de1fb55dfb2a7"
dependencies = [
"diesel",
]
[[package]] [[package]]
name = "diesel-derive-enum" name = "diesel-derive-enum"
version = "2.1.0" version = "2.1.0"
@ -2570,7 +2573,6 @@ dependencies = [
"futures", "futures",
"html2md", "html2md",
"html2text", "html2text",
"http 1.1.0",
"itertools 0.13.0", "itertools 0.13.0",
"lemmy_api_common", "lemmy_api_common",
"lemmy_db_schema", "lemmy_db_schema",
@ -2620,6 +2622,7 @@ dependencies = [
"derive-new", "derive-new",
"diesel", "diesel",
"diesel-async", "diesel-async",
"diesel-bind-if-some",
"diesel-derive-enum", "diesel-derive-enum",
"diesel-derive-newtype", "diesel-derive-newtype",
"diesel_ltree", "diesel_ltree",
@ -2641,6 +2644,7 @@ dependencies = [
"tokio-postgres-rustls", "tokio-postgres-rustls",
"tracing", "tracing",
"ts-rs", "ts-rs",
"tuplex",
"url", "url",
"uuid", "uuid",
] ]
@ -2809,9 +2813,12 @@ dependencies = [
"itertools 0.13.0", "itertools 0.13.0",
"lettre", "lettre",
"markdown-it", "markdown-it",
"markdown-it-block-spoiler",
"markdown-it-ruby",
"markdown-it-sub",
"markdown-it-sup",
"pretty_assertions", "pretty_assertions",
"regex", "regex",
"reqwest 0.12.8",
"reqwest-middleware", "reqwest-middleware",
"rosetta-build", "rosetta-build",
"rosetta-i18n", "rosetta-i18n",
@ -2980,6 +2987,44 @@ dependencies = [
"unicode-general-category", "unicode-general-category",
] ]
[[package]]
name = "markdown-it-block-spoiler"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008a8e4184fd08b5dca0f2b5b2ef8f126c1e83ca797c44ee41f8d7765951360c"
dependencies = [
"itertools 0.13.0",
"markdown-it",
]
[[package]]
name = "markdown-it-ruby"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3505f4ada7c372e7f5eb4b07850bf5921193bc0bd43cb18991233999c9134d4"
dependencies = [
"itertools 0.13.0",
"markdown-it",
]
[[package]]
name = "markdown-it-sub"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8abe3aa8927af2314644b3aae37393241a229e869ff9c95ac640749e08357d2a"
dependencies = [
"markdown-it",
]
[[package]]
name = "markdown-it-sup"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ae949e78c7a615f88a47019d51b65962bfc5c4cbc65fa81eae8b9b2506d1cb1"
dependencies = [
"markdown-it",
]
[[package]] [[package]]
name = "markup5ever" name = "markup5ever"
version = "0.11.0" version = "0.11.0"
@ -5215,28 +5260,35 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "ts-rs" name = "ts-rs"
version = "7.1.1" version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2cae1fc5d05d47aa24b64f9a4f7cba24cdc9187a2084dd97ac57bef5eccae6" checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9"
dependencies = [ dependencies = [
"chrono", "chrono",
"lazy_static",
"thiserror", "thiserror",
"ts-rs-macros", "ts-rs-macros",
"url",
] ]
[[package]] [[package]]
name = "ts-rs-macros" name = "ts-rs-macros"
version = "7.1.1" version = "10.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f7f9b821696963053a89a7bd8b292dc34420aea8294d7b225274d488f3ec92" checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
dependencies = [ dependencies = [
"Inflector",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.77", "syn 2.0.77",
"termcolor", "termcolor",
] ]
[[package]]
name = "tuplex"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.17.0" version = "1.17.0"
@ -5579,7 +5631,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]

View file

@ -24,11 +24,12 @@ doctest = false
[lints] [lints]
workspace = true workspace = true
# See https://github.com/johnthagen/min-sized-rust for additional optimizations
[profile.release] [profile.release]
debug = 0 debug = 0
lto = "thin" lto = "fat"
strip = true # Automatically strip symbols from the binary. opt-level = 3 # Optimize for speed, not size.
opt-level = "z" # Optimize for size. codegen-units = 1 # Reduce parallel code generation.
# This profile significantly speeds up build time. If debug info is needed you can comment the line # This profile significantly speeds up build time. If debug info is needed you can comment the line
# out temporarily, but make sure to leave this in the main branch. # out temporarily, but make sure to leave this in the main branch.
@ -77,6 +78,7 @@ uninlined_format_args = "allow"
unused_self = "deny" unused_self = "deny"
unwrap_used = "deny" unwrap_used = "deny"
unimplemented = "deny" unimplemented = "deny"
unused_async = "deny"
[workspace.dependencies] [workspace.dependencies]
lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" } lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" }
@ -141,10 +143,11 @@ itertools = "0.13.0"
futures = "0.3.30" futures = "0.3.30"
http = "1.1" http = "1.1"
rosetta-i18n = "0.1.3" rosetta-i18n = "0.1.3"
ts-rs = { version = "7.1.1", features = [ ts-rs = { version = "10.0.0", features = [
"serde-compat", "serde-compat",
"chrono-impl", "chrono-impl",
"no-serde-warnings", "no-serde-warnings",
"url-impl",
] } ] }
rustls = { version = "0.23.12", features = ["ring"] } rustls = { version = "0.23.12", features = ["ring"] }
futures-util = "0.3.30" futures-util = "0.3.30"
@ -157,6 +160,8 @@ i-love-jesus = { version = "0.1.0" }
clap = { version = "4.5.13", features = ["derive", "env"] } clap = { version = "4.5.13", features = ["derive", "env"] }
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
derive-new = "0.7.0" derive-new = "0.7.0"
diesel-bind-if-some = "0.1.0"
tuplex = "0.1.2"
[dependencies] [dependencies]
lemmy_api = { workspace = true } lemmy_api = { workspace = true }

View file

@ -6,16 +6,17 @@
"repository": "https://github.com/LemmyNet/lemmy", "repository": "https://github.com/LemmyNet/lemmy",
"author": "Dessalines", "author": "Dessalines",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"packageManager": "pnpm@9.12.0", "packageManager": "pnpm@9.12.3",
"scripts": { "scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'", "lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'",
"fix": "prettier --write src && eslint --fix src", "fix": "prettier --write src && eslint --fix src",
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ", "api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i private_community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
"api-test-follow": "jest -i follow.spec.ts", "api-test-follow": "jest -i follow.spec.ts",
"api-test-comment": "jest -i comment.spec.ts", "api-test-comment": "jest -i comment.spec.ts",
"api-test-post": "jest -i post.spec.ts", "api-test-post": "jest -i post.spec.ts",
"api-test-user": "jest -i user.spec.ts", "api-test-user": "jest -i user.spec.ts",
"api-test-community": "jest -i community.spec.ts", "api-test-community": "jest -i community.spec.ts",
"api-test-private-community": "jest -i private_community.spec.ts",
"api-test-private-message": "jest -i private_message.spec.ts", "api-test-private-message": "jest -i private_message.spec.ts",
"api-test-image": "jest -i image.spec.ts" "api-test-image": "jest -i image.spec.ts"
}, },
@ -27,7 +28,7 @@
"eslint": "^9.9.0", "eslint": "^9.9.0",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"jest": "^29.5.0", "jest": "^29.5.0",
"lemmy-js-client": "0.20.0-alpha.11", "lemmy-js-client": "0.20.0-private-community.9",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"typescript": "^5.5.4", "typescript": "^5.5.4",

File diff suppressed because it is too large Load diff

View file

@ -158,16 +158,16 @@ test("Delete a comment", async () => {
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true); expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
expect(deleteCommentRes.comment_view.comment.content).toBe(""); expect(deleteCommentRes.comment_view.comment.content).toBe("");
// Make sure that comment is undefined on beta // Make sure that comment is deleted on beta
await waitUntil( await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), () => resolveComment(beta, commentRes.comment_view.comment),
e => e.message == "not_found", c => c.comment?.comment.deleted === true,
); );
// Make sure that comment is undefined on gamma after delete // Make sure that comment is deleted on gamma after delete
await waitUntil( await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e), () => resolveComment(gamma, commentRes.comment_view.comment),
e => e.message === "not_found", c => c.comment?.comment.deleted === true,
); );
// Test undeleting the comment // Test undeleting the comment
@ -181,11 +181,10 @@ test("Delete a comment", async () => {
// Make sure that comment is undeleted on beta // Make sure that comment is undeleted on beta
let betaComment2 = ( let betaComment2 = (
await waitUntil( await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e), () => resolveComment(beta, commentRes.comment_view.comment),
e => e.message !== "not_found", c => c.comment?.comment.deleted === false,
) )
).comment; ).comment;
expect(betaComment2?.comment.deleted).toBe(false);
assertCommentFederation(betaComment2, undeleteCommentRes.comment_view); assertCommentFederation(betaComment2, undeleteCommentRes.comment_view);
}); });
@ -861,7 +860,7 @@ test("Dont send a comment reply to a blocked community", async () => {
/// Fetching a deeply nested comment can lead to stack overflow as all parent comments are also /// Fetching a deeply nested comment can lead to stack overflow as all parent comments are also
/// fetched recursively. Ensure that it works properly. /// fetched recursively. Ensure that it works properly.
test("Fetch a deeply nested comment", async () => { test.skip("Fetch a deeply nested comment", async () => {
let lastComment; let lastComment;
for (let i = 0; i < 50; i++) { for (let i = 0; i < 50; i++) {
let commentRes = await createComment( let commentRes = await createComment(

View file

@ -0,0 +1,214 @@
jest.setTimeout(120000);
import { FollowCommunity } from "lemmy-js-client";
import {
alpha,
setupLogins,
createCommunity,
unfollows,
registerUser,
listCommunityPendingFollows,
getCommunity,
getCommunityPendingFollowsCount,
approveCommunityPendingFollow,
randomString,
createPost,
createComment,
beta,
resolveCommunity,
betaUrl,
resolvePost,
resolveComment,
likeComment,
waitUntil,
} from "./shared";
beforeAll(setupLogins);
afterAll(unfollows);
test("Follow a private community", async () => {
// create private community
const community = await createCommunity(alpha, randomString(10), "Private");
expect(community.community_view.community.visibility).toBe("Private");
const alphaCommunityId = community.community_view.community.id;
// No pending follows yet
const pendingFollows0 = await listCommunityPendingFollows(alpha);
expect(pendingFollows0.items.length).toBe(0);
const pendingFollowsCount0 = await getCommunityPendingFollowsCount(
alpha,
alphaCommunityId,
);
expect(pendingFollowsCount0.count).toBe(0);
// follow as new user
const user = await registerUser(beta, betaUrl);
const betaCommunity = (
await resolveCommunity(user, community.community_view.community.actor_id)
).community;
expect(betaCommunity).toBeDefined();
const betaCommunityId = betaCommunity!.community.id;
const follow_form: FollowCommunity = {
community_id: betaCommunityId,
follow: true,
};
await user.followCommunity(follow_form);
// Follow listed as pending
const follow1 = await getCommunity(user, betaCommunityId);
expect(follow1.community_view.subscribed).toBe("ApprovalRequired");
// Wait for follow to federate, shown as pending
let pendingFollows1 = await waitUntil(
() => listCommunityPendingFollows(alpha),
f => f.items.length == 1,
);
expect(pendingFollows1.items[0].is_new_instance).toBe(true);
const pendingFollowsCount1 = await getCommunityPendingFollowsCount(
alpha,
alphaCommunityId,
);
expect(pendingFollowsCount1.count).toBe(1);
// user still sees approval required at this point
const betaCommunity2 = await getCommunity(user, betaCommunityId);
expect(betaCommunity2.community_view.subscribed).toBe("ApprovalRequired");
// Approve the follow
const approve = await approveCommunityPendingFollow(
alpha,
alphaCommunityId,
pendingFollows1.items[0].person.id,
);
expect(approve.success).toBe(true);
// Follow is confirmed
await waitUntil(
() => getCommunity(user, betaCommunityId),
c => c.community_view.subscribed == "Subscribed",
);
const pendingFollows2 = await listCommunityPendingFollows(alpha);
expect(pendingFollows2.items.length).toBe(0);
const pendingFollowsCount2 = await getCommunityPendingFollowsCount(
alpha,
alphaCommunityId,
);
expect(pendingFollowsCount2.count).toBe(0);
// follow with another user from that instance, is_new_instance should be false now
const user2 = await registerUser(beta, betaUrl);
await user2.followCommunity(follow_form);
let pendingFollows3 = await waitUntil(
() => listCommunityPendingFollows(alpha),
f => f.items.length == 1,
);
expect(pendingFollows3.items[0].is_new_instance).toBe(false);
// cleanup pending follow
const approve2 = await approveCommunityPendingFollow(
alpha,
alphaCommunityId,
pendingFollows3.items[0].person.id,
);
expect(approve2.success).toBe(true);
});
test("Only followers can view and interact with private community content", async () => {
// create private community
const community = await createCommunity(alpha, randomString(10), "Private");
expect(community.community_view.community.visibility).toBe("Private");
const alphaCommunityId = community.community_view.community.id;
// create post and comment
const post0 = await createPost(alpha, alphaCommunityId);
const post_id = post0.post_view.post.id;
expect(post_id).toBeDefined();
const comment = await createComment(alpha, post_id);
const comment_id = comment.comment_view.comment.id;
expect(comment_id).toBeDefined();
// user is not following the community and cannot view nor create posts
const user = await registerUser(beta, betaUrl);
const betaCommunity = (
await resolveCommunity(user, community.community_view.community.actor_id)
).community!.community;
await expect(resolvePost(user, post0.post_view.post)).rejects.toStrictEqual(
Error("not_found"),
);
await expect(
resolveComment(user, comment.comment_view.comment),
).rejects.toStrictEqual(Error("not_found"));
await expect(createPost(user, betaCommunity.id)).rejects.toStrictEqual(
Error("not_found"),
);
// follow the community and approve
const follow_form: FollowCommunity = {
community_id: betaCommunity.id,
follow: true,
};
await user.followCommunity(follow_form);
const pendingFollows1 = await waitUntil(
() => listCommunityPendingFollows(alpha),
f => f.items.length == 1,
);
const approve = await approveCommunityPendingFollow(
alpha,
alphaCommunityId,
pendingFollows1.items[0].person.id,
);
expect(approve.success).toBe(true);
// now user can fetch posts and comments in community (using signed fetch), and create posts
await waitUntil(
() => resolvePost(user, post0.post_view.post),
p => p?.post?.post.id != undefined,
);
const resolvedComment = (
await resolveComment(user, comment.comment_view.comment)
).comment;
expect(resolvedComment?.comment.id).toBeDefined();
const post1 = await createPost(user, betaCommunity.id);
expect(post1.post_view).toBeDefined();
const like = await likeComment(user, 1, resolvedComment!.comment);
expect(like.comment_view.my_vote).toBe(1);
});
test("Reject follower", async () => {
// create private community
const community = await createCommunity(alpha, randomString(10), "Private");
expect(community.community_view.community.visibility).toBe("Private");
const alphaCommunityId = community.community_view.community.id;
// user is not following the community and cannot view nor create posts
const user = await registerUser(beta, betaUrl);
const betaCommunity1 = (
await resolveCommunity(user, community.community_view.community.actor_id)
).community!.community;
// follow the community and reject
const follow_form: FollowCommunity = {
community_id: betaCommunity1.id,
follow: true,
};
const follow = await user.followCommunity(follow_form);
expect(follow.community_view.subscribed).toBe("ApprovalRequired");
const pendingFollows1 = await waitUntil(
() => listCommunityPendingFollows(alpha),
f => f.items.length == 1,
);
const approve = await approveCommunityPendingFollow(
alpha,
alphaCommunityId,
pendingFollows1.items[0].person.id,
false,
);
expect(approve.success).toBe(true);
await waitUntil(
() => getCommunity(user, betaCommunity1.id),
c => c.community_view.subscribed == "NotSubscribed",
);
});

View file

@ -1,17 +1,24 @@
import { import {
ApproveCommunityPendingFollower,
BlockCommunity, BlockCommunity,
BlockCommunityResponse, BlockCommunityResponse,
BlockInstance, BlockInstance,
BlockInstanceResponse, BlockInstanceResponse,
CommunityId, CommunityId,
CommunityVisibility,
CreatePrivateMessageReport, CreatePrivateMessageReport,
DeleteImage, DeleteImage,
EditCommunity, EditCommunity,
GetCommunityPendingFollowsCount,
GetCommunityPendingFollowsCountResponse,
GetReplies, GetReplies,
GetRepliesResponse, GetRepliesResponse,
GetUnreadCountResponse, GetUnreadCountResponse,
InstanceId, InstanceId,
LemmyHttp, LemmyHttp,
ListCommunityPendingFollows,
ListCommunityPendingFollowsResponse,
PersonId,
PostView, PostView,
PrivateMessageReportResponse, PrivateMessageReportResponse,
SuccessResponse, SuccessResponse,
@ -83,7 +90,7 @@ export const fetchFunction = fetch;
export const imageFetchLimit = 50; export const imageFetchLimit = 50;
export const sampleImage = export const sampleImage =
"https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg"; "https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg";
export const sampleSite = "https://yahoo.com"; export const sampleSite = "https://w3.org";
export const alphaUrl = "http://127.0.0.1:8541"; export const alphaUrl = "http://127.0.0.1:8541";
export const betaUrl = "http://127.0.0.1:8551"; export const betaUrl = "http://127.0.0.1:8551";
@ -198,7 +205,7 @@ export async function setupLogins() {
// only needed the first time so do in this try // only needed the first time so do in this try
await delay(10_000); await delay(10_000);
} catch { } catch {
console.log("Communities already exist"); //console.log("Communities already exist");
} }
} }
@ -554,12 +561,14 @@ export async function likeComment(
export async function createCommunity( export async function createCommunity(
api: LemmyHttp, api: LemmyHttp,
name_: string = randomString(10), name_: string = randomString(10),
visibility: CommunityVisibility = "Public",
): Promise<CommunityResponse> { ): Promise<CommunityResponse> {
let description = "a sample description"; let description = "a sample description";
let form: CreateCommunity = { let form: CreateCommunity = {
name: name_, name: name_,
title: name_, title: name_,
description, description,
visibility,
}; };
return api.createCommunity(form); return api.createCommunity(form);
} }
@ -688,7 +697,6 @@ export async function saveUserSettingsBio(
let form: SaveUserSettings = { let form: SaveUserSettings = {
show_nsfw: true, show_nsfw: true,
blur_nsfw: false, blur_nsfw: false,
auto_expand: true,
theme: "darkly", theme: "darkly",
default_post_sort_type: "Active", default_post_sort_type: "Active",
default_listing_type: "All", default_listing_type: "All",
@ -709,7 +717,6 @@ export async function saveUserSettingsFederated(
let form: SaveUserSettings = { let form: SaveUserSettings = {
show_nsfw: false, show_nsfw: false,
blur_nsfw: true, blur_nsfw: true,
auto_expand: false,
default_post_sort_type: "Hot", default_post_sort_type: "Hot",
default_listing_type: "All", default_listing_type: "All",
interface_language: "", interface_language: "",
@ -872,6 +879,39 @@ export function blockCommunity(
return api.blockCommunity(form); return api.blockCommunity(form);
} }
export function listCommunityPendingFollows(
api: LemmyHttp,
): Promise<ListCommunityPendingFollowsResponse> {
let form: ListCommunityPendingFollows = {
pending_only: true,
all_communities: false,
page: 1,
limit: 50,
};
return api.listCommunityPendingFollows(form);
}
export function getCommunityPendingFollowsCount(
api: LemmyHttp,
community_id: CommunityId,
): Promise<GetCommunityPendingFollowsCountResponse> {
return api.getCommunityPendingFollowsCount(community_id);
}
export function approveCommunityPendingFollow(
api: LemmyHttp,
community_id: CommunityId,
follower_id: PersonId,
approve: boolean = true,
): Promise<SuccessResponse> {
let form: ApproveCommunityPendingFollower = {
community_id,
follower_id,
approve,
};
return api.approveCommunityPendingFollow(form);
}
export function delay(millis = 500) { export function delay(millis = 500) {
return new Promise(resolve => setTimeout(resolve, millis)); return new Promise(resolve => setTimeout(resolve, millis));
} }
@ -962,8 +1002,12 @@ export async function waitUntil<T>(
let retry = 0; let retry = 0;
let result; let result;
while (retry++ < retries) { while (retry++ < retries) {
result = await fetcher(); try {
if (checker(result)) return result; result = await fetcher();
if (checker(result)) return result;
} catch (error) {
//console.error(error);
}
await delay( await delay(
delaySeconds[Math.min(retry - 1, delaySeconds.length - 1)] * 1000, delaySeconds[Math.min(retry - 1, delaySeconds.length - 1)] * 1000,
); );

View file

@ -76,7 +76,7 @@
# Timeout for uploading images to pictrs (in seconds) # Timeout for uploading images to pictrs (in seconds)
upload_timeout: 30 upload_timeout: 30
# Resize post thumbnails to this maximum width/height. # Resize post thumbnails to this maximum width/height.
max_thumbnail_size: 256 max_thumbnail_size: 512
} }
# Email sending configuration. All options except login/password are mandatory # Email sending configuration. All options except login/password are mandatory
email: { email: {

View file

@ -26,7 +26,7 @@ pub async fn distinguish_comment(
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
orig_comment.community.id, &orig_comment.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
@ -39,7 +39,7 @@ pub async fn distinguish_comment(
// Verify that only a mod or admin can distinguish a comment // Verify that only a mod or admin can distinguish a comment
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
orig_comment.community.id, &orig_comment.community,
false, false,
&mut context.pool(), &mut context.pool(),
) )

View file

@ -50,7 +50,7 @@ pub async fn like_comment(
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
orig_comment.community.id, &orig_comment.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
@ -67,7 +67,6 @@ pub async fn like_comment(
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: data.comment_id, comment_id: data.comment_id,
post_id: orig_comment.post.id,
person_id: local_user_view.person.id, person_id: local_user_view.person.id,
score: data.score, score: data.score,
}; };
@ -93,8 +92,7 @@ pub async fn like_comment(
score: data.score, score: data.score,
}, },
&context, &context,
) )?;
.await?;
Ok(Json( Ok(Json(
build_comment_response( build_comment_response(

View file

@ -44,7 +44,7 @@ pub async fn create_comment_report(
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
comment_view.community.id, &comment_view.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
@ -85,8 +85,7 @@ pub async fn create_comment_report(
reason: data.reason.clone(), reason: data.reason.clone(),
}, },
&context, &context,
) )?;
.await?;
Ok(Json(CommentReportResponse { Ok(Json(CommentReportResponse {
comment_report_view, comment_report_view,

View file

@ -22,7 +22,7 @@ pub async fn resolve_comment_report(
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
report.community.id, &report.community,
true, true,
&mut context.pool(), &mut context.pool(),
) )

View file

@ -24,12 +24,11 @@ pub async fn add_mod_to_community(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<AddModToCommunityResponse>> { ) -> LemmyResult<Json<AddModToCommunityResponse>> {
let community_id = data.community_id; let community = Community::read(&mut context.pool(), data.community_id).await?;
// Verify that only mods or admins can add mod // Verify that only mods or admins can add mod
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
community_id, &community,
false, false,
&mut context.pool(), &mut context.pool(),
) )
@ -39,15 +38,13 @@ pub async fn add_mod_to_community(
if !data.added { if !data.added {
LocalUser::is_higher_mod_or_admin_check( LocalUser::is_higher_mod_or_admin_check(
&mut context.pool(), &mut context.pool(),
community_id, community.id,
local_user_view.person.id, local_user_view.person.id,
vec![data.person_id], vec![data.person_id],
) )
.await?; .await?;
} }
let community = Community::read(&mut context.pool(), community_id).await?;
// If user is admin and community is remote, explicitly check that he is a // If user is admin and community is remote, explicitly check that he is a
// moderator. This is necessary because otherwise the action would be rejected // moderator. This is necessary because otherwise the action would be rejected
// by the community's home instance. // by the community's home instance.
@ -98,8 +95,7 @@ pub async fn add_mod_to_community(
added: data.added, added: data.added,
}, },
&context, &context,
) )?;
.await?;
Ok(Json(AddModToCommunityResponse { moderators })) Ok(Json(AddModToCommunityResponse { moderators }))
} }

View file

@ -13,6 +13,7 @@ use lemmy_api_common::{
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::{ community::{
Community,
CommunityFollower, CommunityFollower,
CommunityFollowerForm, CommunityFollowerForm,
CommunityPersonBan, CommunityPersonBan,
@ -38,11 +39,12 @@ pub async fn ban_from_community(
) -> LemmyResult<Json<BanFromCommunityResponse>> { ) -> LemmyResult<Json<BanFromCommunityResponse>> {
let banned_person_id = data.person_id; let banned_person_id = data.person_id;
let expires = check_expire_time(data.expires)?; let expires = check_expire_time(data.expires)?;
let community = Community::read(&mut context.pool(), data.community_id).await?;
// Verify that only mods or admins can ban // Verify that only mods or admins can ban
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
data.community_id, &community,
false, false,
&mut context.pool(), &mut context.pool(),
) )
@ -72,12 +74,7 @@ pub async fn ban_from_community(
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?; .with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
// Also unsubscribe them from the community, if they are subscribed // Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm::new(data.community_id, banned_person_id);
community_id: data.community_id,
person_id: banned_person_id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await .await
.ok(); .ok();
@ -123,8 +120,7 @@ pub async fn ban_from_community(
data: data.0.clone(), data: data.0.clone(),
}, },
&context, &context,
) )?;
.await?;
Ok(Json(BanFromCommunityResponse { Ok(Json(BanFromCommunityResponse {
person_view, person_view,

View file

@ -35,12 +35,7 @@ pub async fn block_community(
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?; .with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
// Also, unfollow the community, and send a federated unfollow // Also, unfollow the community, and send a federated unfollow
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm::new(data.community_id, person_id);
community_id: data.community_id,
person_id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await .await
.ok(); .ok();
@ -65,8 +60,7 @@ pub async fn block_community(
false, false,
), ),
&context, &context,
) )?;
.await?;
Ok(Json(BlockCommunityResponse { Ok(Json(BlockCommunityResponse {
blocked: data.block, blocked: data.block,

View file

@ -4,17 +4,18 @@ use lemmy_api_common::{
community::{CommunityResponse, FollowCommunity}, community::{CommunityResponse, FollowCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::check_community_user_action, utils::{check_community_deleted_removed, check_user_valid},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
actor_language::CommunityLanguage, actor_language::CommunityLanguage,
community::{Community, CommunityFollower, CommunityFollowerForm}, community::{Community, CommunityFollower, CommunityFollowerForm, CommunityFollowerState},
}, },
traits::{Crud, Followable}, traits::{Crud, Followable},
CommunityVisibility,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityView; use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
@ -23,40 +24,52 @@ pub async fn follow_community(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityResponse>> { ) -> LemmyResult<Json<CommunityResponse>> {
check_user_valid(&local_user_view.person)?;
let community = Community::read(&mut context.pool(), data.community_id).await?; let community = Community::read(&mut context.pool(), data.community_id).await?;
let mut community_follower_form = CommunityFollowerForm { let form = CommunityFollowerForm::new(community.id, local_user_view.person.id);
community_id: community.id,
person_id: local_user_view.person.id,
pending: false,
};
if data.follow { if data.follow {
// Only run these checks for local community, in case of remote community the local
// state may be outdated. Can't use check_community_user_action() here as it only allows
// actions from existing followers for private community (so following would be impossible).
if community.local { if community.local {
check_community_user_action(&local_user_view.person, community.id, &mut context.pool()) check_community_deleted_removed(&community)?;
CommunityPersonBanView::check(&mut context.pool(), local_user_view.person.id, community.id)
.await?; .await?;
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} else {
// Mark as pending, the actual federation activity is sent via `SendActivity` handler
community_follower_form.pending = true;
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} }
let state = if community.local {
// Local follow is accepted immediately
Some(CommunityFollowerState::Accepted)
} else if community.visibility == CommunityVisibility::Private {
// Private communities require manual approval
Some(CommunityFollowerState::ApprovalRequired)
} else {
// remote follow needs to be federated first
Some(CommunityFollowerState::Pending)
};
let form = CommunityFollowerForm {
state,
..CommunityFollowerForm::new(community.id, local_user_view.person.id)
};
// Write to db
CommunityFollower::follow(&mut context.pool(), &form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} else { } else {
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) CommunityFollower::unfollow(&mut context.pool(), &form)
.await .await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} }
// Send the federated follow
if !community.local { if !community.local {
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::FollowCommunity(community, local_user_view.person.clone(), data.follow), SendActivityData::FollowCommunity(community, local_user_view.person.clone(), data.follow),
&context, &context,
) )?;
.await?;
} }
let community_id = data.community_id; let community_id = data.community_id;

View file

@ -48,8 +48,7 @@ pub async fn hide_community(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community), SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
&context, &context,
) )?;
.await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -3,5 +3,6 @@ pub mod ban;
pub mod block; pub mod block;
pub mod follow; pub mod follow;
pub mod hide; pub mod hide;
pub mod pending_follows;
pub mod random; pub mod random;
pub mod transfer; pub mod transfer;

View file

@ -0,0 +1,46 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
community::ApproveCommunityPendingFollower,
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::is_mod_or_admin,
SuccessResponse,
};
use lemmy_db_schema::{
source::community::{CommunityFollower, CommunityFollowerForm},
traits::Followable,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
pub async fn post_pending_follows_approve(
data: Json<ApproveCommunityPendingFollower>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<SuccessResponse>> {
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
data.community_id,
)
.await?;
let activity_data = if data.approve {
CommunityFollower::approve(
&mut context.pool(),
data.community_id,
data.follower_id,
local_user_view.person.id,
)
.await?;
SendActivityData::AcceptFollower(data.community_id, data.follower_id)
} else {
let form = CommunityFollowerForm::new(data.community_id, data.follower_id);
CommunityFollower::unfollow(&mut context.pool(), &form).await?;
SendActivityData::RejectFollower(data.community_id, data.follower_id)
};
ActivityChannel::submit_activity(activity_data, &context)?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -0,0 +1,25 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
community::{GetCommunityPendingFollowsCount, GetCommunityPendingFollowsCountResponse},
context::LemmyContext,
utils::is_mod_or_admin,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityFollowerView;
use lemmy_utils::error::LemmyResult;
pub async fn get_pending_follows_count(
data: Query<GetCommunityPendingFollowsCount>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<GetCommunityPendingFollowsCountResponse>> {
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
data.community_id,
)
.await?;
let count =
CommunityFollowerView::count_approval_required(&mut context.pool(), data.community_id).await?;
Ok(Json(GetCommunityPendingFollowsCountResponse { count }))
}

View file

@ -0,0 +1,29 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
community::{ListCommunityPendingFollows, ListCommunityPendingFollowsResponse},
context::LemmyContext,
utils::check_community_mod_of_any_or_admin_action,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityFollowerView;
use lemmy_utils::error::LemmyResult;
pub async fn get_pending_follows_list(
data: Query<ListCommunityPendingFollows>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<ListCommunityPendingFollowsResponse>> {
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
let all_communities =
data.all_communities.unwrap_or_default() && local_user_view.local_user.admin;
let items = CommunityFollowerView::list_approval_required(
&mut context.pool(),
local_user_view.person.id,
all_communities,
data.pending_only.unwrap_or_default(),
data.page,
data.limit,
)
.await?;
Ok(Json(ListCommunityPendingFollowsResponse { items }))
}

View file

@ -0,0 +1,3 @@
pub mod approve;
pub mod count;
pub mod list;

View file

@ -7,7 +7,7 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::{CommunityModerator, CommunityModeratorForm}, community::{Community, CommunityModerator, CommunityModeratorForm},
moderator::{ModTransferCommunity, ModTransferCommunityForm}, moderator::{ModTransferCommunity, ModTransferCommunityForm},
}, },
traits::{Crud, Joinable}, traits::{Crud, Joinable},
@ -27,11 +27,11 @@ pub async fn transfer_community(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<GetCommunityResponse>> { ) -> LemmyResult<Json<GetCommunityResponse>> {
let community_id = data.community_id; let community = Community::read(&mut context.pool(), data.community_id).await?;
let mut community_mods = let mut community_mods =
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; CommunityModeratorView::for_community(&mut context.pool(), community.id).await?;
check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?; check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?;
// Make sure transferrer is either the top community mod, or an admin // Make sure transferrer is either the top community mod, or an admin
if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok()) if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok())

View file

@ -197,11 +197,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
.ok(); .ok();
// Also unsubscribe them from the community, if they are subscribed // Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm::new(community_id, target.id);
community_id,
person_id: target.id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await .await
@ -242,8 +238,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
data: ban_from_community, data: ban_from_community,
}, },
context, context,
) )?;
.await?;
} }
} }

View file

@ -111,8 +111,7 @@ pub async fn ban_from_site(
expires: data.expires, expires: data.expires,
}, },
&context, &context,
) )?;
.await?;
Ok(Json(BanPersonResponse { Ok(Json(BanPersonResponse {
person_view, person_view,

View file

@ -141,6 +141,7 @@ pub async fn save_user_settings(
post_listing_mode: data.post_listing_mode, post_listing_mode: data.post_listing_mode,
enable_keyboard_navigation: data.enable_keyboard_navigation, enable_keyboard_navigation: data.enable_keyboard_navigation,
enable_animated_images: data.enable_animated_images, enable_animated_images: data.enable_animated_images,
enable_private_messages: data.enable_private_messages,
collapse_bot_comments: data.collapse_bot_comments, collapse_bot_comments: data.collapse_bot_comments,
..Default::default() ..Default::default()
}; };

View file

@ -9,6 +9,7 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::Community,
moderator::{ModFeaturePost, ModFeaturePostForm}, moderator::{ModFeaturePost, ModFeaturePostForm},
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
}, },
@ -27,9 +28,10 @@ pub async fn feature_post(
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?; let orig_post = Post::read(&mut context.pool(), post_id).await?;
let community = Community::read(&mut context.pool(), orig_post.community_id).await?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
orig_post.community_id, &community,
false, false,
&mut context.pool(), &mut context.pool(),
) )
@ -67,8 +69,7 @@ pub async fn feature_post(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::FeaturePost(post, local_user_view.person.clone(), data.featured), SendActivityData::FeaturePost(post, local_user_view.person.clone(), data.featured),
&context, &context,
) )?;
.await?;
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
} }

View file

@ -5,10 +5,7 @@ use lemmy_api_common::{
request::fetch_link_metadata, request::fetch_link_metadata,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{ use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
error::{LemmyErrorExt, LemmyResult},
LemmyErrorType,
};
use url::Url; use url::Url;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]

View file

@ -15,13 +15,12 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::Community,
local_site::LocalSite, local_site::LocalSite,
post::{Post, PostLike, PostLikeForm}, post::{PostLike, PostLikeForm},
}, },
traits::{Crud, Likeable}, traits::Likeable,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use std::ops::Deref; use std::ops::Deref;
@ -45,11 +44,11 @@ pub async fn like_post(
check_bot_account(&local_user_view.person)?; check_bot_account(&local_user_view.person)?;
// Check for a community ban // Check for a community ban
let post = Post::read(&mut context.pool(), post_id).await?; let post = PostView::read(&mut context.pool(), post_id, None, false).await?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
post.community_id, &post.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
@ -75,18 +74,15 @@ pub async fn like_post(
mark_post_as_read(person_id, post_id, &mut context.pool()).await?; mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
let community = Community::read(&mut context.pool(), post.community_id).await?;
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::LikePostOrComment { SendActivityData::LikePostOrComment {
object_id: post.ap_id, object_id: post.post.ap_id,
actor: local_user_view.person.clone(), actor: local_user_view.person.clone(),
community, community: post.community.clone(),
score: data.score, score: data.score,
}, },
&context, &context,
) )?;
.await?;
build_post_response(context.deref(), post.community_id, local_user_view, post_id).await build_post_response(context.deref(), post.community.id, local_user_view, post_id).await
} }

View file

@ -14,7 +14,7 @@ use lemmy_db_schema::{
}, },
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
@ -24,11 +24,11 @@ pub async fn lock_post(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> { ) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?; let orig_post = PostView::read(&mut context.pool(), post_id, None, false).await?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
orig_post.community_id, &orig_post.community,
false, false,
&mut context.pool(), &mut context.pool(),
) )
@ -58,8 +58,7 @@ pub async fn lock_post(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::LockPost(post, local_user_view.person.clone(), data.locked), SendActivityData::LockPost(post, local_user_view.person.clone(), data.locked),
&context, &context,
) )?;
.await?;
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await build_post_response(&context, orig_post.community.id, local_user_view, post_id).await
} }

View file

@ -39,7 +39,7 @@ pub async fn create_post_report(
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
post_view.community.id, &post_view.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
@ -80,8 +80,7 @@ pub async fn create_post_report(
reason: data.reason.clone(), reason: data.reason.clone(),
}, },
&context, &context,
) )?;
.await?;
Ok(Json(PostReportResponse { post_report_view })) Ok(Json(PostReportResponse { post_report_view }))
} }

View file

@ -22,7 +22,7 @@ pub async fn resolve_post_report(
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
report.community.id, &report.community,
true, true,
&mut context.pool(), &mut context.pool(),
) )

View file

@ -67,8 +67,7 @@ pub async fn purge_comment(
reason: data.reason.clone(), reason: data.reason.clone(),
}, },
&context, &context,
) )?;
.await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -75,8 +75,7 @@ pub async fn purge_community(
removed: true, removed: true,
}, },
&context, &context,
) )?;
.await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -80,8 +80,7 @@ pub async fn purge_person(
expires: None, expires: None,
}, },
&context, &context,
) )?;
.await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -66,8 +66,7 @@ pub async fn purge_post(
removed: true, removed: true,
}, },
&context, &context,
) )?;
.await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -31,7 +31,10 @@ use lemmy_db_schema::{
RegistrationMode, RegistrationMode,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API}; use lemmy_utils::{
error::{LemmyErrorType, LemmyResult},
CACHE_DURATION_API,
};
use serial_test::serial; use serial_test::serial;
async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> { async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> {

View file

@ -9,14 +9,12 @@ use lemmy_utils::error::LemmyResult;
use sitemap_rs::{url::Url, url_set::UrlSet}; use sitemap_rs::{url::Url, url_set::UrlSet};
use tracing::info; use tracing::info;
async fn generate_urlset( fn generate_urlset(posts: Vec<(DbUrl, chrono::DateTime<chrono::Utc>)>) -> LemmyResult<UrlSet> {
posts: Vec<(DbUrl, chrono::DateTime<chrono::Utc>)>,
) -> LemmyResult<UrlSet> {
let urls = posts let urls = posts
.into_iter() .into_iter()
.map_while(|post| { .map_while(|(url, date_time)| {
Url::builder(post.0.to_string()) Url::builder(url.to_string())
.last_modified(post.1.into()) .last_modified(date_time.into())
.build() .build()
.ok() .ok()
}) })
@ -31,7 +29,7 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
info!("Loaded latest {} posts", posts.len()); info!("Loaded latest {} posts", posts.len());
let mut buf = Vec::<u8>::new(); let mut buf = Vec::<u8>::new();
generate_urlset(posts).await?.write(&mut buf)?; generate_urlset(posts)?.write(&mut buf)?;
Ok( Ok(
HttpResponse::Ok() HttpResponse::Ok()
@ -74,7 +72,7 @@ pub(crate) mod tests {
]; ];
let mut buf = Vec::<u8>::new(); let mut buf = Vec::<u8>::new();
generate_urlset(posts).await?.write(&mut buf)?; generate_urlset(posts)?.write(&mut buf)?;
let root = Element::from_reader(buf.as_slice())?; let root = Element::from_reader(buf.as_slice())?;
assert_eq!(root.tag().name(), "urlset"); assert_eq!(root.tag().name(), "urlset");

View file

@ -72,7 +72,7 @@ jsonwebtoken = { version = "9.3.0", optional = true }
# necessary for wasmt compilation # necessary for wasmt compilation
getrandom = { version = "0.2.15", features = ["js"] } getrandom = { version = "0.2.15", features = ["js"] }
[package.metadata.cargo-machete] [package.metadata.cargo-shear]
ignored = ["getrandom"] ignored = ["getrandom"]
[dev-dependencies] [dev-dependencies]

View file

@ -92,7 +92,7 @@ mod tests {
#[tokio::test] #[tokio::test]
#[serial] #[serial]
async fn test_should_not_validate_user_token_after_password_change() -> LemmyResult<()> { async fn test_should_not_validate_user_token_after_password_change() -> LemmyResult<()> {
let pool_ = build_db_pool_for_tests().await; let pool_ = build_db_pool_for_tests();
let pool = &mut (&pool_).into(); let pool = &mut (&pool_).into();
let secret = Secret::init(pool).await?; let secret = Secret::init(pool).await?;
let context = LemmyContext::create( let context = LemmyContext::create(

View file

@ -17,7 +17,9 @@ use ts_rs::TS;
pub struct CreateComment { pub struct CreateComment {
pub content: String, pub content: String,
pub post_id: PostId, pub post_id: PostId,
#[cfg_attr(feature = "full", ts(optional))]
pub parent_id: Option<CommentId>, pub parent_id: Option<CommentId>,
#[cfg_attr(feature = "full", ts(optional))]
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
} }
@ -37,7 +39,9 @@ pub struct GetComment {
/// Edit a comment. /// Edit a comment.
pub struct EditComment { pub struct EditComment {
pub comment_id: CommentId, pub comment_id: CommentId,
#[cfg_attr(feature = "full", ts(optional))]
pub content: Option<String>, pub content: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
} }
@ -69,6 +73,7 @@ pub struct DeleteComment {
pub struct RemoveComment { pub struct RemoveComment {
pub comment_id: CommentId, pub comment_id: CommentId,
pub removed: bool, pub removed: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -107,17 +112,29 @@ pub struct CreateCommentLike {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a list of comments. /// Get a list of comments.
pub struct GetComments { pub struct GetComments {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<ListingType>, pub type_: Option<ListingType>,
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<CommentSortType>, pub sort: Option<CommentSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub max_depth: Option<i32>, pub max_depth: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_name: Option<String>, pub community_name: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub post_id: Option<PostId>, pub post_id: Option<PostId>,
#[cfg_attr(feature = "full", ts(optional))]
pub parent_id: Option<CommentId>, pub parent_id: Option<CommentId>,
#[cfg_attr(feature = "full", ts(optional))]
pub saved_only: Option<bool>, pub saved_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub liked_only: Option<bool>, pub liked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub disliked_only: Option<bool>, pub disliked_only: Option<bool>,
} }
@ -161,12 +178,17 @@ pub struct ResolveCommentReport {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// List comment reports. /// List comment reports.
pub struct ListCommentReports { pub struct ListCommentReports {
#[cfg_attr(feature = "full", ts(optional))]
pub comment_id: Option<CommentId>, pub comment_id: Option<CommentId>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
/// Only shows the unresolved reports /// Only shows the unresolved reports
#[cfg_attr(feature = "full", ts(optional))]
pub unresolved_only: Option<bool>, pub unresolved_only: Option<bool>,
/// if no community is given, it returns reports for all communities moderated by the auth user /// if no community is given, it returns reports for all communities moderated by the auth user
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
} }
@ -185,7 +207,9 @@ pub struct ListCommentReportsResponse {
/// List comment likes. Admins-only. /// List comment likes. Admins-only.
pub struct ListCommentLikes { pub struct ListCommentLikes {
pub comment_id: CommentId, pub comment_id: CommentId,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
} }

View file

@ -8,6 +8,7 @@ use lemmy_db_views_actor::structs::{
CommunityModeratorView, CommunityModeratorView,
CommunitySortType, CommunitySortType,
CommunityView, CommunityView,
PendingFollow,
PersonView, PersonView,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -19,10 +20,13 @@ use ts_rs::TS;
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
// TODO make this into a tagged enum
/// Get a community. Must provide either an id, or a name. /// Get a community. Must provide either an id, or a name.
pub struct GetCommunity { pub struct GetCommunity {
#[cfg_attr(feature = "full", ts(optional))]
pub id: Option<CommunityId>, pub id: Option<CommunityId>,
/// Example: star_trek , or star_trek@xyz.tld /// Example: star_trek , or star_trek@xyz.tld
#[cfg_attr(feature = "full", ts(optional))]
pub name: Option<String>, pub name: Option<String>,
} }
@ -33,6 +37,7 @@ pub struct GetCommunity {
/// The community response. /// The community response.
pub struct GetCommunityResponse { pub struct GetCommunityResponse {
pub community_view: CommunityView, pub community_view: CommunityView,
#[cfg_attr(feature = "full", ts(optional))]
pub site: Option<Site>, pub site: Option<Site>,
pub moderators: Vec<CommunityModeratorView>, pub moderators: Vec<CommunityModeratorView>,
pub discussion_languages: Vec<LanguageId>, pub discussion_languages: Vec<LanguageId>,
@ -48,17 +53,27 @@ pub struct CreateCommunity {
pub name: String, pub name: String,
/// A longer title. /// A longer title.
pub title: String, pub title: String,
/// A longer sidebar, or description of your community, in markdown. /// A sidebar for the community in markdown.
#[cfg_attr(feature = "full", ts(optional))]
pub sidebar: Option<String>,
/// A shorter, one line description of your community.
#[cfg_attr(feature = "full", ts(optional))]
pub description: Option<String>, pub description: Option<String>,
/// An icon URL. /// An icon URL.
#[cfg_attr(feature = "full", ts(optional))]
pub icon: Option<String>, pub icon: Option<String>,
/// A banner URL. /// A banner URL.
#[cfg_attr(feature = "full", ts(optional))]
pub banner: Option<String>, pub banner: Option<String>,
/// Whether its an NSFW community. /// Whether its an NSFW community.
#[cfg_attr(feature = "full", ts(optional))]
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
/// Whether to restrict posting only to moderators. /// Whether to restrict posting only to moderators.
#[cfg_attr(feature = "full", ts(optional))]
pub posting_restricted_to_mods: Option<bool>, pub posting_restricted_to_mods: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub discussion_languages: Option<Vec<LanguageId>>, pub discussion_languages: Option<Vec<LanguageId>>,
#[cfg_attr(feature = "full", ts(optional))]
pub visibility: Option<CommunityVisibility>, pub visibility: Option<CommunityVisibility>,
} }
@ -77,10 +92,15 @@ pub struct CommunityResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of communities. /// Fetches a list of communities.
pub struct ListCommunities { pub struct ListCommunities {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<ListingType>, pub type_: Option<ListingType>,
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<CommunitySortType>, pub sort: Option<CommunitySortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub show_nsfw: Option<bool>, pub show_nsfw: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
} }
@ -103,11 +123,14 @@ pub struct BanFromCommunity {
pub ban: bool, pub ban: bool,
/// Optionally remove or restore all their data. Useful for new troll accounts. /// Optionally remove or restore all their data. Useful for new troll accounts.
/// If ban is true, then this means remove. If ban is false, it means restore. /// If ban is true, then this means remove. If ban is false, it means restore.
#[cfg_attr(feature = "full", ts(optional))]
pub remove_or_restore_data: Option<bool>, pub remove_or_restore_data: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
/// A time that the ban will expire, in unix epoch seconds. /// A time that the ban will expire, in unix epoch seconds.
/// ///
/// An i64 unix timestamp is used for a simpler API client implementation. /// An i64 unix timestamp is used for a simpler API client implementation.
#[cfg_attr(feature = "full", ts(optional))]
pub expires: Option<i64>, pub expires: Option<i64>,
} }
@ -146,18 +169,29 @@ pub struct AddModToCommunityResponse {
pub struct EditCommunity { pub struct EditCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
/// A longer title. /// A longer title.
#[cfg_attr(feature = "full", ts(optional))]
pub title: Option<String>, pub title: Option<String>,
/// A longer sidebar, or description of your community, in markdown. /// A sidebar for the community in markdown.
#[cfg_attr(feature = "full", ts(optional))]
pub sidebar: Option<String>,
/// A shorter, one line description of your community.
#[cfg_attr(feature = "full", ts(optional))]
pub description: Option<String>, pub description: Option<String>,
/// An icon URL. /// An icon URL.
#[cfg_attr(feature = "full", ts(optional))]
pub icon: Option<String>, pub icon: Option<String>,
/// A banner URL. /// A banner URL.
#[cfg_attr(feature = "full", ts(optional))]
pub banner: Option<String>, pub banner: Option<String>,
/// Whether its an NSFW community. /// Whether its an NSFW community.
#[cfg_attr(feature = "full", ts(optional))]
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
/// Whether to restrict posting only to moderators. /// Whether to restrict posting only to moderators.
#[cfg_attr(feature = "full", ts(optional))]
pub posting_restricted_to_mods: Option<bool>, pub posting_restricted_to_mods: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub discussion_languages: Option<Vec<LanguageId>>, pub discussion_languages: Option<Vec<LanguageId>>,
#[cfg_attr(feature = "full", ts(optional))]
pub visibility: Option<CommunityVisibility>, pub visibility: Option<CommunityVisibility>,
} }
@ -169,6 +203,7 @@ pub struct EditCommunity {
pub struct HideCommunity { pub struct HideCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
pub hidden: bool, pub hidden: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -190,6 +225,7 @@ pub struct DeleteCommunity {
pub struct RemoveCommunity { pub struct RemoveCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
pub removed: bool, pub removed: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -236,5 +272,53 @@ pub struct TransferCommunity {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches a random community /// Fetches a random community
pub struct GetRandomCommunity { pub struct GetRandomCommunity {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<ListingType>, pub type_: Option<ListingType>,
} }
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ListCommunityPendingFollows {
/// Only shows the unapproved applications
#[cfg_attr(feature = "full", ts(optional))]
pub pending_only: Option<bool>,
// Only for admins, show pending follows for communities which you dont moderate
#[cfg_attr(feature = "full", ts(optional))]
pub all_communities: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct GetCommunityPendingFollowsCount {
pub community_id: CommunityId,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct GetCommunityPendingFollowsCountResponse {
pub count: i64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ListCommunityPendingFollowsResponse {
pub items: Vec<PendingFollow>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ApproveCommunityPendingFollower {
pub community_id: CommunityId,
pub follower_id: PersonId,
pub approve: bool,
}

View file

@ -57,7 +57,7 @@ impl LemmyContext {
/// Do not use this in production code. /// Do not use this in production code.
pub async fn init_test_federation_config() -> FederationConfig<LemmyContext> { pub async fn init_test_federation_config() -> FederationConfig<LemmyContext> {
// call this to run migrations // call this to run migrations
let pool = build_db_pool_for_tests().await; let pool = build_db_pool_for_tests();
let client = client_builder(&SETTINGS).build().expect("build client"); let client = client_builder(&SETTINGS).build().expect("build client");

View file

@ -62,8 +62,12 @@ pub struct ListCustomEmojisResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of custom emojis. /// Fetches a list of custom emojis.
pub struct ListCustomEmojis { pub struct ListCustomEmojis {
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub category: Option<String>, pub category: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub ignore_page_limits: Option<bool>, pub ignore_page_limits: Option<bool>,
} }

View file

@ -26,7 +26,7 @@ pub extern crate lemmy_db_views_actor;
pub extern crate lemmy_db_views_moderator; pub extern crate lemmy_db_views_moderator;
pub extern crate lemmy_utils; pub extern crate lemmy_utils;
pub use lemmy_utils::LemmyErrorType; pub use lemmy_utils::error::LemmyErrorType;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{cmp::min, time::Duration}; use std::{cmp::min, time::Duration};

View file

@ -20,8 +20,11 @@ pub struct CreateOAuthProvider {
pub client_id: String, pub client_id: String,
pub client_secret: String, pub client_secret: String,
pub scopes: String, pub scopes: String,
#[cfg_attr(feature = "full", ts(optional))]
pub auto_verify_email: Option<bool>, pub auto_verify_email: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub account_linking_enabled: Option<bool>, pub account_linking_enabled: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub enabled: Option<bool>, pub enabled: Option<bool>,
} }
@ -32,15 +35,25 @@ pub struct CreateOAuthProvider {
/// Edit an external auth method. /// Edit an external auth method.
pub struct EditOAuthProvider { pub struct EditOAuthProvider {
pub id: OAuthProviderId, pub id: OAuthProviderId,
#[cfg_attr(feature = "full", ts(optional))]
pub display_name: Option<String>, pub display_name: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub authorization_endpoint: Option<String>, pub authorization_endpoint: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub token_endpoint: Option<String>, pub token_endpoint: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub userinfo_endpoint: Option<String>, pub userinfo_endpoint: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub id_claim: Option<String>, pub id_claim: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub client_secret: Option<String>, pub client_secret: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub scopes: Option<String>, pub scopes: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub auto_verify_email: Option<bool>, pub auto_verify_email: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub account_linking_enabled: Option<bool>, pub account_linking_enabled: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub enabled: Option<bool>, pub enabled: Option<bool>,
} }
@ -59,13 +72,14 @@ pub struct DeleteOAuthProvider {
/// Logging in with an OAuth 2.0 authorization /// Logging in with an OAuth 2.0 authorization
pub struct AuthenticateWithOauth { pub struct AuthenticateWithOauth {
pub code: String, pub code: String,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub oauth_provider_id: OAuthProviderId, pub oauth_provider_id: OAuthProviderId,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub redirect_uri: Url, pub redirect_uri: Url,
#[cfg_attr(feature = "full", ts(optional))]
pub show_nsfw: Option<bool>, pub show_nsfw: Option<bool>,
/// Username is mandatory at registration time /// Username is mandatory at registration time
#[cfg_attr(feature = "full", ts(optional))]
pub username: Option<String>, pub username: Option<String>,
/// An answer is mandatory if require application is enabled on the server /// An answer is mandatory if require application is enabled on the server
#[cfg_attr(feature = "full", ts(optional))]
pub answer: Option<String>, pub answer: Option<String>,
} }

View file

@ -28,6 +28,7 @@ pub struct Login {
pub username_or_email: SensitiveString, pub username_or_email: SensitiveString,
pub password: SensitiveString, pub password: SensitiveString,
/// May be required, if totp is enabled for their account. /// May be required, if totp is enabled for their account.
#[cfg_attr(feature = "full", ts(optional))]
pub totp_2fa_token: Option<String>, pub totp_2fa_token: Option<String>,
} }
@ -40,16 +41,22 @@ pub struct Register {
pub username: String, pub username: String,
pub password: SensitiveString, pub password: SensitiveString,
pub password_verify: SensitiveString, pub password_verify: SensitiveString,
#[cfg_attr(feature = "full", ts(optional))]
pub show_nsfw: Option<bool>, pub show_nsfw: Option<bool>,
/// email is mandatory if email verification is enabled on the server /// email is mandatory if email verification is enabled on the server
#[cfg_attr(feature = "full", ts(optional))]
pub email: Option<SensitiveString>, pub email: Option<SensitiveString>,
/// The UUID of the captcha item. /// The UUID of the captcha item.
#[cfg_attr(feature = "full", ts(optional))]
pub captcha_uuid: Option<String>, pub captcha_uuid: Option<String>,
/// Your captcha answer. /// Your captcha answer.
#[cfg_attr(feature = "full", ts(optional))]
pub captcha_answer: Option<String>, pub captcha_answer: Option<String>,
/// A form field to trick signup bots. Should be None. /// A form field to trick signup bots. Should be None.
#[cfg_attr(feature = "full", ts(optional))]
pub honeypot: Option<String>, pub honeypot: Option<String>,
/// An answer is mandatory if require application is enabled on the server /// An answer is mandatory if require application is enabled on the server
#[cfg_attr(feature = "full", ts(optional))]
pub answer: Option<String>, pub answer: Option<String>,
} }
@ -60,6 +67,7 @@ pub struct Register {
/// A wrapper for the captcha response. /// A wrapper for the captcha response.
pub struct GetCaptchaResponse { pub struct GetCaptchaResponse {
/// Will be None if captchas are disabled. /// Will be None if captchas are disabled.
#[cfg_attr(feature = "full", ts(optional))]
pub ok: Option<CaptchaResponse>, pub ok: Option<CaptchaResponse>,
} }
@ -83,60 +91,92 @@ pub struct CaptchaResponse {
/// Saves settings for your user. /// Saves settings for your user.
pub struct SaveUserSettings { pub struct SaveUserSettings {
/// Show nsfw posts. /// Show nsfw posts.
#[cfg_attr(feature = "full", ts(optional))]
pub show_nsfw: Option<bool>, pub show_nsfw: Option<bool>,
/// Blur nsfw posts. /// Blur nsfw posts.
#[cfg_attr(feature = "full", ts(optional))]
pub blur_nsfw: Option<bool>, pub blur_nsfw: Option<bool>,
/// Your user's theme. /// Your user's theme.
#[cfg_attr(feature = "full", ts(optional))]
pub theme: Option<String>, pub theme: Option<String>,
/// The default post listing type, usually "local" /// The default post listing type, usually "local"
#[cfg_attr(feature = "full", ts(optional))]
pub default_listing_type: Option<ListingType>, pub default_listing_type: Option<ListingType>,
/// A post-view mode that changes how multiple post listings look. /// A post-view mode that changes how multiple post listings look.
#[cfg_attr(feature = "full", ts(optional))]
pub post_listing_mode: Option<PostListingMode>, pub post_listing_mode: Option<PostListingMode>,
/// The default post sort, usually "active" /// The default post sort, usually "active"
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_sort_type: Option<PostSortType>, pub default_post_sort_type: Option<PostSortType>,
/// The default comment sort, usually "hot" /// The default comment sort, usually "hot"
#[cfg_attr(feature = "full", ts(optional))]
pub default_comment_sort_type: Option<CommentSortType>, pub default_comment_sort_type: Option<CommentSortType>,
/// The language of the lemmy interface /// The language of the lemmy interface
#[cfg_attr(feature = "full", ts(optional))]
pub interface_language: Option<String>, pub interface_language: Option<String>,
/// A URL for your avatar. /// A URL for your avatar.
#[cfg_attr(feature = "full", ts(optional))]
pub avatar: Option<String>, pub avatar: Option<String>,
/// A URL for your banner. /// A URL for your banner.
#[cfg_attr(feature = "full", ts(optional))]
pub banner: Option<String>, pub banner: Option<String>,
/// Your display name, which can contain strange characters, and does not need to be unique. /// Your display name, which can contain strange characters, and does not need to be unique.
#[cfg_attr(feature = "full", ts(optional))]
pub display_name: Option<String>, pub display_name: Option<String>,
/// Your email. /// Your email.
#[cfg_attr(feature = "full", ts(optional))]
pub email: Option<SensitiveString>, pub email: Option<SensitiveString>,
/// Your bio / info, in markdown. /// Your bio / info, in markdown.
#[cfg_attr(feature = "full", ts(optional))]
pub bio: Option<String>, pub bio: Option<String>,
/// Your matrix user id. Ex: @my_user:matrix.org /// Your matrix user id. Ex: @my_user:matrix.org
#[cfg_attr(feature = "full", ts(optional))]
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
/// Whether to show or hide avatars. /// Whether to show or hide avatars.
#[cfg_attr(feature = "full", ts(optional))]
pub show_avatars: Option<bool>, pub show_avatars: Option<bool>,
/// Sends notifications to your email. /// Sends notifications to your email.
#[cfg_attr(feature = "full", ts(optional))]
pub send_notifications_to_email: Option<bool>, pub send_notifications_to_email: Option<bool>,
/// Whether this account is a bot account. Users can hide these accounts easily if they wish. /// Whether this account is a bot account. Users can hide these accounts easily if they wish.
#[cfg_attr(feature = "full", ts(optional))]
pub bot_account: Option<bool>, pub bot_account: Option<bool>,
/// Whether to show bot accounts. /// Whether to show bot accounts.
#[cfg_attr(feature = "full", ts(optional))]
pub show_bot_accounts: Option<bool>, pub show_bot_accounts: Option<bool>,
/// Whether to show read posts. /// Whether to show read posts.
#[cfg_attr(feature = "full", ts(optional))]
pub show_read_posts: Option<bool>, pub show_read_posts: Option<bool>,
/// A list of languages you are able to see discussion in. /// A list of languages you are able to see discussion in.
#[cfg_attr(feature = "full", ts(optional))]
pub discussion_languages: Option<Vec<LanguageId>>, pub discussion_languages: Option<Vec<LanguageId>>,
/// Open links in a new tab /// Open links in a new tab
#[cfg_attr(feature = "full", ts(optional))]
pub open_links_in_new_tab: Option<bool>, pub open_links_in_new_tab: Option<bool>,
/// Enable infinite scroll /// Enable infinite scroll
#[cfg_attr(feature = "full", ts(optional))]
pub infinite_scroll_enabled: Option<bool>, pub infinite_scroll_enabled: Option<bool>,
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments). /// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
#[cfg_attr(feature = "full", ts(optional))]
pub enable_keyboard_navigation: Option<bool>, pub enable_keyboard_navigation: Option<bool>,
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or /// Whether user avatars or inline images in the UI that are gifs should be allowed to play or
/// should be paused /// should be paused
#[cfg_attr(feature = "full", ts(optional))]
pub enable_animated_images: Option<bool>, pub enable_animated_images: Option<bool>,
/// Whether a user can send / receive private messages
#[cfg_attr(feature = "full", ts(optional))]
pub enable_private_messages: Option<bool>,
/// Whether to auto-collapse bot comments. /// Whether to auto-collapse bot comments.
#[cfg_attr(feature = "full", ts(optional))]
pub collapse_bot_comments: Option<bool>, pub collapse_bot_comments: Option<bool>,
/// Some vote display mode settings /// Some vote display mode settings
#[cfg_attr(feature = "full", ts(optional))]
pub show_scores: Option<bool>, pub show_scores: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub show_upvotes: Option<bool>, pub show_upvotes: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub show_downvotes: Option<bool>, pub show_downvotes: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub show_upvote_percentage: Option<bool>, pub show_upvote_percentage: Option<bool>,
} }
@ -158,6 +198,7 @@ pub struct ChangePassword {
pub struct LoginResponse { pub struct LoginResponse {
/// This is None in response to `Register` if email verification is enabled, or the server /// This is None in response to `Register` if email verification is enabled, or the server
/// requires registration applications. /// requires registration applications.
#[cfg_attr(feature = "full", ts(optional))]
pub jwt: Option<SensitiveString>, pub jwt: Option<SensitiveString>,
/// If registration applications are required, this will return true for a signup response. /// If registration applications are required, this will return true for a signup response.
pub registration_created: bool, pub registration_created: bool,
@ -173,13 +214,20 @@ pub struct LoginResponse {
/// ///
/// Either person_id, or username are required. /// Either person_id, or username are required.
pub struct GetPersonDetails { pub struct GetPersonDetails {
#[cfg_attr(feature = "full", ts(optional))]
pub person_id: Option<PersonId>, pub person_id: Option<PersonId>,
/// Example: dessalines , or dessalines@xyz.tld /// Example: dessalines , or dessalines@xyz.tld
#[cfg_attr(feature = "full", ts(optional))]
pub username: Option<String>, pub username: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<PostSortType>, pub sort: Option<PostSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub saved_only: Option<bool>, pub saved_only: Option<bool>,
} }
@ -190,6 +238,7 @@ pub struct GetPersonDetails {
/// A person's details response. /// A person's details response.
pub struct GetPersonDetailsResponse { pub struct GetPersonDetailsResponse {
pub person_view: PersonView, pub person_view: PersonView,
#[cfg_attr(feature = "full", ts(optional))]
pub site: Option<Site>, pub site: Option<Site>,
pub comments: Vec<CommentView>, pub comments: Vec<CommentView>,
pub posts: Vec<PostView>, pub posts: Vec<PostView>,
@ -223,11 +272,14 @@ pub struct BanPerson {
pub ban: bool, pub ban: bool,
/// Optionally remove or restore all their data. Useful for new troll accounts. /// Optionally remove or restore all their data. Useful for new troll accounts.
/// If ban is true, then this means remove. If ban is false, it means restore. /// If ban is true, then this means remove. If ban is false, it means restore.
#[cfg_attr(feature = "full", ts(optional))]
pub remove_or_restore_data: Option<bool>, pub remove_or_restore_data: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
/// A time that the ban will expire, in unix epoch seconds. /// A time that the ban will expire, in unix epoch seconds.
/// ///
/// An i64 unix timestamp is used for a simpler API client implementation. /// An i64 unix timestamp is used for a simpler API client implementation.
#[cfg_attr(feature = "full", ts(optional))]
pub expires: Option<i64>, pub expires: Option<i64>,
} }
@ -273,9 +325,13 @@ pub struct BlockPersonResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get comment replies. /// Get comment replies.
pub struct GetReplies { pub struct GetReplies {
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<CommentSortType>, pub sort: Option<CommentSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub unread_only: Option<bool>, pub unread_only: Option<bool>,
} }
@ -294,9 +350,13 @@ pub struct GetRepliesResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get mentions for your user. /// Get mentions for your user.
pub struct GetPersonMentions { pub struct GetPersonMentions {
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<CommentSortType>, pub sort: Option<CommentSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub unread_only: Option<bool>, pub unread_only: Option<bool>,
} }
@ -375,6 +435,7 @@ pub struct PasswordChangeAfterReset {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a count of the number of reports. /// Get a count of the number of reports.
pub struct GetReportCount { pub struct GetReportCount {
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
} }
@ -384,9 +445,11 @@ pub struct GetReportCount {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// A response for the number of reports. /// A response for the number of reports.
pub struct GetReportCountResponse { pub struct GetReportCountResponse {
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
pub comment_reports: i64, pub comment_reports: i64,
pub post_reports: i64, pub post_reports: i64,
#[cfg_attr(feature = "full", ts(optional))]
pub private_message_reports: Option<i64>, pub private_message_reports: Option<i64>,
} }
@ -436,7 +499,9 @@ pub struct UpdateTotpResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get your user's image / media uploads. /// Get your user's image / media uploads.
pub struct ListMedia { pub struct ListMedia {
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
} }

View file

@ -19,18 +19,26 @@ use ts_rs::TS;
pub struct CreatePost { pub struct CreatePost {
pub name: String, pub name: String,
pub community_id: CommunityId, pub community_id: CommunityId,
#[cfg_attr(feature = "full", ts(optional))]
pub url: Option<String>, pub url: Option<String>,
/// An optional body for the post in markdown. /// An optional body for the post in markdown.
#[cfg_attr(feature = "full", ts(optional))]
pub body: Option<String>, pub body: Option<String>,
/// An optional alt_text, usable for image posts. /// An optional alt_text, usable for image posts.
#[cfg_attr(feature = "full", ts(optional))]
pub alt_text: Option<String>, pub alt_text: Option<String>,
/// A honeypot to catch bots. Should be None. /// A honeypot to catch bots. Should be None.
#[cfg_attr(feature = "full", ts(optional))]
pub honeypot: Option<String>, pub honeypot: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
/// Instead of fetching a thumbnail, use a custom one. /// Instead of fetching a thumbnail, use a custom one.
#[cfg_attr(feature = "full", ts(optional))]
pub custom_thumbnail: Option<String>, pub custom_thumbnail: Option<String>,
/// Time when this post should be scheduled. Null means publish immediately. /// Time when this post should be scheduled. Null means publish immediately.
#[cfg_attr(feature = "full", ts(optional))]
pub scheduled_publish_time: Option<i64>, pub scheduled_publish_time: Option<i64>,
} }
@ -45,9 +53,12 @@ pub struct PostResponse {
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
// TODO this should be made into a tagged enum
/// Get a post. Needs either the post id, or comment_id. /// Get a post. Needs either the post id, or comment_id.
pub struct GetPost { pub struct GetPost {
#[cfg_attr(feature = "full", ts(optional))]
pub id: Option<PostId>, pub id: Option<PostId>,
#[cfg_attr(feature = "full", ts(optional))]
pub comment_id: Option<CommentId>, pub comment_id: Option<CommentId>,
} }
@ -70,21 +81,37 @@ pub struct GetPostResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get a list of posts. /// Get a list of posts.
pub struct GetPosts { pub struct GetPosts {
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<ListingType>, pub type_: Option<ListingType>,
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<PostSortType>, pub sort: Option<PostSortType>,
/// DEPRECATED, use page_cursor /// DEPRECATED, use page_cursor
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_name: Option<String>, pub community_name: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub saved_only: Option<bool>, pub saved_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub liked_only: Option<bool>, pub liked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub disliked_only: Option<bool>, pub disliked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub show_hidden: Option<bool>, pub show_hidden: Option<bool>,
/// If true, then show the read posts (even if your user setting is to hide them) /// If true, then show the read posts (even if your user setting is to hide them)
#[cfg_attr(feature = "full", ts(optional))]
pub show_read: Option<bool>, pub show_read: Option<bool>,
/// If true, then show the nsfw posts (even if your user setting is to hide them) /// If true, then show the nsfw posts (even if your user setting is to hide them)
#[cfg_attr(feature = "full", ts(optional))]
pub show_nsfw: Option<bool>, pub show_nsfw: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
/// If true, then only show posts with no comments
pub no_comments_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page_cursor: Option<PaginationCursor>, pub page_cursor: Option<PaginationCursor>,
} }
@ -96,6 +123,7 @@ pub struct GetPosts {
pub struct GetPostsResponse { pub struct GetPostsResponse {
pub posts: Vec<PostView>, pub posts: Vec<PostView>,
/// the pagination cursor to use to fetch the next page /// the pagination cursor to use to fetch the next page
#[cfg_attr(feature = "full", ts(optional))]
pub next_page: Option<PaginationCursor>, pub next_page: Option<PaginationCursor>,
} }
@ -116,17 +144,25 @@ pub struct CreatePostLike {
/// Edit a post. /// Edit a post.
pub struct EditPost { pub struct EditPost {
pub post_id: PostId, pub post_id: PostId,
#[cfg_attr(feature = "full", ts(optional))]
pub name: Option<String>, pub name: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub url: Option<String>, pub url: Option<String>,
/// An optional body for the post in markdown. /// An optional body for the post in markdown.
#[cfg_attr(feature = "full", ts(optional))]
pub body: Option<String>, pub body: Option<String>,
/// An optional alt_text, usable for image posts. /// An optional alt_text, usable for image posts.
#[cfg_attr(feature = "full", ts(optional))]
pub alt_text: Option<String>, pub alt_text: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub language_id: Option<LanguageId>, pub language_id: Option<LanguageId>,
/// Instead of fetching a thumbnail, use a custom one. /// Instead of fetching a thumbnail, use a custom one.
#[cfg_attr(feature = "full", ts(optional))]
pub custom_thumbnail: Option<String>, pub custom_thumbnail: Option<String>,
/// Time when this post should be scheduled. Null means publish immediately. /// Time when this post should be scheduled. Null means publish immediately.
#[cfg_attr(feature = "full", ts(optional))]
pub scheduled_publish_time: Option<i64>, pub scheduled_publish_time: Option<i64>,
} }
@ -147,6 +183,7 @@ pub struct DeletePost {
pub struct RemovePost { pub struct RemovePost {
pub post_id: PostId, pub post_id: PostId,
pub removed: bool, pub removed: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -230,12 +267,18 @@ pub struct ResolvePostReport {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// List post reports. /// List post reports.
pub struct ListPostReports { pub struct ListPostReports {
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
/// Only shows the unresolved reports /// Only shows the unresolved reports
#[cfg_attr(feature = "full", ts(optional))]
pub unresolved_only: Option<bool>, pub unresolved_only: Option<bool>,
// TODO make into tagged enum at some point
/// if no community is given, it returns reports for all communities moderated by the auth user /// if no community is given, it returns reports for all communities moderated by the auth user
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub post_id: Option<PostId>, pub post_id: Option<PostId>,
} }
@ -271,6 +314,7 @@ pub struct GetSiteMetadataResponse {
pub struct LinkMetadata { pub struct LinkMetadata {
#[serde(flatten)] #[serde(flatten)]
pub opengraph_data: OpenGraphData, pub opengraph_data: OpenGraphData,
#[cfg_attr(feature = "full", ts(optional))]
pub content_type: Option<String>, pub content_type: Option<String>,
} }
@ -280,9 +324,13 @@ pub struct LinkMetadata {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Site metadata, from its opengraph tags. /// Site metadata, from its opengraph tags.
pub struct OpenGraphData { pub struct OpenGraphData {
#[cfg_attr(feature = "full", ts(optional))]
pub title: Option<String>, pub title: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub description: Option<String>, pub description: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub(crate) image: Option<DbUrl>, pub(crate) image: Option<DbUrl>,
#[cfg_attr(feature = "full", ts(optional))]
pub embed_video_url: Option<DbUrl>, pub embed_video_url: Option<DbUrl>,
} }
@ -293,7 +341,9 @@ pub struct OpenGraphData {
/// List post likes. Admins-only. /// List post likes. Admins-only.
pub struct ListPostLikes { pub struct ListPostLikes {
pub post_id: PostId, pub post_id: PostId,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
} }

View file

@ -47,9 +47,13 @@ pub struct MarkPrivateMessageAsRead {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get your private messages. /// Get your private messages.
pub struct GetPrivateMessages { pub struct GetPrivateMessages {
#[cfg_attr(feature = "full", ts(optional))]
pub unread_only: Option<bool>, pub unread_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub creator_id: Option<PersonId>, pub creator_id: Option<PersonId>,
} }
@ -102,9 +106,12 @@ pub struct ResolvePrivateMessageReport {
/// List private message reports. /// List private message reports.
// TODO , perhaps GetReports should be a tagged enum list too. // TODO , perhaps GetReports should be a tagged enum list too.
pub struct ListPrivateMessageReports { pub struct ListPrivateMessageReports {
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
/// Only shows the unresolved reports /// Only shows the unresolved reports
#[cfg_attr(feature = "full", ts(optional))]
pub unresolved_only: Option<bool>, pub unresolved_only: Option<bool>,
} }

View file

@ -175,7 +175,7 @@ pub async fn generate_post_link_metadata(
}; };
let updated_post = Post::update(&mut context.pool(), post.id, &form).await?; let updated_post = Post::update(&mut context.pool(), post.id, &form).await?;
if let Some(send_activity) = send_activity(updated_post) { if let Some(send_activity) = send_activity(updated_post) {
ActivityChannel::submit_activity(send_activity, &context).await?; ActivityChannel::submit_activity(send_activity, &context)?;
} }
Ok(()) Ok(())
} }

View file

@ -59,6 +59,8 @@ pub enum SendActivityData {
score: i16, score: i16,
}, },
FollowCommunity(Community, Person, bool), FollowCommunity(Community, Person, bool),
AcceptFollower(CommunityId, PersonId),
RejectFollower(CommunityId, PersonId),
UpdateCommunity(Person, Community), UpdateCommunity(Person, Community),
DeleteCommunity(Person, Community, bool), DeleteCommunity(Person, Community, bool),
RemoveCommunity { RemoveCommunity {
@ -123,10 +125,7 @@ impl ActivityChannel {
lock.recv().await lock.recv().await
} }
pub async fn submit_activity( pub fn submit_activity(data: SendActivityData, _context: &Data<LemmyContext>) -> LemmyResult<()> {
data: SendActivityData,
_context: &Data<LemmyContext>,
) -> LemmyResult<()> {
// could do `ACTIVITY_CHANNEL.keepalive_sender.lock()` instead and get rid of weak_sender, // could do `ACTIVITY_CHANNEL.keepalive_sender.lock()` instead and get rid of weak_sender,
// not sure which way is more efficient // not sure which way is more efficient
if let Some(sender) = ACTIVITY_CHANNEL.weak_sender.upgrade() { if let Some(sender) = ACTIVITY_CHANNEL.weak_sender.upgrade() {

View file

@ -71,18 +71,31 @@ use ts_rs::TS;
/// Searches the site, given a query string, and some optional filters. /// Searches the site, given a query string, and some optional filters.
pub struct Search { pub struct Search {
pub q: String, pub q: String,
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_name: Option<String>, pub community_name: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub creator_id: Option<PersonId>, pub creator_id: Option<PersonId>,
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<SearchType>, pub type_: Option<SearchType>,
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<PostSortType>, pub sort: Option<PostSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub listing_type: Option<ListingType>, pub listing_type: Option<ListingType>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub title_only: Option<bool>, pub title_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub post_url_only: Option<bool>, pub post_url_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub saved_only: Option<bool>, pub saved_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub liked_only: Option<bool>, pub liked_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub disliked_only: Option<bool>, pub disliked_only: Option<bool>,
} }
@ -115,9 +128,13 @@ pub struct ResolveObject {
// TODO Change this to an enum // TODO Change this to an enum
/// The response of an apub object fetch. /// The response of an apub object fetch.
pub struct ResolveObjectResponse { pub struct ResolveObjectResponse {
#[cfg_attr(feature = "full", ts(optional))]
pub comment: Option<CommentView>, pub comment: Option<CommentView>,
#[cfg_attr(feature = "full", ts(optional))]
pub post: Option<PostView>, pub post: Option<PostView>,
#[cfg_attr(feature = "full", ts(optional))]
pub community: Option<CommunityView>, pub community: Option<CommunityView>,
#[cfg_attr(feature = "full", ts(optional))]
pub person: Option<PersonView>, pub person: Option<PersonView>,
} }
@ -127,13 +144,21 @@ pub struct ResolveObjectResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches the modlog. /// Fetches the modlog.
pub struct GetModlog { pub struct GetModlog {
#[cfg_attr(feature = "full", ts(optional))]
pub mod_person_id: Option<PersonId>, pub mod_person_id: Option<PersonId>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_id: Option<CommunityId>, pub community_id: Option<CommunityId>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub type_: Option<ModlogActionType>, pub type_: Option<ModlogActionType>,
#[cfg_attr(feature = "full", ts(optional))]
pub other_person_id: Option<PersonId>, pub other_person_id: Option<PersonId>,
#[cfg_attr(feature = "full", ts(optional))]
pub post_id: Option<PostId>, pub post_id: Option<PostId>,
#[cfg_attr(feature = "full", ts(optional))]
pub comment_id: Option<CommentId>, pub comment_id: Option<CommentId>,
} }
@ -167,50 +192,95 @@ pub struct GetModlogResponse {
/// Creates a site. Should be done after first running lemmy. /// Creates a site. Should be done after first running lemmy.
pub struct CreateSite { pub struct CreateSite {
pub name: String, pub name: String,
#[cfg_attr(feature = "full", ts(optional))]
pub sidebar: Option<String>, pub sidebar: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub description: Option<String>, pub description: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub icon: Option<String>, pub icon: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub banner: Option<String>, pub banner: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub enable_nsfw: Option<bool>, pub enable_nsfw: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub community_creation_admin_only: Option<bool>, pub community_creation_admin_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub require_email_verification: Option<bool>, pub require_email_verification: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub application_question: Option<String>, pub application_question: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub private_instance: Option<bool>, pub private_instance: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub default_theme: Option<String>, pub default_theme: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_listing_type: Option<ListingType>, pub default_post_listing_type: Option<ListingType>,
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_listing_mode: Option<PostListingMode>, pub default_post_listing_mode: Option<PostListingMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_sort_type: Option<PostSortType>, pub default_post_sort_type: Option<PostSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub default_comment_sort_type: Option<CommentSortType>, pub default_comment_sort_type: Option<CommentSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub legal_information: Option<String>, pub legal_information: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub application_email_admins: Option<bool>, pub application_email_admins: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub hide_modlog_mod_names: Option<bool>, pub hide_modlog_mod_names: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub discussion_languages: Option<Vec<LanguageId>>, pub discussion_languages: Option<Vec<LanguageId>>,
#[cfg_attr(feature = "full", ts(optional))]
pub slur_filter_regex: Option<String>, pub slur_filter_regex: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub actor_name_max_length: Option<i32>, pub actor_name_max_length: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_message: Option<i32>, pub rate_limit_message: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_message_per_second: Option<i32>, pub rate_limit_message_per_second: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_post: Option<i32>, pub rate_limit_post: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_post_per_second: Option<i32>, pub rate_limit_post_per_second: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_register: Option<i32>, pub rate_limit_register: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_register_per_second: Option<i32>, pub rate_limit_register_per_second: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_image: Option<i32>, pub rate_limit_image: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_image_per_second: Option<i32>, pub rate_limit_image_per_second: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_comment: Option<i32>, pub rate_limit_comment: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_comment_per_second: Option<i32>, pub rate_limit_comment_per_second: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_search: Option<i32>, pub rate_limit_search: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_search_per_second: Option<i32>, pub rate_limit_search_per_second: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub federation_enabled: Option<bool>, pub federation_enabled: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub federation_debug: Option<bool>, pub federation_debug: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub captcha_enabled: Option<bool>, pub captcha_enabled: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub captcha_difficulty: Option<String>, pub captcha_difficulty: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub allowed_instances: Option<Vec<String>>, pub allowed_instances: Option<Vec<String>>,
#[cfg_attr(feature = "full", ts(optional))]
pub blocked_instances: Option<Vec<String>>, pub blocked_instances: Option<Vec<String>>,
#[cfg_attr(feature = "full", ts(optional))]
pub registration_mode: Option<RegistrationMode>, pub registration_mode: Option<RegistrationMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub oauth_registration: Option<bool>, pub oauth_registration: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub content_warning: Option<String>, pub content_warning: Option<String>,
#[cfg_attr(feature = "full", ts(optional))]
pub post_upvotes: Option<FederationMode>, pub post_upvotes: Option<FederationMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub post_downvotes: Option<FederationMode>, pub post_downvotes: Option<FederationMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub comment_upvotes: Option<FederationMode>, pub comment_upvotes: Option<FederationMode>,
#[cfg_attr(feature = "full", ts(optional))]
pub comment_downvotes: Option<FederationMode>, pub comment_downvotes: Option<FederationMode>,
} }
@ -220,93 +290,142 @@ pub struct CreateSite {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Edits a site. /// Edits a site.
pub struct EditSite { pub struct EditSite {
#[cfg_attr(feature = "full", ts(optional))]
pub name: Option<String>, pub name: Option<String>,
/// A sidebar for the site, in markdown.
#[cfg_attr(feature = "full", ts(optional))]
pub sidebar: Option<String>, pub sidebar: Option<String>,
/// A shorter, one line description of your site. /// A shorter, one line description of your site.
#[cfg_attr(feature = "full", ts(optional))]
pub description: Option<String>, pub description: Option<String>,
/// A url for your site's icon. /// A url for your site's icon.
#[cfg_attr(feature = "full", ts(optional))]
pub icon: Option<String>, pub icon: Option<String>,
/// A url for your site's banner. /// A url for your site's banner.
#[cfg_attr(feature = "full", ts(optional))]
pub banner: Option<String>, pub banner: Option<String>,
/// Whether to enable NSFW. /// Whether to enable NSFW.
#[cfg_attr(feature = "full", ts(optional))]
pub enable_nsfw: Option<bool>, pub enable_nsfw: Option<bool>,
/// Limits community creation to admins only. /// Limits community creation to admins only.
#[cfg_attr(feature = "full", ts(optional))]
pub community_creation_admin_only: Option<bool>, pub community_creation_admin_only: Option<bool>,
/// Whether to require email verification. /// Whether to require email verification.
#[cfg_attr(feature = "full", ts(optional))]
pub require_email_verification: Option<bool>, pub require_email_verification: Option<bool>,
/// Your application question form. This is in markdown, and can be many questions. /// Your application question form. This is in markdown, and can be many questions.
#[cfg_attr(feature = "full", ts(optional))]
pub application_question: Option<String>, pub application_question: Option<String>,
/// Whether your instance is public, or private. /// Whether your instance is public, or private.
#[cfg_attr(feature = "full", ts(optional))]
pub private_instance: Option<bool>, pub private_instance: Option<bool>,
/// The default theme. Usually "browser" /// The default theme. Usually "browser"
#[cfg_attr(feature = "full", ts(optional))]
pub default_theme: Option<String>, pub default_theme: Option<String>,
/// The default post listing type, usually "local" /// The default post listing type, usually "local"
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_listing_type: Option<ListingType>, pub default_post_listing_type: Option<ListingType>,
/// Default value for listing mode, usually "list" /// Default value for listing mode, usually "list"
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_listing_mode: Option<PostListingMode>, pub default_post_listing_mode: Option<PostListingMode>,
/// The default post sort, usually "active" /// The default post sort, usually "active"
#[cfg_attr(feature = "full", ts(optional))]
pub default_post_sort_type: Option<PostSortType>, pub default_post_sort_type: Option<PostSortType>,
/// The default comment sort, usually "hot" /// The default comment sort, usually "hot"
#[cfg_attr(feature = "full", ts(optional))]
pub default_comment_sort_type: Option<CommentSortType>, pub default_comment_sort_type: Option<CommentSortType>,
/// An optional page of legal information /// An optional page of legal information
#[cfg_attr(feature = "full", ts(optional))]
pub legal_information: Option<String>, pub legal_information: Option<String>,
/// Whether to email admins when receiving a new application. /// Whether to email admins when receiving a new application.
#[cfg_attr(feature = "full", ts(optional))]
pub application_email_admins: Option<bool>, pub application_email_admins: Option<bool>,
/// Whether to hide moderator names from the modlog. /// Whether to hide moderator names from the modlog.
#[cfg_attr(feature = "full", ts(optional))]
pub hide_modlog_mod_names: Option<bool>, pub hide_modlog_mod_names: Option<bool>,
/// A list of allowed discussion languages. /// A list of allowed discussion languages.
#[cfg_attr(feature = "full", ts(optional))]
pub discussion_languages: Option<Vec<LanguageId>>, pub discussion_languages: Option<Vec<LanguageId>>,
/// A regex string of items to filter. /// A regex string of items to filter.
#[cfg_attr(feature = "full", ts(optional))]
pub slur_filter_regex: Option<String>, pub slur_filter_regex: Option<String>,
/// The max length of actor names. /// The max length of actor names.
#[cfg_attr(feature = "full", ts(optional))]
pub actor_name_max_length: Option<i32>, pub actor_name_max_length: Option<i32>,
/// The number of messages allowed in a given time frame. /// The number of messages allowed in a given time frame.
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_message: Option<i32>, pub rate_limit_message: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_message_per_second: Option<i32>, pub rate_limit_message_per_second: Option<i32>,
/// The number of posts allowed in a given time frame. /// The number of posts allowed in a given time frame.
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_post: Option<i32>, pub rate_limit_post: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_post_per_second: Option<i32>, pub rate_limit_post_per_second: Option<i32>,
/// The number of registrations allowed in a given time frame. /// The number of registrations allowed in a given time frame.
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_register: Option<i32>, pub rate_limit_register: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_register_per_second: Option<i32>, pub rate_limit_register_per_second: Option<i32>,
/// The number of image uploads allowed in a given time frame. /// The number of image uploads allowed in a given time frame.
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_image: Option<i32>, pub rate_limit_image: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_image_per_second: Option<i32>, pub rate_limit_image_per_second: Option<i32>,
/// The number of comments allowed in a given time frame. /// The number of comments allowed in a given time frame.
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_comment: Option<i32>, pub rate_limit_comment: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_comment_per_second: Option<i32>, pub rate_limit_comment_per_second: Option<i32>,
/// The number of searches allowed in a given time frame. /// The number of searches allowed in a given time frame.
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_search: Option<i32>, pub rate_limit_search: Option<i32>,
#[cfg_attr(feature = "full", ts(optional))]
pub rate_limit_search_per_second: Option<i32>, pub rate_limit_search_per_second: Option<i32>,
/// Whether to enable federation. /// Whether to enable federation.
#[cfg_attr(feature = "full", ts(optional))]
pub federation_enabled: Option<bool>, pub federation_enabled: Option<bool>,
/// Enables federation debugging. /// Enables federation debugging.
#[cfg_attr(feature = "full", ts(optional))]
pub federation_debug: Option<bool>, pub federation_debug: Option<bool>,
/// Whether to enable captchas for signups. /// Whether to enable captchas for signups.
#[cfg_attr(feature = "full", ts(optional))]
pub captcha_enabled: Option<bool>, pub captcha_enabled: Option<bool>,
/// The captcha difficulty. Can be easy, medium, or hard /// The captcha difficulty. Can be easy, medium, or hard
#[cfg_attr(feature = "full", ts(optional))]
pub captcha_difficulty: Option<String>, pub captcha_difficulty: Option<String>,
/// A list of allowed instances. If none are set, federation is open. /// A list of allowed instances. If none are set, federation is open.
#[cfg_attr(feature = "full", ts(optional))]
pub allowed_instances: Option<Vec<String>>, pub allowed_instances: Option<Vec<String>>,
/// A list of blocked instances. /// A list of blocked instances.
#[cfg_attr(feature = "full", ts(optional))]
pub blocked_instances: Option<Vec<String>>, pub blocked_instances: Option<Vec<String>>,
/// A list of blocked URLs /// A list of blocked URLs
#[cfg_attr(feature = "full", ts(optional))]
pub blocked_urls: Option<Vec<String>>, pub blocked_urls: Option<Vec<String>>,
#[cfg_attr(feature = "full", ts(optional))]
pub registration_mode: Option<RegistrationMode>, pub registration_mode: Option<RegistrationMode>,
/// Whether to email admins for new reports. /// Whether to email admins for new reports.
#[cfg_attr(feature = "full", ts(optional))]
pub reports_email_admins: Option<bool>, pub reports_email_admins: Option<bool>,
/// If present, nsfw content is visible by default. Should be displayed by frontends/clients /// If present, nsfw content is visible by default. Should be displayed by frontends/clients
/// when the site is first opened by a user. /// when the site is first opened by a user.
#[cfg_attr(feature = "full", ts(optional))]
pub content_warning: Option<String>, pub content_warning: Option<String>,
/// Whether or not external auth methods can auto-register users. /// Whether or not external auth methods can auto-register users.
#[cfg_attr(feature = "full", ts(optional))]
pub oauth_registration: Option<bool>, pub oauth_registration: Option<bool>,
/// What kind of post upvotes your site allows. /// What kind of post upvotes your site allows.
#[cfg_attr(feature = "full", ts(optional))]
pub post_upvotes: Option<FederationMode>, pub post_upvotes: Option<FederationMode>,
/// What kind of post downvotes your site allows. /// What kind of post downvotes your site allows.
#[cfg_attr(feature = "full", ts(optional))]
pub post_downvotes: Option<FederationMode>, pub post_downvotes: Option<FederationMode>,
/// What kind of comment upvotes your site allows. /// What kind of comment upvotes your site allows.
#[cfg_attr(feature = "full", ts(optional))]
pub comment_upvotes: Option<FederationMode>, pub comment_upvotes: Option<FederationMode>,
/// What kind of comment downvotes your site allows. /// What kind of comment downvotes your site allows.
#[cfg_attr(feature = "full", ts(optional))]
pub comment_downvotes: Option<FederationMode>, pub comment_downvotes: Option<FederationMode>,
} }
@ -329,6 +448,7 @@ pub struct GetSiteResponse {
pub site_view: SiteView, pub site_view: SiteView,
pub admins: Vec<PersonView>, pub admins: Vec<PersonView>,
pub version: String, pub version: String,
#[cfg_attr(feature = "full", ts(optional))]
pub my_user: Option<MyUserInfo>, pub my_user: Option<MyUserInfo>,
pub all_languages: Vec<Language>, pub all_languages: Vec<Language>,
pub discussion_languages: Vec<LanguageId>, pub discussion_languages: Vec<LanguageId>,
@ -337,9 +457,12 @@ pub struct GetSiteResponse {
/// deprecated, use /api/v3/custom_emoji/list /// deprecated, use /api/v3/custom_emoji/list
pub custom_emojis: Vec<()>, pub custom_emojis: Vec<()>,
/// If the site has any taglines, a random one is included here for displaying /// If the site has any taglines, a random one is included here for displaying
#[cfg_attr(feature = "full", ts(optional))]
pub tagline: Option<Tagline>, pub tagline: Option<Tagline>,
/// A list of external auth methods your site supports. /// A list of external auth methods your site supports.
#[cfg_attr(feature = "full", ts(optional))]
pub oauth_providers: Option<Vec<PublicOAuthProvider>>, pub oauth_providers: Option<Vec<PublicOAuthProvider>>,
#[cfg_attr(feature = "full", ts(optional))]
pub admin_oauth_providers: Option<Vec<OAuthProvider>>, pub admin_oauth_providers: Option<Vec<OAuthProvider>>,
pub blocked_urls: Vec<LocalSiteUrlBlocklist>, pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
} }
@ -351,6 +474,7 @@ pub struct GetSiteResponse {
/// A response of federated instances. /// A response of federated instances.
pub struct GetFederatedInstancesResponse { pub struct GetFederatedInstancesResponse {
/// Optional, because federation may be disabled. /// Optional, because federation may be disabled.
#[cfg_attr(feature = "full", ts(optional))]
pub federated_instances: Option<FederatedInstances>, pub federated_instances: Option<FederatedInstances>,
} }
@ -386,6 +510,7 @@ pub struct ReadableFederationState {
#[serde(flatten)] #[serde(flatten)]
internal_state: FederationQueueState, internal_state: FederationQueueState,
/// timestamp of the next retry attempt (null if fail count is 0) /// timestamp of the next retry attempt (null if fail count is 0)
#[cfg_attr(feature = "full", ts(optional))]
next_retry: Option<DateTime<Utc>>, next_retry: Option<DateTime<Utc>>,
} }
@ -410,6 +535,7 @@ pub struct InstanceWithFederationState {
pub instance: Instance, pub instance: Instance,
/// if federation to this instance is or was active, show state of outgoing federation to this /// if federation to this instance is or was active, show state of outgoing federation to this
/// instance /// instance
#[cfg_attr(feature = "full", ts(optional))]
pub federation_state: Option<ReadableFederationState>, pub federation_state: Option<ReadableFederationState>,
} }
@ -420,6 +546,7 @@ pub struct InstanceWithFederationState {
/// Purges a person from the database. This will delete all content attached to that person. /// Purges a person from the database. This will delete all content attached to that person.
pub struct PurgePerson { pub struct PurgePerson {
pub person_id: PersonId, pub person_id: PersonId,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -430,6 +557,7 @@ pub struct PurgePerson {
/// Purges a community from the database. This will delete all content attached to that community. /// Purges a community from the database. This will delete all content attached to that community.
pub struct PurgeCommunity { pub struct PurgeCommunity {
pub community_id: CommunityId, pub community_id: CommunityId,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -440,6 +568,7 @@ pub struct PurgeCommunity {
/// Purges a post from the database. This will delete all content attached to that post. /// Purges a post from the database. This will delete all content attached to that post.
pub struct PurgePost { pub struct PurgePost {
pub post_id: PostId, pub post_id: PostId,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -450,6 +579,7 @@ pub struct PurgePost {
/// Purges a comment from the database. This will delete all content attached to that comment. /// Purges a comment from the database. This will delete all content attached to that comment.
pub struct PurgeComment { pub struct PurgeComment {
pub comment_id: CommentId, pub comment_id: CommentId,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -460,8 +590,11 @@ pub struct PurgeComment {
/// Fetches a list of registration applications. /// Fetches a list of registration applications.
pub struct ListRegistrationApplications { pub struct ListRegistrationApplications {
/// Only shows the unread applications (IE those without an admin actor) /// Only shows the unread applications (IE those without an admin actor)
#[cfg_attr(feature = "full", ts(optional))]
pub unread_only: Option<bool>, pub unread_only: Option<bool>,
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
} }
@ -490,6 +623,7 @@ pub struct GetRegistrationApplication {
pub struct ApproveRegistrationApplication { pub struct ApproveRegistrationApplication {
pub id: RegistrationApplicationId, pub id: RegistrationApplicationId,
pub approve: bool, pub approve: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub deny_reason: Option<String>, pub deny_reason: Option<String>,
} }

View file

@ -50,6 +50,8 @@ pub struct ListTaglinesResponse {
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of taglines. /// Fetches a list of taglines.
pub struct ListTaglines { pub struct ListTaglines {
#[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
#[cfg_attr(feature = "full", ts(optional))]
pub limit: Option<i64>, pub limit: Option<i64>,
} }

View file

@ -42,6 +42,7 @@ use lemmy_db_views::{
structs::{LocalImageView, LocalUserView, SiteView}, structs::{LocalImageView, LocalUserView, SiteView},
}; };
use lemmy_db_views_actor::structs::{ use lemmy_db_views_actor::structs::{
CommunityFollowerView,
CommunityModeratorView, CommunityModeratorView,
CommunityPersonBanView, CommunityPersonBanView,
CommunityView, CommunityView,
@ -50,7 +51,10 @@ use lemmy_utils::{
email::{send_email, translations::Lang}, email::{send_email, translations::Lang},
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
rate_limit::{ActionType, BucketConfig}, rate_limit::{ActionType, BucketConfig},
settings::structs::{PictrsImageMode, Settings}, settings::{
structs::{PictrsImageMode, Settings},
SETTINGS,
},
utils::{ utils::{
markdown::{image_links::markdown_rewrite_image_links, markdown_check_for_blocked_urls}, markdown::{image_links::markdown_rewrite_image_links, markdown_check_for_blocked_urls},
slurs::{build_slur_regex, remove_slurs}, slurs::{build_slur_regex, remove_slurs},
@ -162,7 +166,6 @@ pub async fn update_read_comments(
person_id, person_id,
post_id, post_id,
read_comments, read_comments,
..PersonPostAggregatesForm::default()
}; };
PersonPostAggregates::upsert(pool, &person_post_agg_form).await?; PersonPostAggregates::upsert(pool, &person_post_agg_form).await?;
@ -213,7 +216,9 @@ pub async fn check_registration_application(
let local_user_id = local_user_view.local_user.id; let local_user_id = local_user_view.local_user.id;
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?; let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?;
if registration.admin_id.is_some() { if registration.admin_id.is_some() {
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))? Err(LemmyErrorType::RegistrationDenied {
reason: registration.deny_reason,
})?
} else { } else {
Err(LemmyErrorType::RegistrationApplicationIsPending)? Err(LemmyErrorType::RegistrationApplicationIsPending)?
} }
@ -227,20 +232,17 @@ pub async fn check_registration_application(
/// the user isn't banned. /// the user isn't banned.
pub async fn check_community_user_action( pub async fn check_community_user_action(
person: &Person, person: &Person,
community_id: CommunityId, community: &Community,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
check_user_valid(person)?; check_user_valid(person)?;
check_community_deleted_removed(community_id, pool).await?; check_community_deleted_removed(community)?;
CommunityPersonBanView::check(pool, person.id, community_id).await?; CommunityPersonBanView::check(pool, person.id, community.id).await?;
CommunityFollowerView::check_private_community_action(pool, person.id, community).await?;
Ok(()) Ok(())
} }
async fn check_community_deleted_removed( pub fn check_community_deleted_removed(community: &Community) -> LemmyResult<()> {
community_id: CommunityId,
pool: &mut DbPool<'_>,
) -> LemmyResult<()> {
let community = Community::read(pool, community_id).await?;
if community.deleted || community.removed { if community.deleted || community.removed {
Err(LemmyErrorType::Deleted)? Err(LemmyErrorType::Deleted)?
} }
@ -253,16 +255,16 @@ async fn check_community_deleted_removed(
/// removed/deleted. /// removed/deleted.
pub async fn check_community_mod_action( pub async fn check_community_mod_action(
person: &Person, person: &Person,
community_id: CommunityId, community: &Community,
allow_deleted: bool, allow_deleted: bool,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
is_mod_or_admin(pool, person, community_id).await?; is_mod_or_admin(pool, person, community.id).await?;
CommunityPersonBanView::check(pool, person.id, community_id).await?; CommunityPersonBanView::check(pool, person.id, community.id).await?;
// it must be possible to restore deleted community // it must be possible to restore deleted community
if !allow_deleted { if !allow_deleted {
check_community_deleted_removed(community_id, pool).await?; check_community_deleted_removed(community)?;
} }
Ok(()) Ok(())
} }
@ -352,6 +354,16 @@ pub fn check_private_instance(
} }
} }
/// If private messages are disabled, dont allow them to be sent / received
#[tracing::instrument(skip_all)]
pub fn check_private_messages_enabled(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
if !local_user_view.local_user.enable_private_messages {
Err(LemmyErrorType::CouldntCreatePrivateMessage)?
} else {
Ok(())
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn build_federated_instances( pub async fn build_federated_instances(
local_site: &LocalSite, local_site: &LocalSite,
@ -973,12 +985,8 @@ pub fn generate_followers_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
Ok(Url::parse(&format!("{actor_id}/followers"))?.into()) Ok(Url::parse(&format!("{actor_id}/followers"))?.into())
} }
pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> { pub fn generate_inbox_url() -> LemmyResult<DbUrl> {
Ok(Url::parse(&format!("{actor_id}/inbox"))?.into()) let url = format!("{}/inbox", SETTINGS.get_protocol_and_hostname());
}
pub fn generate_shared_inbox_url(settings: &Settings) -> LemmyResult<DbUrl> {
let url = format!("{}/inbox", settings.get_protocol_and_hostname());
Ok(Url::parse(&url)?.into()) Ok(Url::parse(&url)?.into())
} }
@ -1111,7 +1119,7 @@ async fn proxy_image_link_internal(
/// Rewrite a link to go through `/api/v3/image_proxy` endpoint. This is only for remote urls and /// Rewrite a link to go through `/api/v3/image_proxy` endpoint. This is only for remote urls and
/// if image_proxy setting is enabled. /// if image_proxy setting is enabled.
pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> { pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
proxy_image_link_internal( proxy_image_link_internal(
link, link,
context.settings().pictrs_config()?.image_mode(), context.settings().pictrs_config()?.image_mode(),

View file

@ -34,5 +34,5 @@ serde_json = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
serde_with = { workspace = true } serde_with = { workspace = true }
[package.metadata.cargo-machete] [package.metadata.cargo-shear]
ignored = ["futures"] ignored = ["futures"]

View file

@ -60,7 +60,12 @@ pub async fn create_comment(
let post = post_view.post; let post = post_view.post;
let community_id = post_view.community.id; let community_id = post_view.community.id;
check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?; check_community_user_action(
&local_user_view.person,
&post_view.community,
&mut context.pool(),
)
.await?;
check_post_deleted_or_removed(&post)?; check_post_deleted_or_removed(&post)?;
// Check if post is locked, no new comments // Check if post is locked, no new comments
@ -123,7 +128,6 @@ pub async fn create_comment(
// You like your own comment by default // You like your own comment by default
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
post_id: post.id,
person_id: local_user_view.person.id, person_id: local_user_view.person.id,
score: 1, score: 1,
}; };
@ -135,8 +139,7 @@ pub async fn create_comment(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::CreateComment(inserted_comment.clone()), SendActivityData::CreateComment(inserted_comment.clone()),
&context, &context,
) )?;
.await?;
// Update the read comments, so your own new comment doesn't appear as a +1 unread // Update the read comments, so your own new comment doesn't appear as a +1 unread
update_read_comments( update_read_comments(

View file

@ -35,7 +35,7 @@ pub async fn delete_comment(
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
orig_comment.community.id, &orig_comment.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
@ -76,8 +76,7 @@ pub async fn delete_comment(
orig_comment.community, orig_comment.community,
), ),
&context, &context,
) )?;
.await?;
Ok(Json( Ok(Json(
build_comment_response( build_comment_response(

View file

@ -35,7 +35,7 @@ pub async fn remove_comment(
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
orig_comment.community.id, &orig_comment.community,
false, false,
&mut context.pool(), &mut context.pool(),
) )
@ -99,8 +99,7 @@ pub async fn remove_comment(
reason: data.reason.clone(), reason: data.reason.clone(),
}, },
&context, &context,
) )?;
.await?;
Ok(Json( Ok(Json(
build_comment_response( build_comment_response(

View file

@ -45,7 +45,7 @@ pub async fn update_comment(
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
orig_comment.community.id, &orig_comment.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
@ -97,8 +97,7 @@ pub async fn update_comment(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::UpdateComment(updated_comment.clone()), SendActivityData::UpdateComment(updated_comment.clone()),
&context, &context,
) )?;
.await?;
Ok(Json( Ok(Json(
build_comment_response( build_comment_response(

View file

@ -1,3 +1,4 @@
use super::check_community_visibility_allowed;
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair}; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
@ -8,7 +9,6 @@ use lemmy_api_common::{
generate_followers_url, generate_followers_url,
generate_inbox_url, generate_inbox_url,
generate_local_apub_endpoint, generate_local_apub_endpoint,
generate_shared_inbox_url,
get_url_blocklist, get_url_blocklist,
is_admin, is_admin,
local_site_to_slur_regex, local_site_to_slur_regex,
@ -24,6 +24,7 @@ use lemmy_db_schema::{
Community, Community,
CommunityFollower, CommunityFollower,
CommunityFollowerForm, CommunityFollowerForm,
CommunityFollowerState,
CommunityInsertForm, CommunityInsertForm,
CommunityModerator, CommunityModerator,
CommunityModeratorForm, CommunityModeratorForm,
@ -37,7 +38,11 @@ use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::{ utils::{
slurs::check_slurs, slurs::check_slurs,
validation::{is_valid_actor_name, is_valid_body_field}, validation::{
is_valid_actor_name,
is_valid_body_field,
site_or_community_description_length_check,
},
}, },
}; };
@ -58,8 +63,18 @@ pub async fn create_community(
let url_blocklist = get_url_blocklist(&context).await?; let url_blocklist = get_url_blocklist(&context).await?;
check_slurs(&data.name, &slur_regex)?; check_slurs(&data.name, &slur_regex)?;
check_slurs(&data.title, &slur_regex)?; check_slurs(&data.title, &slur_regex)?;
let description = let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
// Ensure that the sidebar has fewer than the max num characters...
if let Some(sidebar) = &sidebar {
is_valid_body_field(sidebar, false)?;
}
let description = data.description.clone();
if let Some(desc) = &description {
site_or_community_description_length_check(desc)?;
check_slurs(desc, &slur_regex)?;
}
let icon = diesel_url_create(data.icon.as_deref())?; let icon = diesel_url_create(data.icon.as_deref())?;
let icon = proxy_image_link_api(icon, &context).await?; let icon = proxy_image_link_api(icon, &context).await?;
@ -73,6 +88,8 @@ pub async fn create_community(
is_valid_body_field(desc, false)?; is_valid_body_field(desc, false)?;
} }
check_community_visibility_allowed(data.visibility, &local_user_view)?;
// Double check for duplicate community actor_ids // Double check for duplicate community actor_ids
let community_actor_id = generate_local_apub_endpoint( let community_actor_id = generate_local_apub_endpoint(
EndpointType::Community, EndpointType::Community,
@ -89,6 +106,7 @@ pub async fn create_community(
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
let community_form = CommunityInsertForm { let community_form = CommunityInsertForm {
sidebar,
description, description,
icon, icon,
banner, banner,
@ -96,8 +114,7 @@ pub async fn create_community(
actor_id: Some(community_actor_id.clone()), actor_id: Some(community_actor_id.clone()),
private_key: Some(keypair.private_key), private_key: Some(keypair.private_key),
followers_url: Some(generate_followers_url(&community_actor_id)?), followers_url: Some(generate_followers_url(&community_actor_id)?),
inbox_url: Some(generate_inbox_url(&community_actor_id)?), inbox_url: Some(generate_inbox_url()?),
shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?),
posting_restricted_to_mods: data.posting_restricted_to_mods, posting_restricted_to_mods: data.posting_restricted_to_mods,
visibility: data.visibility, visibility: data.visibility,
..CommunityInsertForm::new( ..CommunityInsertForm::new(
@ -126,7 +143,8 @@ pub async fn create_community(
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id, community_id: inserted_community.id,
person_id: local_user_view.person.id, person_id: local_user_view.person.id,
pending: false, state: Some(CommunityFollowerState::Accepted),
approver_id: None,
}; };
CommunityFollower::follow(&mut context.pool(), &community_follower_form) CommunityFollower::follow(&mut context.pool(), &community_follower_form)

View file

@ -22,13 +22,13 @@ pub async fn delete_community(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityResponse>> { ) -> LemmyResult<Json<CommunityResponse>> {
// Fetch the community mods // Fetch the community mods
let community_id = data.community_id;
let community_mods = let community_mods =
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; CommunityModeratorView::for_community(&mut context.pool(), data.community_id).await?;
let community = Community::read(&mut context.pool(), data.community_id).await?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
community_id, &community,
true, true,
&mut context.pool(), &mut context.pool(),
) )
@ -54,8 +54,7 @@ pub async fn delete_community(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::DeleteCommunity(local_user_view.person.clone(), community, data.deleted), SendActivityData::DeleteCommunity(local_user_view.person.clone(), community, data.deleted),
&context, &context,
) )?;
.await?;
build_community_response(&context, local_user_view, community_id).await build_community_response(&context, local_user_view, community_id).await
} }

View file

@ -1,5 +1,22 @@
use lemmy_api_common::utils::is_admin;
use lemmy_db_schema::CommunityVisibility;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
pub mod create; pub mod create;
pub mod delete; pub mod delete;
pub mod list; pub mod list;
pub mod remove; pub mod remove;
pub mod update; pub mod update;
/// For now only admins can make communities private, in order to prevent abuse.
/// Need to implement admin approval for new communities to get rid of this.
fn check_community_visibility_allowed(
visibility: Option<CommunityVisibility>,
local_user_view: &LocalUserView,
) -> LemmyResult<()> {
if visibility == Some(lemmy_db_schema::CommunityVisibility::Private) {
is_admin(local_user_view)?;
}
Ok(())
}

View file

@ -23,9 +23,10 @@ pub async fn remove_community(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityResponse>> { ) -> LemmyResult<Json<CommunityResponse>> {
let community = Community::read(&mut context.pool(), data.community_id).await?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
data.community_id, &community,
true, true,
&mut context.pool(), &mut context.pool(),
) )
@ -65,8 +66,7 @@ pub async fn remove_community(
removed: data.removed, removed: data.removed,
}, },
&context, &context,
) )?;
.await?;
build_community_response(&context, local_user_view, community_id).await build_community_response(&context, local_user_view, community_id).await
} }

View file

@ -1,3 +1,4 @@
use super::check_community_visibility_allowed;
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
@ -41,16 +42,19 @@ pub async fn update_community(
let url_blocklist = get_url_blocklist(&context).await?; let url_blocklist = get_url_blocklist(&context).await?;
check_slurs_opt(&data.title, &slur_regex)?; check_slurs_opt(&data.title, &slur_regex)?;
let description = diesel_string_update( let sidebar = diesel_string_update(
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context) process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context)
.await? .await?
.as_deref(), .as_deref(),
); );
if let Some(Some(desc)) = &description { if let Some(Some(sidebar)) = &sidebar {
is_valid_body_field(desc, false)?; is_valid_body_field(sidebar, false)?;
} }
check_community_visibility_allowed(data.visibility, &local_user_view)?;
let description = diesel_string_update(data.description.as_deref());
let old_community = Community::read(&mut context.pool(), data.community_id).await?; let old_community = Community::read(&mut context.pool(), data.community_id).await?;
let icon = diesel_url_update(data.icon.as_deref())?; let icon = diesel_url_update(data.icon.as_deref())?;
@ -64,7 +68,7 @@ pub async fn update_community(
// Verify its a mod (only mods can edit it) // Verify its a mod (only mods can edit it)
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
data.community_id, &old_community,
false, false,
&mut context.pool(), &mut context.pool(),
) )
@ -84,6 +88,7 @@ pub async fn update_community(
let community_form = CommunityUpdateForm { let community_form = CommunityUpdateForm {
title: data.title.clone(), title: data.title.clone(),
sidebar,
description, description,
icon, icon,
banner, banner,
@ -102,8 +107,7 @@ pub async fn update_community(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community), SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
&context, &context,
) )?;
.await?;
build_community_response(&context, local_user_view, community_id).await build_community_response(&context, local_user_view, community_id).await
} }

View file

@ -84,15 +84,9 @@ pub async fn create_post(
is_valid_body_field(body, true)?; is_valid_body_field(body, true)?;
} }
check_community_user_action( let community = Community::read(&mut context.pool(), data.community_id).await?;
&local_user_view.person, check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?;
data.community_id,
&mut context.pool(),
)
.await?;
let community_id = data.community_id;
let community = Community::read(&mut context.pool(), community_id).await?;
if community.posting_restricted_to_mods { if community.posting_restricted_to_mods {
let community_id = data.community_id; let community_id = data.community_id;
CommunityModeratorView::check_is_community_moderator( CommunityModeratorView::check_is_community_moderator(
@ -106,7 +100,7 @@ pub async fn create_post(
let language_id = validate_post_language( let language_id = validate_post_language(
&mut context.pool(), &mut context.pool(),
data.language_id, data.language_id,
community_id, data.community_id,
local_user_view.local_user.id, local_user_view.local_user.id,
) )
.await?; .await?;
@ -131,6 +125,7 @@ pub async fn create_post(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
let community_id = community.id;
let federate_post = if scheduled_publish_time.is_none() { let federate_post = if scheduled_publish_time.is_none() {
send_webmention(inserted_post.clone(), community); send_webmention(inserted_post.clone(), community);
|post| Some(SendActivityData::CreatePost(post)) |post| Some(SendActivityData::CreatePost(post))

View file

@ -8,7 +8,10 @@ use lemmy_api_common::{
utils::check_community_user_action, utils::check_community_user_action,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::post::{Post, PostUpdateForm}, source::{
community::Community,
post::{Post, PostUpdateForm},
},
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
@ -28,12 +31,8 @@ pub async fn delete_post(
Err(LemmyErrorType::CouldntUpdatePost)? Err(LemmyErrorType::CouldntUpdatePost)?
} }
check_community_user_action( let community = Community::read(&mut context.pool(), orig_post.community_id).await?;
&local_user_view.person, check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?;
orig_post.community_id,
&mut context.pool(),
)
.await?;
// Verify that only the creator can delete // Verify that only the creator can delete
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
@ -54,8 +53,7 @@ pub async fn delete_post(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::DeletePost(post, local_user_view.person.clone(), data.0), SendActivityData::DeletePost(post, local_user_view.person.clone(), data.0),
&context, &context,
) )?;
.await?;
build_post_response( build_post_response(
&context, &context,

View file

@ -2,7 +2,7 @@ use chrono::{DateTime, TimeZone, Utc};
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::source::post::Post; use lemmy_db_schema::source::post::Post;
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType}; use lemmy_utils::error::{LemmyErrorType, LemmyResult};
pub mod create; pub mod create;
pub mod delete; pub mod delete;

View file

@ -9,6 +9,7 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::Community,
local_user::LocalUser, local_user::LocalUser,
moderator::{ModRemovePost, ModRemovePostForm}, moderator::{ModRemovePost, ModRemovePostForm},
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
@ -26,11 +27,16 @@ pub async fn remove_post(
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<PostResponse>> { ) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id; let post_id = data.post_id;
// We cannot use PostView to avoid a database read here, as it doesn't return removed items
// by default. So we would have to pass in `is_mod_or_admin`, but that is impossible without
// knowing which community the post belongs to.
let orig_post = Post::read(&mut context.pool(), post_id).await?; let orig_post = Post::read(&mut context.pool(), post_id).await?;
let community = Community::read(&mut context.pool(), orig_post.community_id).await?;
check_community_mod_action( check_community_mod_action(
&local_user_view.person, &local_user_view.person,
orig_post.community_id, &community,
false, false,
&mut context.pool(), &mut context.pool(),
) )
@ -77,8 +83,7 @@ pub async fn remove_post(
removed: data.removed, removed: data.removed,
}, },
&context, &context,
) )?;
.await?;
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
} }

View file

@ -24,7 +24,7 @@ use lemmy_db_schema::{
traits::Crud, traits::Crud,
utils::{diesel_string_update, diesel_url_update, naive_now}, utils::{diesel_string_update, diesel_url_update, naive_now},
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::{ utils::{
@ -87,31 +87,31 @@ pub async fn update_post(
} }
let post_id = data.post_id; let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?; let orig_post = PostView::read(&mut context.pool(), post_id, None, false).await?;
check_community_user_action( check_community_user_action(
&local_user_view.person, &local_user_view.person,
orig_post.community_id, &orig_post.community,
&mut context.pool(), &mut context.pool(),
) )
.await?; .await?;
// Verify that only the creator can edit // Verify that only the creator can edit
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { if !Post::is_post_creator(local_user_view.person.id, orig_post.post.creator_id) {
Err(LemmyErrorType::NoPostEditAllowed)? Err(LemmyErrorType::NoPostEditAllowed)?
} }
let language_id = validate_post_language( let language_id = validate_post_language(
&mut context.pool(), &mut context.pool(),
data.language_id, data.language_id,
orig_post.community_id, orig_post.post.community_id,
local_user_view.local_user.id, local_user_view.local_user.id,
) )
.await?; .await?;
// handle changes to scheduled_publish_time // handle changes to scheduled_publish_time
let scheduled_publish_time = match ( let scheduled_publish_time = match (
orig_post.scheduled_publish_time, orig_post.post.scheduled_publish_time,
data.scheduled_publish_time, data.scheduled_publish_time,
) { ) {
// schedule time can be changed if post is still scheduled (and not published yet) // schedule time can be changed if post is still scheduled (and not published yet)
@ -143,12 +143,12 @@ pub async fn update_post(
// send out federation/webmention if necessary // send out federation/webmention if necessary
match ( match (
orig_post.scheduled_publish_time, orig_post.post.scheduled_publish_time,
data.scheduled_publish_time, data.scheduled_publish_time,
) { ) {
// schedule was removed, send create activity and webmention // schedule was removed, send create activity and webmention
(Some(_), None) => { (Some(_), None) => {
let community = Community::read(&mut context.pool(), orig_post.community_id).await?; let community = Community::read(&mut context.pool(), orig_post.community.id).await?;
send_webmention(updated_post.clone(), community); send_webmention(updated_post.clone(), community);
generate_post_link_metadata( generate_post_link_metadata(
updated_post.clone(), updated_post.clone(),
@ -174,7 +174,7 @@ pub async fn update_post(
build_post_response( build_post_response(
context.deref(), context.deref(),
orig_post.community_id, orig_post.community.id,
local_user_view, local_user_view,
post_id, post_id,
) )

View file

@ -5,6 +5,7 @@ use lemmy_api_common::{
private_message::{CreatePrivateMessage, PrivateMessageResponse}, private_message::{CreatePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_private_messages_enabled,
get_interface_language, get_interface_language,
get_url_blocklist, get_url_blocklist,
local_site_to_slur_regex, local_site_to_slur_regex,
@ -46,6 +47,16 @@ pub async fn create_private_message(
) )
.await?; .await?;
check_private_messages_enabled(&local_user_view)?;
// Don't allow local sends to people who have private messages disabled
let recipient_local_user_opt = LocalUserView::read_person(&mut context.pool(), data.recipient_id)
.await
.ok();
if let Some(recipient_local_user) = recipient_local_user_opt {
check_private_messages_enabled(&recipient_local_user)?;
}
let private_message_form = PrivateMessageInsertForm::new( let private_message_form = PrivateMessageInsertForm::new(
local_user_view.person.id, local_user_view.person.id,
data.recipient_id, data.recipient_id,
@ -78,8 +89,7 @@ pub async fn create_private_message(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::CreatePrivateMessage(view.clone()), SendActivityData::CreatePrivateMessage(view.clone()),
&context, &context,
) )?;
.await?;
Ok(Json(PrivateMessageResponse { Ok(Json(PrivateMessageResponse {
private_message_view: view, private_message_view: view,

View file

@ -42,8 +42,7 @@ pub async fn delete_private_message(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::DeletePrivateMessage(local_user_view.person, private_message, data.deleted), SendActivityData::DeletePrivateMessage(local_user_view.person, private_message, data.deleted),
&context, &context,
) )?;
.await?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
Ok(Json(PrivateMessageResponse { Ok(Json(PrivateMessageResponse {

View file

@ -59,8 +59,7 @@ pub async fn update_private_message(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::UpdatePrivateMessage(view.clone()), SendActivityData::UpdatePrivateMessage(view.clone()),
&context, &context,
) )?;
.await?;
Ok(Json(PrivateMessageResponse { Ok(Json(PrivateMessageResponse {
private_message_view: view, private_message_view: view,

View file

@ -6,7 +6,7 @@ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
site::{CreateSite, SiteResponse}, site::{CreateSite, SiteResponse},
utils::{ utils::{
generate_shared_inbox_url, generate_inbox_url,
get_url_blocklist, get_url_blocklist,
is_admin, is_admin,
local_site_rate_limit_to_rate_limit_config, local_site_rate_limit_to_rate_limit_config,
@ -29,13 +29,13 @@ use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyErrorType, LemmyResult}, error::{LemmyErrorType, LemmyResult},
utils::{ utils::{
slurs::{check_slurs, check_slurs_opt}, slurs::check_slurs,
validation::{ validation::{
build_and_check_regex, build_and_check_regex,
check_site_visibility_valid, check_site_visibility_valid,
is_valid_body_field, is_valid_body_field,
site_description_length_check,
site_name_length_check, site_name_length_check,
site_or_community_description_length_check,
}, },
}, },
}; };
@ -55,7 +55,7 @@ pub async fn create_site(
validate_create_payload(&local_site, &data)?; validate_create_payload(&local_site, &data)?;
let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into(); let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into();
let inbox_url = Some(generate_shared_inbox_url(context.settings())?); let inbox_url = Some(generate_inbox_url()?);
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
let slur_regex = local_site_to_slur_regex(&local_site); let slur_regex = local_site_to_slur_regex(&local_site);
@ -167,8 +167,8 @@ fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) ->
check_slurs(&create_site.name, &slur_regex)?; check_slurs(&create_site.name, &slur_regex)?;
if let Some(desc) = &create_site.description { if let Some(desc) = &create_site.description {
site_description_length_check(desc)?; site_or_community_description_length_check(desc)?;
check_slurs_opt(&create_site.description, &slur_regex)?; check_slurs(desc, &slur_regex)?;
} }
site_default_post_listing_type_check(&create_site.default_post_listing_type)?; site_default_post_listing_type_check(&create_site.default_post_listing_type)?;

View file

@ -40,8 +40,8 @@ use lemmy_utils::{
check_site_visibility_valid, check_site_visibility_valid,
check_urls_are_valid, check_urls_are_valid,
is_valid_body_field, is_valid_body_field,
site_description_length_check,
site_name_length_check, site_name_length_check,
site_or_community_description_length_check,
}, },
}, },
}; };
@ -219,7 +219,7 @@ fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> Lemm
} }
if let Some(desc) = &edit_site.description { if let Some(desc) = &edit_site.description {
site_description_length_check(desc)?; site_or_community_description_length_check(desc)?;
check_slurs_opt(&edit_site.description, &slur_regex)?; check_slurs_opt(&edit_site.description, &slur_regex)?;
} }

View file

@ -11,7 +11,6 @@ use lemmy_api_common::{
check_user_valid, check_user_valid,
generate_inbox_url, generate_inbox_url,
generate_local_apub_endpoint, generate_local_apub_endpoint,
generate_shared_inbox_url,
honeypot_check, honeypot_check,
local_site_to_slur_regex, local_site_to_slur_regex,
password_length_check, password_length_check,
@ -418,8 +417,7 @@ async fn create_person(
// Register the new person // Register the new person
let person_form = PersonInsertForm { let person_form = PersonInsertForm {
actor_id: Some(actor_id.clone()), actor_id: Some(actor_id.clone()),
inbox_url: Some(generate_inbox_url(&actor_id)?), inbox_url: Some(generate_inbox_url()?),
shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?),
private_key: Some(actor_keypair.private_key), private_key: Some(actor_keypair.private_key),
..PersonInsertForm::new(username.clone(), actor_keypair.public_key, instance_id) ..PersonInsertForm::new(username.clone(), actor_keypair.public_key, instance_id)
}; };

View file

@ -45,8 +45,7 @@ pub async fn delete_account(
ActivityChannel::submit_activity( ActivityChannel::submit_activity(
SendActivityData::DeleteUser(local_user_view.person, data.delete_content), SendActivityData::DeleteUser(local_user_view.person, data.delete_content),
&context, &context,
) )?;
.await?;
Ok(Json(SuccessResponse::default())) Ok(Json(SuccessResponse::default()))
} }

View file

@ -33,7 +33,6 @@ tokio = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
strum = { workspace = true } strum = { workspace = true }
url = { workspace = true } url = { workspace = true }
http = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }

View file

@ -3,11 +3,13 @@
"type": "Group", "type": "Group",
"preferredUsername": "tenforward", "preferredUsername": "tenforward",
"name": "Ten Forward", "name": "Ten Forward",
"summary": "<p>Lounge and recreation facility</p>\n<hr />\n<p>Welcome to the Enterprise!.</p>\n", "summary": "A description of ten forward.",
"content": "<p>Lounge and recreation facility</p>\n<hr />\n<p>Welcome to the Enterprise!.</p>\n",
"source": { "source": {
"content": "Lounge and recreation facility\n\n---\n\nWelcome to the Enterprise!", "content": "Lounge and recreation facility\n\n---\n\nWelcome to the Enterprise!",
"mediaType": "text/markdown" "mediaType": "text/markdown"
}, },
"mediaType": "text/html",
"sensitive": false, "sensitive": false,
"icon": { "icon": {
"type": "Image", "type": "Image",

View file

@ -0,0 +1,79 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"ostatus": "http://ostatus.org#",
"atomUri": "ostatus:atomUri",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#",
"votersCount": "toot:votersCount",
"blurhash": "toot:blurhash",
"focalPoint": {
"@container": "@list",
"@id": "toot:focalPoint"
}
}
],
"id": "https://floss.social/users/kde/statuses/113306831140126616",
"type": "Note",
"summary": null,
"inReplyTo": "https://floss.social/users/kde/statuses/113306824627995724",
"published": "2024-10-14T16:57:15Z",
"url": "https://floss.social/@kde/113306831140126616",
"attributedTo": "https://floss.social/users/kde",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": [
"https://floss.social/users/kde/followers",
"https://lemmy.kde.social/c/kde",
"https://lemmy.kde.social/c/kde/followers"
],
"sensitive": false,
"atomUri": "https://floss.social/users/kde/statuses/113306831140126616",
"inReplyToAtomUri": "https://floss.social/users/kde/statuses/113306824627995724",
"conversation": "tag:floss.social,2024-10-14:objectId=71424279:objectType=Conversation",
"content": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://lemmy.kde.social/c/kde\" class=\"u-url mention\">@<span>kde@lemmy.kde.social</span></a></span> </p><p>We also need funding 💶 to keep the gears turning! Please support us with a donation:</p><p><a href=\"https://kde.org/donate/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">kde.org/donate/</span><span class=\"invisible\"></span></a></p><p>[3/3]</p>",
"contentMap": {
"en": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://lemmy.kde.social/c/kde\" class=\"u-url mention\">@<span>kde@lemmy.kde.social</span></a></span> </p><p>We also need funding 💶 to keep the gears turning! Please support us with a donation:</p><p><a href=\"https://kde.org/donate/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">kde.org/donate/</span><span class=\"invisible\"></span></a></p><p>[3/3]</p>"
},
"attachment": [
{
"type": "Document",
"mediaType": "image/jpeg",
"url": "https://cdn.masto.host/floss/media_attachments/files/113/306/826/682/985/891/original/c8d906a2f2ab2334.jpg",
"name": "The KDE dragons Katie and Konqi stand on either side of a pot filling up with gold coins. Donate!",
"blurhash": "USQv:h-W-qI-^,W;RPs=^-R%NZxbo#sDobSc",
"focalPoint": [0.0, 0.0],
"width": 1500,
"height": 1095
}
],
"tag": [
{
"type": "Mention",
"href": "https://lemmy.kde.social/c/kde",
"name": "@kde@lemmy.kde.social"
}
],
"replies": {
"id": "https://floss.social/users/kde/statuses/113306831140126616/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://floss.social/users/kde/statuses/113306831140126616/replies?only_other_accounts=true&page=true",
"partOf": "https://floss.social/users/kde/statuses/113306831140126616/replies",
"items": []
}
},
"likes": {
"id": "https://floss.social/users/kde/statuses/113306831140126616/likes",
"type": "Collection",
"totalItems": 39
},
"shares": {
"id": "https://floss.social/users/kde/statuses/113306831140126616/shares",
"type": "Collection",
"totalItems": 24
}
}

View file

@ -1,3 +1,4 @@
use super::to_and_audience;
use crate::{ use crate::{
activities::{ activities::{
block::{generate_cc, SiteOrCommunity}, block::{generate_cc, SiteOrCommunity},
@ -7,6 +8,7 @@ use crate::{
verify_is_public, verify_is_public,
verify_mod_action, verify_mod_action,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -15,7 +17,7 @@ use crate::{
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::{activity::BlockType, public}, kinds::activity::BlockType,
protocol::verification::verify_domains_match, protocol::verification::verify_domains_match,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
@ -39,10 +41,7 @@ use lemmy_db_schema::{
}, },
traits::{Bannable, Crud, Followable}, traits::{Bannable, Crud, Followable},
}; };
use lemmy_utils::{ use lemmy_utils::error::{FederationError, LemmyError, LemmyResult};
error::{LemmyError, LemmyResult},
LemmyErrorType,
};
use url::Url; use url::Url;
impl BlockUser { impl BlockUser {
@ -55,14 +54,10 @@ impl BlockUser {
expires: Option<DateTime<Utc>>, expires: Option<DateTime<Utc>>,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<BlockUser> { ) -> LemmyResult<BlockUser> {
let audience = if let SiteOrCommunity::Community(c) = target { let (to, audience) = to_and_audience(target)?;
Some(c.id().into())
} else {
None
};
Ok(BlockUser { Ok(BlockUser {
actor: mod_.id().into(), actor: mod_.id().into(),
to: vec![public()], to,
object: user.id().into(), object: user.id().into(),
cc: generate_cc(target, &mut context.pool()).await?, cc: generate_cc(target, &mut context.pool()).await?,
target: target.id(), target: target.id(),
@ -128,14 +123,14 @@ impl ActivityHandler for BlockUser {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> { async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
match self.target.dereference(context).await? { match self.target.dereference(context).await? {
SiteOrCommunity::Site(site) => { SiteOrCommunity::Site(site) => {
verify_is_public(&self.to, &self.cc)?;
let domain = self let domain = self
.object .object
.inner() .inner()
.domain() .domain()
.ok_or(LemmyErrorType::UrlWithoutDomain)?; .ok_or(FederationError::UrlWithoutDomain)?;
if context.settings().hostname == domain { if context.settings().hostname == domain {
return Err( return Err(
anyhow!("Site bans from remote instance can't affect user's home instance").into(), anyhow!("Site bans from remote instance can't affect user's home instance").into(),
@ -146,6 +141,7 @@ impl ActivityHandler for BlockUser {
verify_domains_match(&site.id(), self.object.inner())?; verify_domains_match(&site.id(), self.object.inner())?;
} }
SiteOrCommunity::Community(community) => { SiteOrCommunity::Community(community) => {
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
verify_mod_action(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?;
} }
@ -197,11 +193,7 @@ impl ActivityHandler for BlockUser {
CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form).await?; CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form).await?;
// Also unsubscribe them from the community, if they are subscribed // Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm { let community_follower_form = CommunityFollowerForm::new(community.id, blocked_person.id);
community_id: community.id,
person_id: blocked_person.id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await .await
.ok(); .ok();

View file

@ -1,3 +1,4 @@
use super::generate_to;
use crate::{ use crate::{
objects::{community::ApubCommunity, instance::ApubSite}, objects::{community::ApubCommunity, instance::ApubSite},
protocol::{ protocol::{
@ -8,6 +9,7 @@ use crate::{
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::public,
traits::{Actor, Object}, traits::{Actor, Object},
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -205,3 +207,13 @@ pub(crate) async fn send_ban_from_community(
.await .await
} }
} }
fn to_and_audience(
target: &SiteOrCommunity,
) -> LemmyResult<(Vec<Url>, Option<ObjectId<ApubCommunity>>)> {
Ok(if let SiteOrCommunity::Community(c) = target {
(vec![generate_to(c)?], Some(c.id().into()))
} else {
(vec![public()], None)
})
}

View file

@ -1,3 +1,4 @@
use super::to_and_audience;
use crate::{ use crate::{
activities::{ activities::{
block::{generate_cc, SiteOrCommunity}, block::{generate_cc, SiteOrCommunity},
@ -5,6 +6,7 @@ use crate::{
generate_activity_id, generate_activity_id,
send_lemmy_activity, send_lemmy_activity,
verify_is_public, verify_is_public,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -13,7 +15,7 @@ use crate::{
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::{activity::UndoType, public}, kinds::activity::UndoType,
protocol::verification::verify_domains_match, protocol::verification::verify_domains_match,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
@ -44,11 +46,7 @@ impl UndoBlockUser {
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let block = BlockUser::new(target, user, mod_, None, reason, None, context).await?; let block = BlockUser::new(target, user, mod_, None, reason, None, context).await?;
let audience = if let SiteOrCommunity::Community(c) = target { let (to, audience) = to_and_audience(target)?;
Some(c.id().into())
} else {
None
};
let id = generate_activity_id( let id = generate_activity_id(
UndoType::Undo, UndoType::Undo,
@ -56,7 +54,7 @@ impl UndoBlockUser {
)?; )?;
let undo = UndoBlockUser { let undo = UndoBlockUser {
actor: mod_.id().into(), actor: mod_.id().into(),
to: vec![public()], to,
object: block, object: block,
cc: generate_cc(target, &mut context.pool()).await?, cc: generate_cc(target, &mut context.pool()).await?,
kind: UndoType::Undo, kind: UndoType::Undo,
@ -94,7 +92,6 @@ impl ActivityHandler for UndoBlockUser {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> { async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
verify_domains_match(self.actor.inner(), self.object.actor.inner())?; verify_domains_match(self.actor.inner(), self.object.actor.inner())?;
self.object.verify(context).await?; self.object.verify(context).await?;
Ok(()) Ok(())
@ -108,6 +105,7 @@ impl ActivityHandler for UndoBlockUser {
let blocked_person = self.object.object.dereference(context).await?; let blocked_person = self.object.object.dereference(context).await?;
match self.object.target.dereference(context).await? { match self.object.target.dereference(context).await? {
SiteOrCommunity::Site(_site) => { SiteOrCommunity::Site(_site) => {
verify_is_public(&self.to, &self.cc)?;
let blocked_person = Person::update( let blocked_person = Person::update(
&mut context.pool(), &mut context.pool(),
blocked_person.id, blocked_person.id,
@ -135,6 +133,7 @@ impl ActivityHandler for UndoBlockUser {
ModBan::create(&mut context.pool(), &form).await?; ModBan::create(&mut context.pool(), &form).await?;
} }
SiteOrCommunity::Community(community) => { SiteOrCommunity::Community(community) => {
verify_visibility(&self.to, &self.cc, &community)?;
let community_user_ban_form = CommunityPersonBanForm { let community_user_ban_form = CommunityPersonBanForm {
community_id: community.id, community_id: community.id,
person_id: blocked_person.id, person_id: blocked_person.id,

View file

@ -2,9 +2,10 @@ use crate::{
activities::{ activities::{
generate_activity_id, generate_activity_id,
generate_announce_activity_id, generate_announce_activity_id,
generate_to,
send_lemmy_activity, send_lemmy_activity,
verify_is_public,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -18,7 +19,7 @@ use crate::{
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::{activity::AnnounceType, public}, kinds::activity::AnnounceType,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
@ -26,7 +27,7 @@ use lemmy_db_schema::{
source::{activity::ActivitySendTargets, community::CommunityFollower}, source::{activity::ActivitySendTargets, community::CommunityFollower},
CommunityVisibility, CommunityVisibility,
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult};
use serde_json::Value; use serde_json::Value;
use url::Url; use url::Url;
@ -54,7 +55,7 @@ impl ActivityHandler for RawAnnouncableActivities {
// This is only for sending, not receiving so we reject it. // This is only for sending, not receiving so we reject it.
if let AnnouncableActivities::Page(_) = activity { if let AnnouncableActivities::Page(_) = activity {
Err(LemmyErrorType::CannotReceivePage)? Err(FederationError::CannotReceivePage)?
} }
// Need to treat community as optional here because `Delete/PrivateMessage` gets routed through // Need to treat community as optional here because `Delete/PrivateMessage` gets routed through
@ -92,7 +93,7 @@ impl AnnounceActivity {
generate_announce_activity_id(inner_kind, &context.settings().get_protocol_and_hostname())?; generate_announce_activity_id(inner_kind, &context.settings().get_protocol_and_hostname())?;
Ok(AnnounceActivity { Ok(AnnounceActivity {
actor: community.id().into(), actor: community.id().into(),
to: vec![public()], to: vec![generate_to(community)?],
object: IdOrNestedObject::NestedObject(object), object: IdOrNestedObject::NestedObject(object),
cc: community cc: community
.followers_url .followers_url
@ -154,7 +155,6 @@ impl ActivityHandler for AnnounceActivity {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, _context: &Data<Self::DataType>) -> LemmyResult<()> { async fn verify(&self, _context: &Data<Self::DataType>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
Ok(()) Ok(())
} }
@ -165,10 +165,11 @@ impl ActivityHandler for AnnounceActivity {
// This is only for sending, not receiving so we reject it. // This is only for sending, not receiving so we reject it.
if let AnnouncableActivities::Page(_) = object { if let AnnouncableActivities::Page(_) = object {
Err(LemmyErrorType::CannotReceivePage)? Err(FederationError::CannotReceivePage)?
} }
let community = object.community(context).await?; let community = object.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
can_accept_activity_in_community(&Some(community), context).await?; can_accept_activity_in_community(&Some(community), context).await?;
// verify here in order to avoid fetching the object twice over http // verify here in order to avoid fetching the object twice over http

View file

@ -2,9 +2,10 @@ use crate::{
activities::{ activities::{
community::send_activity_in_community, community::send_activity_in_community,
generate_activity_id, generate_activity_id,
verify_is_public, generate_to,
verify_mod_action, verify_mod_action,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -17,7 +18,7 @@ use crate::{
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::{activity::AddType, public}, kinds::activity::AddType,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::{
@ -53,7 +54,7 @@ impl CollectionAdd {
)?; )?;
let add = CollectionAdd { let add = CollectionAdd {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![public()], to: vec![generate_to(community)?],
object: added_mod.id(), object: added_mod.id(),
target: generate_moderators_url(&community.actor_id)?.into(), target: generate_moderators_url(&community.actor_id)?.into(),
cc: vec![community.id()], cc: vec![community.id()],
@ -79,7 +80,7 @@ impl CollectionAdd {
)?; )?;
let add = CollectionAdd { let add = CollectionAdd {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![public()], to: vec![generate_to(community)?],
object: featured_post.ap_id.clone().into(), object: featured_post.ap_id.clone().into(),
target: generate_featured_url(&community.actor_id)?.into(), target: generate_featured_url(&community.actor_id)?.into(),
cc: vec![community.id()], cc: vec![community.id()],
@ -115,8 +116,8 @@ impl ActivityHandler for CollectionAdd {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> { async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
verify_mod_action(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?;
Ok(()) Ok(())

View file

@ -2,9 +2,10 @@ use crate::{
activities::{ activities::{
community::send_activity_in_community, community::send_activity_in_community,
generate_activity_id, generate_activity_id,
verify_is_public, generate_to,
verify_mod_action, verify_mod_action,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -14,7 +15,7 @@ use crate::{
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::{activity::RemoveType, public}, kinds::activity::RemoveType,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::{
@ -48,7 +49,7 @@ impl CollectionRemove {
)?; )?;
let remove = CollectionRemove { let remove = CollectionRemove {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![public()], to: vec![generate_to(community)?],
object: removed_mod.id(), object: removed_mod.id(),
target: generate_moderators_url(&community.actor_id)?.into(), target: generate_moderators_url(&community.actor_id)?.into(),
id: id.clone(), id: id.clone(),
@ -74,7 +75,7 @@ impl CollectionRemove {
)?; )?;
let remove = CollectionRemove { let remove = CollectionRemove {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![public()], to: vec![generate_to(community)?],
object: featured_post.ap_id.clone().into(), object: featured_post.ap_id.clone().into(),
target: generate_featured_url(&community.actor_id)?.into(), target: generate_featured_url(&community.actor_id)?.into(),
cc: vec![community.id()], cc: vec![community.id()],
@ -110,8 +111,8 @@ impl ActivityHandler for CollectionRemove {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> { async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
verify_mod_action(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?;
Ok(()) Ok(())

View file

@ -3,9 +3,10 @@ use crate::{
check_community_deleted_or_removed, check_community_deleted_or_removed,
community::send_activity_in_community, community::send_activity_in_community,
generate_activity_id, generate_activity_id,
verify_is_public, generate_to,
verify_mod_action, verify_mod_action,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -18,7 +19,7 @@ use crate::{
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::{activity::UndoType, public}, kinds::activity::UndoType,
traits::ActivityHandler, traits::ActivityHandler,
}; };
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
@ -49,8 +50,8 @@ impl ActivityHandler for LockPage {
} }
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), Self::Error> {
verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
check_community_deleted_or_removed(&community)?; check_community_deleted_or_removed(&community)?;
verify_mod_action(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?;
@ -92,8 +93,8 @@ impl ActivityHandler for UndoLockPage {
} }
async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn verify(&self, context: &Data<Self::DataType>) -> Result<(), Self::Error> {
verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
check_community_deleted_or_removed(&community)?; check_community_deleted_or_removed(&community)?;
verify_mod_action(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?;
@ -137,7 +138,7 @@ pub(crate) async fn send_lock_post(
let community_id = community.actor_id.inner().clone(); let community_id = community.actor_id.inner().clone();
let lock = LockPage { let lock = LockPage {
actor: actor.actor_id.clone().into(), actor: actor.actor_id.clone().into(),
to: vec![public()], to: vec![generate_to(&community)?],
object: ObjectId::from(post.ap_id), object: ObjectId::from(post.ap_id),
cc: vec![community_id.clone()], cc: vec![community_id.clone()],
kind: LockType::Lock, kind: LockType::Lock,
@ -153,7 +154,7 @@ pub(crate) async fn send_lock_post(
)?; )?;
let undo = UndoLockPage { let undo = UndoLockPage {
actor: lock.actor.clone(), actor: lock.actor.clone(),
to: vec![public()], to: vec![generate_to(&community)?],
cc: lock.cc.clone(), cc: lock.cc.clone(),
kind: UndoType::Undo, kind: UndoType::Undo,
id, id,

View file

@ -2,9 +2,10 @@ use crate::{
activities::{ activities::{
community::send_activity_in_community, community::send_activity_in_community,
generate_activity_id, generate_activity_id,
verify_is_public, generate_to,
verify_mod_action, verify_mod_action,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -13,7 +14,7 @@ use crate::{
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::{activity::UpdateType, public}, kinds::activity::UpdateType,
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
@ -42,7 +43,7 @@ pub(crate) async fn send_update_community(
)?; )?;
let update = UpdateCommunity { let update = UpdateCommunity {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![public()], to: vec![generate_to(&community)?],
object: Box::new(community.clone().into_json(&context).await?), object: Box::new(community.clone().into_json(&context).await?),
cc: vec![community.id()], cc: vec![community.id()],
kind: UpdateType::Update, kind: UpdateType::Update,
@ -77,8 +78,8 @@ impl ActivityHandler for UpdateCommunity {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> { async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
verify_mod_action(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?;
ApubCommunity::verify(&self.object, &community.actor_id.clone().into(), context).await?; ApubCommunity::verify(&self.object, &community.actor_id.clone().into(), context).await?;
@ -106,8 +107,14 @@ impl ActivityHandler for UpdateCommunity {
icon: Some(self.object.icon.map(|i| i.url.into())), icon: Some(self.object.icon.map(|i| i.url.into())),
banner: Some(self.object.image.map(|i| i.url.into())), banner: Some(self.object.image.map(|i| i.url.into())),
followers_url: self.object.followers.map(Into::into), followers_url: self.object.followers.map(Into::into),
inbox_url: Some(self.object.inbox.into()), inbox_url: Some(
shared_inbox_url: Some(self.object.endpoints.map(|e| e.shared_inbox.into())), self
.object
.endpoints
.map(|e| e.shared_inbox)
.unwrap_or(self.object.inbox)
.into(),
),
moderators_url: self.object.attributed_to.map(Into::into), moderators_url: self.object.attributed_to.map(Into::into),
posting_restricted_to_mods: self.object.posting_restricted_to_mods, posting_restricted_to_mods: self.object.posting_restricted_to_mods,
featured_url: self.object.featured.map(Into::into), featured_url: self.object.featured.map(Into::into),

View file

@ -3,8 +3,9 @@ use crate::{
check_community_deleted_or_removed, check_community_deleted_or_removed,
community::send_activity_in_community, community::send_activity_in_community,
generate_activity_id, generate_activity_id,
verify_is_public, generate_to,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -18,7 +19,6 @@ use crate::{
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId, fetch::object_id::ObjectId,
kinds::public,
protocol::verification::{verify_domains_match, verify_urls_match}, protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
@ -70,7 +70,7 @@ impl CreateOrUpdateNote {
let create_or_update = CreateOrUpdateNote { let create_or_update = CreateOrUpdateNote {
actor: person.id().into(), actor: person.id().into(),
to: vec![public()], to: vec![generate_to(&community)?],
cc: note.cc.clone(), cc: note.cc.clone(),
tag: note.tag.clone(), tag: note.tag.clone(),
object: note, object: note,
@ -118,9 +118,9 @@ impl ActivityHandler for CreateOrUpdateNote {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> { async fn verify(&self, context: &Data<Self::DataType>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
let post = self.object.get_parents(context).await?.0; let post = self.object.get_parents(context).await?.0;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
verify_domains_match(self.actor.inner(), self.object.id.inner())?; verify_domains_match(self.actor.inner(), self.object.id.inner())?;
@ -153,7 +153,6 @@ impl ActivityHandler for CreateOrUpdateNote {
// author likes their own comment by default // author likes their own comment by default
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: comment.id, comment_id: comment.id,
post_id: comment.post_id,
person_id: comment.creator_id, person_id: comment.creator_id,
score: 1, score: 1,
}; };

View file

@ -3,8 +3,9 @@ use crate::{
check_community_deleted_or_removed, check_community_deleted_or_removed,
community::send_activity_in_community, community::send_activity_in_community,
generate_activity_id, generate_activity_id,
verify_is_public, generate_to,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
@ -16,7 +17,6 @@ use crate::{
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::public,
protocol::verification::{verify_domains_match, verify_urls_match}, protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
@ -49,7 +49,7 @@ impl CreateOrUpdatePage {
)?; )?;
Ok(CreateOrUpdatePage { Ok(CreateOrUpdatePage {
actor: actor.id().into(), actor: actor.id().into(),
to: vec![public()], to: vec![generate_to(community)?],
object: post.into_json(context).await?, object: post.into_json(context).await?,
cc: vec![community.id()], cc: vec![community.id()],
kind, kind,
@ -102,8 +102,8 @@ impl ActivityHandler for CreateOrUpdatePage {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> { async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> {
verify_is_public(&self.to, &self.cc)?;
let community = self.community(context).await?; let community = self.community(context).await?;
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?; verify_person_in_community(&self.actor, &community, context).await?;
check_community_deleted_or_removed(&community)?; check_community_deleted_or_removed(&community)?;
verify_domains_match(self.actor.inner(), self.object.id.inner())?; verify_domains_match(self.actor.inner(), self.object.id.inner())?;

View file

@ -27,7 +27,7 @@ use lemmy_db_schema::{
}, },
traits::{Crud, Reportable}, traits::{Crud, Reportable},
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult};
use url::Url; use url::Url;
#[async_trait::async_trait] #[async_trait::async_trait]
@ -84,7 +84,7 @@ impl Delete {
pub(in crate::activities::deletion) fn new( pub(in crate::activities::deletion) fn new(
actor: &ApubPerson, actor: &ApubPerson,
object: DeletableObjects, object: DeletableObjects,
to: Url, to: Vec<Url>,
community: Option<&Community>, community: Option<&Community>,
summary: Option<String>, summary: Option<String>,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
@ -96,7 +96,7 @@ impl Delete {
let cc: Option<Url> = community.map(|c| c.actor_id.clone().into()); let cc: Option<Url> = community.map(|c| c.actor_id.clone().into());
Ok(Delete { Ok(Delete {
actor: actor.actor_id.clone().into(), actor: actor.actor_id.clone().into(),
to: vec![to], to,
object: IdOrNestedObject::Id(object.id()), object: IdOrNestedObject::Id(object.id()),
cc: cc.into_iter().collect(), cc: cc.into_iter().collect(),
kind: DeleteType::Delete, kind: DeleteType::Delete,
@ -118,7 +118,7 @@ pub(in crate::activities) async fn receive_remove_action(
match DeletableObjects::read_from_db(object, context).await? { match DeletableObjects::read_from_db(object, context).await? {
DeletableObjects::Community(community) => { DeletableObjects::Community(community) => {
if community.local { if community.local {
Err(LemmyErrorType::OnlyLocalAdminCanRemoveCommunity)? Err(FederationError::OnlyLocalAdminCanRemoveCommunity)?
} }
let form = ModRemoveCommunityForm { let form = ModRemoveCommunityForm {
mod_person_id: actor.id, mod_person_id: actor.id,

View file

@ -1,11 +1,12 @@
use super::{generate_to, verify_is_public};
use crate::{ use crate::{
activities::{ activities::{
community::send_activity_in_community, community::send_activity_in_community,
send_lemmy_activity, send_lemmy_activity,
verify_is_public,
verify_mod_action, verify_mod_action,
verify_person, verify_person,
verify_person_in_community, verify_person_in_community,
verify_visibility,
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
objects::{ objects::{
@ -59,11 +60,12 @@ pub(crate) async fn send_apub_delete_in_community(
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let actor = ApubPerson::from(actor); let actor = ApubPerson::from(actor);
let is_mod_action = reason.is_some(); let is_mod_action = reason.is_some();
let to = vec![generate_to(&community)?];
let activity = if deleted { let activity = if deleted {
let delete = Delete::new(&actor, object, public(), Some(&community), reason, context)?; let delete = Delete::new(&actor, object, to, Some(&community), reason, context)?;
AnnouncableActivities::Delete(delete) AnnouncableActivities::Delete(delete)
} else { } else {
let undo = UndoDelete::new(&actor, object, public(), Some(&community), reason, context)?; let undo = UndoDelete::new(&actor, object, to, Some(&community), reason, context)?;
AnnouncableActivities::UndoDelete(undo) AnnouncableActivities::UndoDelete(undo)
}; };
send_activity_in_community( send_activity_in_community(
@ -92,10 +94,10 @@ pub(crate) async fn send_apub_delete_private_message(
let deletable = DeletableObjects::PrivateMessage(pm.into()); let deletable = DeletableObjects::PrivateMessage(pm.into());
let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox()); let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox());
if deleted { if deleted {
let delete: Delete = Delete::new(actor, deletable, recipient.id(), None, None, &context)?; let delete: Delete = Delete::new(actor, deletable, vec![recipient.id()], None, None, &context)?;
send_lemmy_activity(&context, delete, actor, inbox, true).await?; send_lemmy_activity(&context, delete, actor, inbox, true).await?;
} else { } else {
let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, &context)?; let undo = UndoDelete::new(actor, deletable, vec![recipient.id()], None, None, &context)?;
send_lemmy_activity(&context, undo, actor, inbox, true).await?; send_lemmy_activity(&context, undo, actor, inbox, true).await?;
}; };
Ok(()) Ok(())
@ -109,7 +111,7 @@ pub async fn send_apub_delete_user(
let person: ApubPerson = person.into(); let person: ApubPerson = person.into();
let deletable = DeletableObjects::Person(person.clone()); let deletable = DeletableObjects::Person(person.clone());
let mut delete: Delete = Delete::new(&person, deletable, public(), None, None, &context)?; let mut delete: Delete = Delete::new(&person, deletable, vec![public()], None, None, &context)?;
delete.remove_data = Some(remove_data); delete.remove_data = Some(remove_data);
let inboxes = ActivitySendTargets::to_all_instances(); let inboxes = ActivitySendTargets::to_all_instances();
@ -170,7 +172,7 @@ pub(in crate::activities) async fn verify_delete_activity(
let object = DeletableObjects::read_from_db(activity.object.id(), context).await?; let object = DeletableObjects::read_from_db(activity.object.id(), context).await?;
match object { match object {
DeletableObjects::Community(community) => { DeletableObjects::Community(community) => {
verify_is_public(&activity.to, &[])?; verify_visibility(&activity.to, &[], &community)?;
if community.local { if community.local {
// can only do this check for local community, in remote case it would try to fetch the // can only do this check for local community, in remote case it would try to fetch the
// deleted community (which fails) // deleted community (which fails)
@ -185,22 +187,24 @@ pub(in crate::activities) async fn verify_delete_activity(
verify_urls_match(person.actor_id.inner(), activity.object.id())?; verify_urls_match(person.actor_id.inner(), activity.object.id())?;
} }
DeletableObjects::Post(p) => { DeletableObjects::Post(p) => {
verify_is_public(&activity.to, &[])?; let community = activity.community(context).await?;
verify_visibility(&activity.to, &[], &community)?;
verify_delete_post_or_comment( verify_delete_post_or_comment(
&activity.actor, &activity.actor,
&p.ap_id.clone().into(), &p.ap_id.clone().into(),
&activity.community(context).await?, &community,
is_mod_action, is_mod_action,
context, context,
) )
.await?; .await?;
} }
DeletableObjects::Comment(c) => { DeletableObjects::Comment(c) => {
verify_is_public(&activity.to, &[])?; let community = activity.community(context).await?;
verify_visibility(&activity.to, &[], &community)?;
verify_delete_post_or_comment( verify_delete_post_or_comment(
&activity.actor, &activity.actor,
&c.ap_id.clone().into(), &c.ap_id.clone().into(),
&activity.community(context).await?, &community,
is_mod_action, is_mod_action,
context, context,
) )

View file

@ -25,7 +25,7 @@ use lemmy_db_schema::{
}, },
traits::Crud, traits::Crud,
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult};
use url::Url; use url::Url;
#[async_trait::async_trait] #[async_trait::async_trait]
@ -68,7 +68,7 @@ impl UndoDelete {
pub(in crate::activities::deletion) fn new( pub(in crate::activities::deletion) fn new(
actor: &ApubPerson, actor: &ApubPerson,
object: DeletableObjects, object: DeletableObjects,
to: Url, to: Vec<Url>,
community: Option<&Community>, community: Option<&Community>,
summary: Option<String>, summary: Option<String>,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
@ -82,7 +82,7 @@ impl UndoDelete {
let cc: Option<Url> = community.map(|c| c.actor_id.clone().into()); let cc: Option<Url> = community.map(|c| c.actor_id.clone().into());
Ok(UndoDelete { Ok(UndoDelete {
actor: actor.actor_id.clone().into(), actor: actor.actor_id.clone().into(),
to: vec![to], to,
object, object,
cc: cc.into_iter().collect(), cc: cc.into_iter().collect(),
kind: UndoType::Undo, kind: UndoType::Undo,
@ -100,7 +100,7 @@ impl UndoDelete {
match DeletableObjects::read_from_db(object, context).await? { match DeletableObjects::read_from_db(object, context).await? {
DeletableObjects::Community(community) => { DeletableObjects::Community(community) => {
if community.local { if community.local {
Err(LemmyErrorType::OnlyLocalAdminCanRestoreCommunity)? Err(FederationError::OnlyLocalAdminCanRestoreCommunity)?
} }
let form = ModRemoveCommunityForm { let form = ModRemoveCommunityForm {
mod_person_id: actor.id, mod_person_id: actor.id,

View file

@ -20,7 +20,7 @@ use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
community::{CommunityFollower, CommunityFollowerForm}, community::{CommunityFollower, CommunityFollowerForm, CommunityFollowerState},
person::{PersonFollower, PersonFollowerForm}, person::{PersonFollower, PersonFollowerForm},
}, },
traits::Followable, traits::Followable,
@ -102,21 +102,25 @@ impl ActivityHandler for Follow {
pending: false, pending: false,
}; };
PersonFollower::follow(&mut context.pool(), &form).await?; PersonFollower::follow(&mut context.pool(), &form).await?;
AcceptFollow::send(self, context).await?;
} }
UserOrCommunity::Community(c) => { UserOrCommunity::Community(c) => {
// Dont allow following local-only community via federation. let state = Some(match c.visibility {
if c.visibility != CommunityVisibility::Public { CommunityVisibility::Public => CommunityFollowerState::Accepted,
return Err(LemmyErrorType::NotFound.into()); CommunityVisibility::Private => CommunityFollowerState::ApprovalRequired,
} // Dont allow following local-only community via federation.
CommunityVisibility::LocalOnly => return Err(LemmyErrorType::NotFound.into()),
});
let form = CommunityFollowerForm { let form = CommunityFollowerForm {
community_id: c.id, state,
person_id: actor.id, ..CommunityFollowerForm::new(c.id, actor.id)
pending: false,
}; };
CommunityFollower::follow(&mut context.pool(), &form).await?; CommunityFollower::follow(&mut context.pool(), &form).await?;
if c.visibility == CommunityVisibility::Public {
AcceptFollow::send(self, context).await?;
}
} }
} }
Ok(())
AcceptFollow::send(self, context).await
} }
} }

View file

@ -1,15 +1,26 @@
use super::generate_activity_id;
use crate::{ use crate::{
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{follow::Follow, undo_follow::UndoFollow}, protocol::activities::following::{
accept::AcceptFollow,
follow::Follow,
reject::RejectFollow,
undo_follow::UndoFollow,
},
}; };
use activitypub_federation::config::Data; use activitypub_federation::{config::Data, kinds::activity::FollowType};
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::source::{community::Community, person::Person}; use lemmy_db_schema::{
newtypes::{CommunityId, PersonId},
source::{community::Community, person::Person},
traits::Crud,
};
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
pub mod accept; pub(crate) mod accept;
pub mod follow; pub(crate) mod follow;
pub mod undo_follow; pub(crate) mod reject;
pub(crate) mod undo_follow;
pub async fn send_follow_community( pub async fn send_follow_community(
community: Community, community: Community,
@ -25,3 +36,29 @@ pub async fn send_follow_community(
UndoFollow::send(&actor, &community, context).await UndoFollow::send(&actor, &community, context).await
} }
} }
pub async fn send_accept_or_reject_follow(
community_id: CommunityId,
person_id: PersonId,
accepted: bool,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
let community = Community::read(&mut context.pool(), community_id).await?;
let person = Person::read(&mut context.pool(), person_id).await?;
let follow = Follow {
actor: person.actor_id.into(),
to: Some([community.actor_id.clone().into()]),
object: community.actor_id.into(),
kind: FollowType::Follow,
id: generate_activity_id(
FollowType::Follow,
&context.settings().get_protocol_and_hostname(),
)?,
};
if accepted {
AcceptFollow::send(follow, context).await
} else {
RejectFollow::send(follow, context).await
}
}

View file

@ -0,0 +1,79 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity},
insert_received_activity,
protocol::activities::following::{follow::Follow, reject::RejectFollow},
};
use activitypub_federation::{
config::Data,
kinds::activity::RejectType,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
source::{
activity::ActivitySendTargets,
community::{CommunityFollower, CommunityFollowerForm},
},
traits::Followable,
};
use lemmy_utils::error::{LemmyError, LemmyResult};
use url::Url;
impl RejectFollow {
#[tracing::instrument(skip_all)]
pub async fn send(follow: Follow, context: &Data<LemmyContext>) -> LemmyResult<()> {
let user_or_community = follow.object.dereference_local(context).await?;
let person = follow.actor.clone().dereference(context).await?;
let reject = RejectFollow {
actor: user_or_community.id().into(),
to: Some([person.id().into()]),
object: follow,
kind: RejectType::Reject,
id: generate_activity_id(
RejectType::Reject,
&context.settings().get_protocol_and_hostname(),
)?,
};
let inbox = ActivitySendTargets::to_inbox(person.shared_inbox_or_inbox());
send_lemmy_activity(context, reject, &user_or_community, inbox, true).await
}
}
/// Handle rejected follows
#[async_trait::async_trait]
impl ActivityHandler for RejectFollow {
type DataType = LemmyContext;
type Error = LemmyError;
fn id(&self) -> &Url {
&self.id
}
fn actor(&self) -> &Url {
self.actor.inner()
}
#[tracing::instrument(skip_all)]
async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> {
verify_urls_match(self.actor.inner(), self.object.object.inner())?;
self.object.verify(context).await?;
if let Some(to) = &self.to {
verify_urls_match(to[0].inner(), self.object.actor.inner())?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
insert_received_activity(&self.id, context).await?;
let community = self.actor.dereference(context).await?;
let person = self.object.actor.dereference(context).await?;
// remove the follow
let form = CommunityFollowerForm::new(community.id, person.id);
CommunityFollower::unfollow(&mut context.pool(), &form).await?;
Ok(())
}
}

View file

@ -90,11 +90,7 @@ impl ActivityHandler for UndoFollow {
PersonFollower::unfollow(&mut context.pool(), &form).await?; PersonFollower::unfollow(&mut context.pool(), &form).await?;
} }
UserOrCommunity::Community(c) => { UserOrCommunity::Community(c) => {
let form = CommunityFollowerForm { let form = CommunityFollowerForm::new(c.id, person.id);
community_id: c.id,
person_id: person.id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &form).await?; CommunityFollower::unfollow(&mut context.pool(), &form).await?;
} }
} }

View file

@ -30,6 +30,7 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use anyhow::anyhow; use anyhow::anyhow;
use following::send_accept_or_reject_follow;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
@ -40,9 +41,10 @@ use lemmy_db_schema::{
community::Community, community::Community,
}, },
traits::Crud, traits::Crud,
CommunityVisibility,
}; };
use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView}; use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
use serde::Serialize; use serde::Serialize;
use tracing::info; use tracing::info;
use url::{ParseError, Url}; use url::{ParseError, Url};
@ -81,7 +83,7 @@ pub(crate) async fn verify_person_in_community(
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let person = person_id.dereference(context).await?; let person = person_id.dereference(context).await?;
if person.banned { if person.banned {
Err(LemmyErrorType::PersonIsBannedFromSite( Err(FederationError::PersonIsBannedFromSite(
person.actor_id.to_string(), person.actor_id.to_string(),
))? ))?
} }
@ -114,19 +116,41 @@ pub(crate) async fn verify_mod_action(
pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> { pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> {
if ![to, cc].iter().any(|set| set.contains(&public())) { if ![to, cc].iter().any(|set| set.contains(&public())) {
Err(LemmyErrorType::ObjectIsNotPublic)? Err(FederationError::ObjectIsNotPublic)?
} else { } else {
Ok(()) Ok(())
} }
} }
/// Returns an error if object visibility doesnt match community visibility
/// (ie content in private community must also be private).
pub(crate) fn verify_visibility(to: &[Url], cc: &[Url], community: &Community) -> LemmyResult<()> {
use CommunityVisibility::*;
let object_is_public = [to, cc].iter().any(|set| set.contains(&public()));
match community.visibility {
Public if !object_is_public => Err(FederationError::ObjectIsNotPublic)?,
Private if object_is_public => Err(FederationError::ObjectIsNotPrivate)?,
LocalOnly => Err(LemmyErrorType::NotFound.into()),
_ => Ok(()),
}
}
/// Marks object as public only if the community is public
pub(crate) fn generate_to(community: &Community) -> LemmyResult<Url> {
if community.visibility == CommunityVisibility::Public {
Ok(public())
} else {
Ok(Url::parse(&format!("{}/followers", community.actor_id))?)
}
}
pub(crate) fn verify_community_matches<T>(a: &ObjectId<ApubCommunity>, b: T) -> LemmyResult<()> pub(crate) fn verify_community_matches<T>(a: &ObjectId<ApubCommunity>, b: T) -> LemmyResult<()>
where where
T: Into<ObjectId<ApubCommunity>>, T: Into<ObjectId<ApubCommunity>>,
{ {
let b: ObjectId<ApubCommunity> = b.into(); let b: ObjectId<ApubCommunity> = b.into();
if a != &b { if a != &b {
Err(LemmyErrorType::InvalidCommunity)? Err(FederationError::InvalidCommunity)?
} else { } else {
Ok(()) Ok(())
} }
@ -134,7 +158,7 @@ where
pub(crate) fn check_community_deleted_or_removed(community: &Community) -> LemmyResult<()> { pub(crate) fn check_community_deleted_or_removed(community: &Community) -> LemmyResult<()> {
if community.deleted || community.removed { if community.deleted || community.removed {
Err(LemmyErrorType::CannotCreatePostOrCommentInDeletedOrRemovedCommunity)? Err(FederationError::CannotCreatePostOrCommentInDeletedOrRemovedCommunity)?
} else { } else {
Ok(()) Ok(())
} }
@ -367,6 +391,12 @@ pub async fn match_outgoing_activities(
community, community,
reason, reason,
} => Report::send(ObjectId::from(object_id), actor, community, reason, context).await, } => Report::send(ObjectId::from(object_id), actor, community, reason, context).await,
AcceptFollower(community_id, person_id) => {
send_accept_or_reject_follow(community_id, person_id, true, &context).await
}
RejectFollower(community_id, person_id) => {
send_accept_or_reject_follow(community_id, person_id, false, &context).await
}
} }
}; };
fed_task.await?; fed_task.await?;

View file

@ -62,7 +62,6 @@ async fn vote_comment(
let comment_id = comment.id; let comment_id = comment.id;
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id, comment_id,
post_id: comment.post_id,
person_id: actor.id, person_id: actor.id,
score: vote_type.into(), score: vote_type.into(),
}; };

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