mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-22 12:21:18 +00:00
Merge branch 'main' into fetch-mods-sync
This commit is contained in:
commit
f48edc3ba0
200 changed files with 4833 additions and 2079 deletions
|
@ -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
|
||||||
|
|
34
Cargo.lock
generated
34
Cargo.lock
generated
|
@ -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"
|
||||||
|
@ -2619,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",
|
||||||
|
@ -2640,6 +2644,7 @@ dependencies = [
|
||||||
"tokio-postgres-rustls",
|
"tokio-postgres-rustls",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
|
"tuplex",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -5255,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"
|
||||||
|
|
|
@ -24,10 +24,10 @@ 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 = "fat"
|
lto = "fat"
|
||||||
strip = true # Automatically strip symbols from the binary.
|
|
||||||
opt-level = 3 # Optimize for speed, not size.
|
opt-level = 3 # Optimize for speed, not size.
|
||||||
codegen-units = 1 # Reduce parallel code generation.
|
codegen-units = 1 # Reduce parallel code generation.
|
||||||
|
|
||||||
|
@ -78,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" }
|
||||||
|
@ -142,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"
|
||||||
|
@ -158,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 }
|
||||||
|
|
|
@ -10,12 +10,13 @@
|
||||||
"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",
|
||||||
|
|
|
@ -30,8 +30,8 @@ importers:
|
||||||
specifier: ^29.5.0
|
specifier: ^29.5.0
|
||||||
version: 29.7.0(@types/node@22.8.6)
|
version: 29.7.0(@types/node@22.8.6)
|
||||||
lemmy-js-client:
|
lemmy-js-client:
|
||||||
specifier: 0.20.0-alpha.11
|
specifier: 0.20.0-private-community.9
|
||||||
version: 0.20.0-alpha.11
|
version: 0.20.0-private-community.9
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
|
@ -1159,8 +1159,8 @@ packages:
|
||||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
lemmy-js-client@0.20.0-alpha.11:
|
lemmy-js-client@0.20.0-private-community.9:
|
||||||
resolution: {integrity: sha512-iRSG4xHMjPDIreQqVIoJ5JrMY71uk07G0Zbgyf068xKbib22J3+i1x/XgCTs6tiHlqTnw1Ig/KRq7p7qJoA4uw==}
|
resolution: {integrity: sha512-iuFezswCzIco5U5Q4Eo8HAWVE65pDW2zeO+fYLEyFl30SLw9a3gqJkip2vbDfVvoAjDXyUskZKddf1Nnj8mVcg==}
|
||||||
|
|
||||||
leven@3.1.0:
|
leven@3.1.0:
|
||||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||||
|
@ -3061,7 +3061,7 @@ snapshots:
|
||||||
|
|
||||||
kleur@3.0.3: {}
|
kleur@3.0.3: {}
|
||||||
|
|
||||||
lemmy-js-client@0.20.0-alpha.11: {}
|
lemmy-js-client@0.20.0-private-community.9: {}
|
||||||
|
|
||||||
leven@3.1.0: {}
|
leven@3.1.0: {}
|
||||||
|
|
||||||
|
|
214
api_tests/src/private_community.spec.ts
Normal file
214
api_tests/src/private_community.spec.ts
Normal 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",
|
||||||
|
);
|
||||||
|
});
|
|
@ -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://google.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) {
|
||||||
|
try {
|
||||||
result = await fetcher();
|
result = await fetcher();
|
||||||
if (checker(result)) return result;
|
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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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?;
|
||||||
|
@ -92,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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
} else {
|
||||||
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
|
// 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 {
|
||||||
|
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;
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
46
crates/api/src/community/pending_follows/approve.rs
Normal file
46
crates/api/src/community/pending_follows/approve.rs
Normal 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()))
|
||||||
|
}
|
25
crates/api/src/community/pending_follows/count.rs
Normal file
25
crates/api/src/community/pending_follows/count.rs
Normal 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 }))
|
||||||
|
}
|
29
crates/api/src/community/pending_follows/list.rs
Normal file
29
crates/api/src/community/pending_follows/list.rs
Normal 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 }))
|
||||||
|
}
|
3
crates/api/src/community/pending_follows/mod.rs
Normal file
3
crates/api/src/community/pending_follows/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod approve;
|
||||||
|
pub mod count;
|
||||||
|
pub mod list;
|
|
@ -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())
|
||||||
|
|
|
@ -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?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,8 +75,7 @@ pub async fn purge_community(
|
||||||
removed: true,
|
removed: true,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,8 +80,7 @@ pub async fn purge_person(
|
||||||
expires: None,
|
expires: None,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,7 @@ pub async fn purge_post(
|
||||||
removed: true,
|
removed: true,
|
||||||
},
|
},
|
||||||
&context,
|
&context,
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
Ok(Json(SuccessResponse::default()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,7 @@ 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(|(url, date_time)| {
|
.map_while(|(url, date_time)| {
|
||||||
|
@ -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");
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
|
@ -49,18 +54,26 @@ pub struct CreateCommunity {
|
||||||
/// A longer title.
|
/// A longer title.
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// A sidebar for the community in markdown.
|
/// A sidebar for the community in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub sidebar: Option<String>,
|
pub sidebar: Option<String>,
|
||||||
/// A shorter, one line description of your community.
|
/// 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,20 +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 sidebar for the community in markdown.
|
/// A sidebar for the community in markdown.
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub sidebar: Option<String>,
|
pub sidebar: Option<String>,
|
||||||
/// A shorter, one line description of your community.
|
/// 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,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,
|
||||||
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,23 +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
|
/// If true, then only show posts with no comments
|
||||||
pub no_comments_only: Option<bool>,
|
pub no_comments_only: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub page_cursor: Option<PaginationCursor>,
|
pub page_cursor: Option<PaginationCursor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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,94 +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.
|
/// 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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,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>,
|
||||||
|
@ -338,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>,
|
||||||
}
|
}
|
||||||
|
@ -352,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -165,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?;
|
||||||
|
@ -216,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)?
|
||||||
}
|
}
|
||||||
|
@ -230,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)?
|
||||||
}
|
}
|
||||||
|
@ -256,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(())
|
||||||
}
|
}
|
||||||
|
@ -355,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,
|
||||||
|
|
|
@ -61,7 +61,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
|
||||||
|
@ -143,8 +148,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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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?;
|
||||||
|
@ -98,8 +98,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(
|
||||||
|
|
|
@ -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::{
|
||||||
|
@ -23,6 +24,7 @@ use lemmy_db_schema::{
|
||||||
Community,
|
Community,
|
||||||
CommunityFollower,
|
CommunityFollower,
|
||||||
CommunityFollowerForm,
|
CommunityFollowerForm,
|
||||||
|
CommunityFollowerState,
|
||||||
CommunityInsertForm,
|
CommunityInsertForm,
|
||||||
CommunityModerator,
|
CommunityModerator,
|
||||||
CommunityModeratorForm,
|
CommunityModeratorForm,
|
||||||
|
@ -82,6 +84,12 @@ pub async fn create_community(
|
||||||
|
|
||||||
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
||||||
|
|
||||||
|
if let Some(desc) = &data.description {
|
||||||
|
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,
|
||||||
|
@ -135,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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::{
|
||||||
|
@ -51,6 +52,7 @@ pub async fn update_community(
|
||||||
is_valid_body_field(sidebar, 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 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?;
|
||||||
|
@ -66,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(),
|
||||||
)
|
)
|
||||||
|
@ -105,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,15 +85,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(
|
||||||
|
@ -110,7 +104,7 @@ pub async fn create_post(
|
||||||
None => {
|
None => {
|
||||||
default_post_language(
|
default_post_language(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
community_id,
|
community.id,
|
||||||
local_user_view.local_user.id,
|
local_user_view.local_user.id,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
@ -119,7 +113,7 @@ pub async fn create_post(
|
||||||
|
|
||||||
// Only need to check if language is allowed in case user set it explicitly. When using default
|
// Only need to check if language is allowed in case user set it explicitly. When using default
|
||||||
// language, it already only returns allowed languages.
|
// language, it already only returns allowed languages.
|
||||||
CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community_id)
|
CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community.id)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let scheduled_publish_time =
|
let scheduled_publish_time =
|
||||||
|
@ -142,6 +136,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))
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,17 +87,17 @@ 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)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,14 +105,14 @@ pub async fn update_post(
|
||||||
CommunityLanguage::is_allowed_community_language(
|
CommunityLanguage::is_allowed_community_language(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
language_id,
|
language_id,
|
||||||
orig_post.community_id,
|
orig_post.community.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)
|
||||||
|
@ -144,12 +144,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(),
|
||||||
|
@ -175,7 +175,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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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},
|
||||||
};
|
};
|
||||||
|
@ -52,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(),
|
||||||
|
@ -125,9 +123,9 @@ 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()
|
||||||
|
@ -143,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?;
|
||||||
}
|
}
|
||||||
|
@ -194,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();
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,6 +169,7 @@ impl ActivityHandler for AnnounceActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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())?;
|
||||||
|
|
|
@ -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())?;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) => {
|
||||||
|
let state = Some(match c.visibility {
|
||||||
|
CommunityVisibility::Public => CommunityFollowerState::Accepted,
|
||||||
|
CommunityVisibility::Private => CommunityFollowerState::ApprovalRequired,
|
||||||
// Dont allow following local-only community via federation.
|
// Dont allow following local-only community via federation.
|
||||||
if c.visibility != CommunityVisibility::Public {
|
CommunityVisibility::LocalOnly => return Err(LemmyErrorType::NotFound.into()),
|
||||||
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?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
AcceptFollow::send(self, context).await
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
79
crates/apub/src/activities/following/reject.rs
Normal file
79
crates/apub/src/activities/following/reject.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,6 +41,7 @@ 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::{FederationError, LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
@ -120,6 +122,28 @@ pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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>>,
|
||||||
|
@ -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?;
|
||||||
|
|
|
@ -17,7 +17,12 @@ use crate::{
|
||||||
page::CreateOrUpdatePage,
|
page::CreateOrUpdatePage,
|
||||||
},
|
},
|
||||||
deletion::{delete::Delete, undo_delete::UndoDelete},
|
deletion::{delete::Delete, undo_delete::UndoDelete},
|
||||||
following::{accept::AcceptFollow, follow::Follow, undo_follow::UndoFollow},
|
following::{
|
||||||
|
accept::AcceptFollow,
|
||||||
|
follow::Follow,
|
||||||
|
reject::RejectFollow,
|
||||||
|
undo_follow::UndoFollow,
|
||||||
|
},
|
||||||
voting::{undo_vote::UndoVote, vote::Vote},
|
voting::{undo_vote::UndoVote, vote::Vote},
|
||||||
},
|
},
|
||||||
objects::page::Page,
|
objects::page::Page,
|
||||||
|
@ -41,6 +46,7 @@ use url::Url;
|
||||||
pub enum SharedInboxActivities {
|
pub enum SharedInboxActivities {
|
||||||
Follow(Follow),
|
Follow(Follow),
|
||||||
AcceptFollow(AcceptFollow),
|
AcceptFollow(AcceptFollow),
|
||||||
|
RejectFollow(RejectFollow),
|
||||||
UndoFollow(UndoFollow),
|
UndoFollow(UndoFollow),
|
||||||
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
|
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
|
||||||
Report(Report),
|
Report(Report),
|
||||||
|
@ -68,6 +74,7 @@ pub enum GroupInboxActivities {
|
||||||
pub enum PersonInboxActivities {
|
pub enum PersonInboxActivities {
|
||||||
Follow(Follow),
|
Follow(Follow),
|
||||||
AcceptFollow(AcceptFollow),
|
AcceptFollow(AcceptFollow),
|
||||||
|
RejectFollow(RejectFollow),
|
||||||
UndoFollow(UndoFollow),
|
UndoFollow(UndoFollow),
|
||||||
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
|
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
|
||||||
Delete(Delete),
|
Delete(Delete),
|
||||||
|
|
|
@ -13,7 +13,7 @@ use lemmy_db_schema::{
|
||||||
newtypes::DbUrl,
|
newtypes::DbUrl,
|
||||||
source::{
|
source::{
|
||||||
comment::{CommentSaved, CommentSavedForm},
|
comment::{CommentSaved, CommentSavedForm},
|
||||||
community::{CommunityFollower, CommunityFollowerForm},
|
community::{CommunityFollower, CommunityFollowerForm, CommunityFollowerState},
|
||||||
community_block::{CommunityBlock, CommunityBlockForm},
|
community_block::{CommunityBlock, CommunityBlockForm},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
instance_block::{InstanceBlock, InstanceBlockForm},
|
instance_block::{InstanceBlock, InstanceBlockForm},
|
||||||
|
@ -186,9 +186,8 @@ pub async fn import_settings(
|
||||||
|(followed, context)| async move {
|
|(followed, context)| async move {
|
||||||
let community = followed.dereference(&context).await?;
|
let community = followed.dereference(&context).await?;
|
||||||
let form = CommunityFollowerForm {
|
let form = CommunityFollowerForm {
|
||||||
person_id,
|
state: Some(CommunityFollowerState::Pending),
|
||||||
community_id: community.id,
|
..CommunityFollowerForm::new(community.id, person_id)
|
||||||
pending: true,
|
|
||||||
};
|
};
|
||||||
CommunityFollower::follow(&mut context.pool(), &form).await?;
|
CommunityFollower::follow(&mut context.pool(), &form).await?;
|
||||||
LemmyResult::Ok(())
|
LemmyResult::Ok(())
|
||||||
|
@ -319,7 +318,13 @@ pub(crate) mod tests {
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm},
|
community::{
|
||||||
|
Community,
|
||||||
|
CommunityFollower,
|
||||||
|
CommunityFollowerForm,
|
||||||
|
CommunityFollowerState,
|
||||||
|
CommunityInsertForm,
|
||||||
|
},
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
},
|
},
|
||||||
traits::{Crud, Followable},
|
traits::{Crud, Followable},
|
||||||
|
@ -327,7 +332,6 @@ pub(crate) mod tests {
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
@ -348,9 +352,8 @@ pub(crate) mod tests {
|
||||||
);
|
);
|
||||||
let community = Community::create(pool, &community_form).await?;
|
let community = Community::create(pool, &community_form).await?;
|
||||||
let follower_form = CommunityFollowerForm {
|
let follower_form = CommunityFollowerForm {
|
||||||
community_id: community.id,
|
state: Some(CommunityFollowerState::Accepted),
|
||||||
person_id: export_user.person.id,
|
..CommunityFollowerForm::new(community.id, export_user.person.id)
|
||||||
pending: false,
|
|
||||||
};
|
};
|
||||||
CommunityFollower::follow(pool, &follower_form).await?;
|
CommunityFollower::follow(pool, &follower_form).await?;
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ use activitypub_federation::{
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
|
use lemmy_db_schema::newtypes::InstanceId;
|
||||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -127,3 +128,13 @@ impl Actor for SiteOrCommunityOrUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SiteOrCommunityOrUser {
|
||||||
|
pub fn instance_id(&self) -> InstanceId {
|
||||||
|
match self {
|
||||||
|
SiteOrCommunityOrUser::Site(s) => s.instance_id,
|
||||||
|
SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::User(u)) => u.instance_id,
|
||||||
|
SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::Community(c)) => c.instance_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
|
use super::check_community_content_fetchable;
|
||||||
use crate::{
|
use crate::{
|
||||||
http::{
|
http::{create_apub_response, create_apub_tombstone_response, redirect_remote_object},
|
||||||
check_community_public,
|
|
||||||
create_apub_response,
|
|
||||||
create_apub_tombstone_response,
|
|
||||||
redirect_remote_object,
|
|
||||||
},
|
|
||||||
objects::comment::ApubComment,
|
objects::comment::ApubComment,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{config::Data, traits::Object};
|
use activitypub_federation::{config::Data, traits::Object};
|
||||||
use actix_web::{web::Path, HttpResponse};
|
use actix_web::{web::Path, HttpRequest, HttpResponse};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::CommentId,
|
newtypes::CommentId,
|
||||||
|
@ -28,13 +24,14 @@ pub(crate) struct CommentQuery {
|
||||||
pub(crate) async fn get_apub_comment(
|
pub(crate) async fn get_apub_comment(
|
||||||
info: Path<CommentQuery>,
|
info: Path<CommentQuery>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
request: HttpRequest,
|
||||||
) -> LemmyResult<HttpResponse> {
|
) -> LemmyResult<HttpResponse> {
|
||||||
let id = CommentId(info.comment_id.parse::<i32>()?);
|
let id = CommentId(info.comment_id.parse::<i32>()?);
|
||||||
// Can't use CommentView here because it excludes deleted/removed/local-only items
|
// Can't use CommentView here because it excludes deleted/removed/local-only items
|
||||||
let comment: ApubComment = Comment::read(&mut context.pool(), id).await?.into();
|
let comment: ApubComment = Comment::read(&mut context.pool(), id).await?.into();
|
||||||
let post = Post::read(&mut context.pool(), comment.post_id).await?;
|
let post = Post::read(&mut context.pool(), comment.post_id).await?;
|
||||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||||
check_community_public(&community)?;
|
check_community_content_fetchable(&community, &request, &context).await?;
|
||||||
|
|
||||||
if !comment.local {
|
if !comment.local {
|
||||||
Ok(redirect_remote_object(&comment.ap_id))
|
Ok(redirect_remote_object(&comment.ap_id))
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::check_community_content_fetchable;
|
||||||
use crate::{
|
use crate::{
|
||||||
collections::{
|
collections::{
|
||||||
community_featured::ApubCommunityFeatured,
|
community_featured::ApubCommunityFeatured,
|
||||||
|
@ -5,14 +6,14 @@ use crate::{
|
||||||
community_moderators::ApubCommunityModerators,
|
community_moderators::ApubCommunityModerators,
|
||||||
community_outbox::ApubCommunityOutbox,
|
community_outbox::ApubCommunityOutbox,
|
||||||
},
|
},
|
||||||
http::{check_community_public, create_apub_response, create_apub_tombstone_response},
|
http::{check_community_fetchable, create_apub_response, create_apub_tombstone_response},
|
||||||
objects::community::ApubCommunity,
|
objects::community::ApubCommunity,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
traits::{Collection, Object},
|
traits::{Collection, Object},
|
||||||
};
|
};
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{source::community::Community, traits::ApubActor};
|
use lemmy_db_schema::{source::community::Community, traits::ApubActor};
|
||||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
@ -38,7 +39,7 @@ pub(crate) async fn get_apub_community_http(
|
||||||
if community.deleted || community.removed {
|
if community.deleted || community.removed {
|
||||||
return create_apub_tombstone_response(community.actor_id.clone());
|
return create_apub_tombstone_response(community.actor_id.clone());
|
||||||
}
|
}
|
||||||
check_community_public(&community)?;
|
check_community_fetchable(&community)?;
|
||||||
|
|
||||||
let apub = community.into_json(&context).await?;
|
let apub = community.into_json(&context).await?;
|
||||||
create_apub_response(&apub)
|
create_apub_response(&apub)
|
||||||
|
@ -52,7 +53,7 @@ pub(crate) async fn get_apub_community_followers(
|
||||||
let community = Community::read_from_name(&mut context.pool(), &info.community_name, false)
|
let community = Community::read_from_name(&mut context.pool(), &info.community_name, false)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(LemmyErrorType::NotFound)?;
|
.ok_or(LemmyErrorType::NotFound)?;
|
||||||
check_community_public(&community)?;
|
check_community_fetchable(&community)?;
|
||||||
let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?;
|
let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?;
|
||||||
create_apub_response(&followers)
|
create_apub_response(&followers)
|
||||||
}
|
}
|
||||||
|
@ -62,13 +63,14 @@ pub(crate) async fn get_apub_community_followers(
|
||||||
pub(crate) async fn get_apub_community_outbox(
|
pub(crate) async fn get_apub_community_outbox(
|
||||||
info: web::Path<CommunityQuery>,
|
info: web::Path<CommunityQuery>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
request: HttpRequest,
|
||||||
) -> LemmyResult<HttpResponse> {
|
) -> LemmyResult<HttpResponse> {
|
||||||
let community: ApubCommunity =
|
let community: ApubCommunity =
|
||||||
Community::read_from_name(&mut context.pool(), &info.community_name, false)
|
Community::read_from_name(&mut context.pool(), &info.community_name, false)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(LemmyErrorType::NotFound)?
|
.ok_or(LemmyErrorType::NotFound)?
|
||||||
.into();
|
.into();
|
||||||
check_community_public(&community)?;
|
check_community_content_fetchable(&community, &request, &context).await?;
|
||||||
let outbox = ApubCommunityOutbox::read_local(&community, &context).await?;
|
let outbox = ApubCommunityOutbox::read_local(&community, &context).await?;
|
||||||
create_apub_response(&outbox)
|
create_apub_response(&outbox)
|
||||||
}
|
}
|
||||||
|
@ -83,7 +85,7 @@ pub(crate) async fn get_apub_community_moderators(
|
||||||
.await?
|
.await?
|
||||||
.ok_or(LemmyErrorType::NotFound)?
|
.ok_or(LemmyErrorType::NotFound)?
|
||||||
.into();
|
.into();
|
||||||
check_community_public(&community)?;
|
check_community_fetchable(&community)?;
|
||||||
let moderators = ApubCommunityModerators::read_local(&community, &context).await?;
|
let moderators = ApubCommunityModerators::read_local(&community, &context).await?;
|
||||||
create_apub_response(&moderators)
|
create_apub_response(&moderators)
|
||||||
}
|
}
|
||||||
|
@ -92,13 +94,14 @@ pub(crate) async fn get_apub_community_moderators(
|
||||||
pub(crate) async fn get_apub_community_featured(
|
pub(crate) async fn get_apub_community_featured(
|
||||||
info: web::Path<CommunityQuery>,
|
info: web::Path<CommunityQuery>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
request: HttpRequest,
|
||||||
) -> LemmyResult<HttpResponse> {
|
) -> LemmyResult<HttpResponse> {
|
||||||
let community: ApubCommunity =
|
let community: ApubCommunity =
|
||||||
Community::read_from_name(&mut context.pool(), &info.community_name, false)
|
Community::read_from_name(&mut context.pool(), &info.community_name, false)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(LemmyErrorType::NotFound)?
|
.ok_or(LemmyErrorType::NotFound)?
|
||||||
.into();
|
.into();
|
||||||
check_community_public(&community)?;
|
check_community_content_fetchable(&community, &request, &context).await?;
|
||||||
let featured = ApubCommunityFeatured::read_local(&community, &context).await?;
|
let featured = ApubCommunityFeatured::read_local(&community, &context).await?;
|
||||||
create_apub_response(&featured)
|
create_apub_response(&featured)
|
||||||
}
|
}
|
||||||
|
@ -108,7 +111,7 @@ pub(crate) mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::protocol::objects::{group::Group, tombstone::Tombstone};
|
use crate::protocol::objects::{group::Group, tombstone::Tombstone};
|
||||||
use actix_web::body::to_bytes;
|
use actix_web::{body::to_bytes, test::TestRequest};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::InstanceId,
|
newtypes::InstanceId,
|
||||||
source::{
|
source::{
|
||||||
|
@ -175,6 +178,7 @@ pub(crate) mod tests {
|
||||||
async fn test_get_community() -> LemmyResult<()> {
|
async fn test_get_community() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
let (instance, community) = init(false, CommunityVisibility::Public, &context).await?;
|
let (instance, community) = init(false, CommunityVisibility::Public, &context).await?;
|
||||||
|
let request = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
// fetch invalid community
|
// fetch invalid community
|
||||||
let query = CommunityQuery {
|
let query = CommunityQuery {
|
||||||
|
@ -194,8 +198,12 @@ pub(crate) mod tests {
|
||||||
let group = community.clone().into_json(&context).await?;
|
let group = community.clone().into_json(&context).await?;
|
||||||
assert_eq!(group, res_group);
|
assert_eq!(group, res_group);
|
||||||
|
|
||||||
let res =
|
let res = get_apub_community_featured(
|
||||||
get_apub_community_featured(query.clone().into(), context.reset_request_count()).await?;
|
query.clone().into(),
|
||||||
|
context.reset_request_count(),
|
||||||
|
request.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
assert_eq!(200, res.status());
|
assert_eq!(200, res.status());
|
||||||
let res =
|
let res =
|
||||||
get_apub_community_followers(query.clone().into(), context.reset_request_count()).await?;
|
get_apub_community_followers(query.clone().into(), context.reset_request_count()).await?;
|
||||||
|
@ -203,7 +211,8 @@ pub(crate) mod tests {
|
||||||
let res =
|
let res =
|
||||||
get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await?;
|
get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await?;
|
||||||
assert_eq!(200, res.status());
|
assert_eq!(200, res.status());
|
||||||
let res = get_apub_community_outbox(query.into(), context.reset_request_count()).await?;
|
let res =
|
||||||
|
get_apub_community_outbox(query.into(), context.reset_request_count(), request).await?;
|
||||||
assert_eq!(200, res.status());
|
assert_eq!(200, res.status());
|
||||||
|
|
||||||
Instance::delete(&mut context.pool(), instance.id).await?;
|
Instance::delete(&mut context.pool(), instance.id).await?;
|
||||||
|
@ -215,6 +224,7 @@ pub(crate) mod tests {
|
||||||
async fn test_get_deleted_community() -> LemmyResult<()> {
|
async fn test_get_deleted_community() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
let (instance, community) = init(true, CommunityVisibility::LocalOnly, &context).await?;
|
let (instance, community) = init(true, CommunityVisibility::LocalOnly, &context).await?;
|
||||||
|
let request = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
// should return tombstone
|
// should return tombstone
|
||||||
let query = CommunityQuery {
|
let query = CommunityQuery {
|
||||||
|
@ -225,8 +235,12 @@ pub(crate) mod tests {
|
||||||
let res_tombstone = decode_response::<Tombstone>(res).await;
|
let res_tombstone = decode_response::<Tombstone>(res).await;
|
||||||
assert!(res_tombstone.is_ok());
|
assert!(res_tombstone.is_ok());
|
||||||
|
|
||||||
let res =
|
let res = get_apub_community_featured(
|
||||||
get_apub_community_featured(query.clone().into(), context.reset_request_count()).await;
|
query.clone().into(),
|
||||||
|
context.reset_request_count(),
|
||||||
|
request.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
let res =
|
let res =
|
||||||
get_apub_community_followers(query.clone().into(), context.reset_request_count()).await;
|
get_apub_community_followers(query.clone().into(), context.reset_request_count()).await;
|
||||||
|
@ -234,7 +248,7 @@ pub(crate) mod tests {
|
||||||
let res =
|
let res =
|
||||||
get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await;
|
get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
let res = get_apub_community_outbox(query.into(), context.reset_request_count()).await;
|
let res = get_apub_community_outbox(query.into(), context.reset_request_count(), request).await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
//Community::delete(&mut context.pool(), community.id).await?;
|
//Community::delete(&mut context.pool(), community.id).await?;
|
||||||
|
@ -247,14 +261,19 @@ pub(crate) mod tests {
|
||||||
async fn test_get_local_only_community() -> LemmyResult<()> {
|
async fn test_get_local_only_community() -> LemmyResult<()> {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
let (instance, community) = init(false, CommunityVisibility::LocalOnly, &context).await?;
|
let (instance, community) = init(false, CommunityVisibility::LocalOnly, &context).await?;
|
||||||
|
let request = TestRequest::default().to_http_request();
|
||||||
|
|
||||||
let query = CommunityQuery {
|
let query = CommunityQuery {
|
||||||
community_name: community.name.clone(),
|
community_name: community.name.clone(),
|
||||||
};
|
};
|
||||||
let res = get_apub_community_http(query.clone().into(), context.reset_request_count()).await;
|
let res = get_apub_community_http(query.clone().into(), context.reset_request_count()).await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
let res =
|
let res = get_apub_community_featured(
|
||||||
get_apub_community_featured(query.clone().into(), context.reset_request_count()).await;
|
query.clone().into(),
|
||||||
|
context.reset_request_count(),
|
||||||
|
request.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
let res =
|
let res =
|
||||||
get_apub_community_followers(query.clone().into(), context.reset_request_count()).await;
|
get_apub_community_followers(query.clone().into(), context.reset_request_count()).await;
|
||||||
|
@ -262,7 +281,7 @@ pub(crate) mod tests {
|
||||||
let res =
|
let res =
|
||||||
get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await;
|
get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
let res = get_apub_community_outbox(query.into(), context.reset_request_count()).await;
|
let res = get_apub_community_outbox(query.into(), context.reset_request_count(), request).await;
|
||||||
assert!(res.is_err());
|
assert!(res.is_err());
|
||||||
|
|
||||||
Instance::delete(&mut context.pool(), instance.id).await?;
|
Instance::delete(&mut context.pool(), instance.id).await?;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activity_lists::SharedInboxActivities,
|
activity_lists::SharedInboxActivities,
|
||||||
fetcher::user_or_community::UserOrCommunity,
|
fetcher::{site_or_community_or_user::SiteOrCommunityOrUser, user_or_community::UserOrCommunity},
|
||||||
protocol::objects::tombstone::Tombstone,
|
protocol::objects::tombstone::Tombstone,
|
||||||
FEDERATION_CONTEXT,
|
FEDERATION_CONTEXT,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
actix_web::inbox::receive_activity,
|
actix_web::{inbox::receive_activity, signing_actor},
|
||||||
config::Data,
|
config::Data,
|
||||||
protocol::context::WithContext,
|
protocol::context::WithContext,
|
||||||
FEDERATION_CONTENT_TYPE,
|
FEDERATION_CONTENT_TYPE,
|
||||||
|
@ -17,6 +17,7 @@ use lemmy_db_schema::{
|
||||||
source::{activity::SentActivity, community::Community},
|
source::{activity::SentActivity, community::Community},
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
};
|
};
|
||||||
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
use lemmy_utils::error::{FederationError, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{FederationError, LemmyErrorType, LemmyResult};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{ops::Deref, time::Duration};
|
use std::{ops::Deref, time::Duration};
|
||||||
|
@ -119,12 +120,46 @@ pub(crate) async fn get_activity(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that the community is public and not removed/deleted.
|
/// Ensure that the community is public and not removed/deleted.
|
||||||
fn check_community_public(community: &Community) -> LemmyResult<()> {
|
fn check_community_fetchable(community: &Community) -> LemmyResult<()> {
|
||||||
if community.deleted || community.removed {
|
check_community_removed_or_deleted(community)?;
|
||||||
Err(LemmyErrorType::Deleted)?
|
if community.visibility == CommunityVisibility::LocalOnly {
|
||||||
}
|
|
||||||
if community.visibility != CommunityVisibility::Public {
|
|
||||||
return Err(LemmyErrorType::NotFound.into());
|
return Err(LemmyErrorType::NotFound.into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if posts or comments in the community are allowed to be fetched
|
||||||
|
async fn check_community_content_fetchable(
|
||||||
|
community: &Community,
|
||||||
|
request: &HttpRequest,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
use CommunityVisibility::*;
|
||||||
|
check_community_removed_or_deleted(community)?;
|
||||||
|
match community.visibility {
|
||||||
|
// content in public community can always be fetched
|
||||||
|
Public => Ok(()),
|
||||||
|
// no federation for local only community
|
||||||
|
LocalOnly => Err(LemmyErrorType::NotFound.into()),
|
||||||
|
// for private community check http signature of request, if there is any approved follower
|
||||||
|
// from the fetching instance then fetching is allowed
|
||||||
|
Private => {
|
||||||
|
let signing_actor = signing_actor::<SiteOrCommunityOrUser>(request, None, context).await?;
|
||||||
|
Ok(
|
||||||
|
CommunityFollowerView::check_has_followers_from_instance(
|
||||||
|
community.id,
|
||||||
|
signing_actor.instance_id(),
|
||||||
|
&mut context.pool(),
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_community_removed_or_deleted(community: &Community) -> LemmyResult<()> {
|
||||||
|
if community.deleted || community.removed {
|
||||||
|
Err(LemmyErrorType::Deleted)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
|
use super::check_community_content_fetchable;
|
||||||
use crate::{
|
use crate::{
|
||||||
http::{
|
http::{create_apub_response, create_apub_tombstone_response, redirect_remote_object},
|
||||||
check_community_public,
|
|
||||||
create_apub_response,
|
|
||||||
create_apub_tombstone_response,
|
|
||||||
redirect_remote_object,
|
|
||||||
},
|
|
||||||
objects::post::ApubPost,
|
objects::post::ApubPost,
|
||||||
};
|
};
|
||||||
use activitypub_federation::{config::Data, traits::Object};
|
use activitypub_federation::{config::Data, traits::Object};
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PostId,
|
newtypes::PostId,
|
||||||
|
@ -28,12 +24,14 @@ pub(crate) struct PostQuery {
|
||||||
pub(crate) async fn get_apub_post(
|
pub(crate) async fn get_apub_post(
|
||||||
info: web::Path<PostQuery>,
|
info: web::Path<PostQuery>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
|
request: HttpRequest,
|
||||||
) -> LemmyResult<HttpResponse> {
|
) -> LemmyResult<HttpResponse> {
|
||||||
let id = PostId(info.post_id.parse::<i32>()?);
|
let id = PostId(info.post_id.parse::<i32>()?);
|
||||||
// Can't use PostView here because it excludes deleted/removed/local-only items
|
// Can't use PostView here because it excludes deleted/removed/local-only items
|
||||||
let post: ApubPost = Post::read(&mut context.pool(), id).await?.into();
|
let post: ApubPost = Post::read(&mut context.pool(), id).await?.into();
|
||||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||||
check_community_public(&community)?;
|
|
||||||
|
check_community_content_fetchable(&community, &request, &context).await?;
|
||||||
|
|
||||||
if !post.local {
|
if !post.local {
|
||||||
Ok(redirect_remote_object(&post.ap_id))
|
Ok(redirect_remote_object(&post.ap_id))
|
||||||
|
|
|
@ -54,15 +54,24 @@ impl UrlVerifier for VerifyUrlData {
|
||||||
use FederationError::*;
|
use FederationError::*;
|
||||||
check_apub_id_valid(url, &local_site_data).map_err(|err| match err {
|
check_apub_id_valid(url, &local_site_data).map_err(|err| match err {
|
||||||
LemmyError {
|
LemmyError {
|
||||||
error_type: LemmyErrorType::FederationError(Some(FederationDisabled)),
|
error_type:
|
||||||
|
LemmyErrorType::FederationError {
|
||||||
|
error: Some(FederationDisabled),
|
||||||
|
},
|
||||||
..
|
..
|
||||||
} => ActivityPubError::Other("Federation disabled".into()),
|
} => ActivityPubError::Other("Federation disabled".into()),
|
||||||
LemmyError {
|
LemmyError {
|
||||||
error_type: LemmyErrorType::FederationError(Some(DomainBlocked(domain))),
|
error_type:
|
||||||
|
LemmyErrorType::FederationError {
|
||||||
|
error: Some(DomainBlocked(domain)),
|
||||||
|
},
|
||||||
..
|
..
|
||||||
} => ActivityPubError::Other(format!("Domain {domain:?} is blocked")),
|
} => ActivityPubError::Other(format!("Domain {domain:?} is blocked")),
|
||||||
LemmyError {
|
LemmyError {
|
||||||
error_type: LemmyErrorType::FederationError(Some(DomainNotInAllowList(domain))),
|
error_type:
|
||||||
|
LemmyErrorType::FederationError {
|
||||||
|
error: Some(DomainNotInAllowList(domain)),
|
||||||
|
},
|
||||||
..
|
..
|
||||||
} => ActivityPubError::Other(format!("Domain {domain:?} is not in allowlist")),
|
} => ActivityPubError::Other(format!("Domain {domain:?} is not in allowlist")),
|
||||||
_ => ActivityPubError::Other("Failed validating apub id".into()),
|
_ => ActivityPubError::Other("Failed validating apub id".into()),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{verify_is_public, verify_person_in_community},
|
activities::{generate_to, verify_person_in_community, verify_visibility},
|
||||||
check_apub_id_valid_with_strictness,
|
check_apub_id_valid_with_strictness,
|
||||||
fetcher::markdown_links::markdown_rewrite_remote_links,
|
fetcher::markdown_links::markdown_rewrite_remote_links,
|
||||||
mentions::collect_non_local_mentions,
|
mentions::collect_non_local_mentions,
|
||||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
kinds::{object::NoteType, public},
|
kinds::object::NoteType,
|
||||||
protocol::{values::MediaTypeMarkdownOrHtml, verification::verify_domains_match},
|
protocol::{values::MediaTypeMarkdownOrHtml, verification::verify_domains_match},
|
||||||
traits::Object,
|
traits::Object,
|
||||||
};
|
};
|
||||||
|
@ -112,7 +112,7 @@ impl Object for ApubComment {
|
||||||
r#type: NoteType::Note,
|
r#type: NoteType::Note,
|
||||||
id: self.ap_id.clone().into(),
|
id: self.ap_id.clone().into(),
|
||||||
attributed_to: creator.actor_id.into(),
|
attributed_to: creator.actor_id.into(),
|
||||||
to: vec![public()],
|
to: vec![generate_to(&community)?],
|
||||||
cc: maa.ccs,
|
cc: maa.ccs,
|
||||||
content: markdown_to_html(&self.content),
|
content: markdown_to_html(&self.content),
|
||||||
media_type: Some(MediaTypeMarkdownOrHtml::Html),
|
media_type: Some(MediaTypeMarkdownOrHtml::Html),
|
||||||
|
@ -140,8 +140,8 @@ impl Object for ApubComment {
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
verify_domains_match(note.id.inner(), expected_domain)?;
|
verify_domains_match(note.id.inner(), expected_domain)?;
|
||||||
verify_domains_match(note.attributed_to.inner(), note.id.inner())?;
|
verify_domains_match(note.attributed_to.inner(), note.id.inner())?;
|
||||||
verify_is_public(¬e.to, ¬e.cc)?;
|
|
||||||
let community = Box::pin(note.community(context)).await?;
|
let community = Box::pin(note.community(context)).await?;
|
||||||
|
verify_visibility(¬e.to, ¬e.cc, &community)?;
|
||||||
|
|
||||||
Box::pin(check_apub_id_valid_with_strictness(
|
Box::pin(check_apub_id_valid_with_strictness(
|
||||||
note.id.inner(),
|
note.id.inner(),
|
||||||
|
|
|
@ -39,6 +39,7 @@ use lemmy_db_schema::{
|
||||||
},
|
},
|
||||||
traits::{ApubActor, Crud},
|
traits::{ApubActor, Crud},
|
||||||
utils::naive_now,
|
utils::naive_now,
|
||||||
|
CommunityVisibility,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
use lemmy_db_views_actor::structs::CommunityFollowerView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -126,6 +127,7 @@ impl Object for ApubCommunity {
|
||||||
updated: self.updated,
|
updated: self.updated,
|
||||||
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
posting_restricted_to_mods: Some(self.posting_restricted_to_mods),
|
||||||
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
|
attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()),
|
||||||
|
manually_approves_followers: Some(self.visibility == CommunityVisibility::Private),
|
||||||
};
|
};
|
||||||
Ok(group)
|
Ok(group)
|
||||||
}
|
}
|
||||||
|
@ -152,7 +154,11 @@ impl Object for ApubCommunity {
|
||||||
let sidebar = markdown_rewrite_remote_links_opt(sidebar, context).await;
|
let sidebar = markdown_rewrite_remote_links_opt(sidebar, context).await;
|
||||||
let icon = proxy_image_link_opt_apub(group.icon.map(|i| i.url), context).await?;
|
let icon = proxy_image_link_opt_apub(group.icon.map(|i| i.url), context).await?;
|
||||||
let banner = proxy_image_link_opt_apub(group.image.map(|i| i.url), context).await?;
|
let banner = proxy_image_link_opt_apub(group.image.map(|i| i.url), context).await?;
|
||||||
|
let visibility = Some(if group.manually_approves_followers.unwrap_or_default() {
|
||||||
|
CommunityVisibility::Private
|
||||||
|
} else {
|
||||||
|
CommunityVisibility::Public
|
||||||
|
});
|
||||||
let form = CommunityInsertForm {
|
let form = CommunityInsertForm {
|
||||||
published: group.published,
|
published: group.published,
|
||||||
updated: group.updated,
|
updated: group.updated,
|
||||||
|
@ -176,6 +182,7 @@ impl Object for ApubCommunity {
|
||||||
moderators_url: group.attributed_to.clone().map(Into::into),
|
moderators_url: group.attributed_to.clone().map(Into::into),
|
||||||
posting_restricted_to_mods: group.posting_restricted_to_mods,
|
posting_restricted_to_mods: group.posting_restricted_to_mods,
|
||||||
featured_url: group.featured.clone().map(Into::into),
|
featured_url: group.featured.clone().map(Into::into),
|
||||||
|
visibility,
|
||||||
..CommunityInsertForm::new(
|
..CommunityInsertForm::new(
|
||||||
instance_id,
|
instance_id,
|
||||||
group.preferred_username.clone(),
|
group.preferred_username.clone(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{verify_is_public, verify_person_in_community},
|
activities::{generate_to, verify_person_in_community, verify_visibility},
|
||||||
check_apub_id_valid_with_strictness,
|
check_apub_id_valid_with_strictness,
|
||||||
fetcher::markdown_links::{markdown_rewrite_remote_links_opt, to_local_url},
|
fetcher::markdown_links::{markdown_rewrite_remote_links_opt, to_local_url},
|
||||||
local_site_data_cached,
|
local_site_data_cached,
|
||||||
|
@ -16,7 +16,6 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
kinds::public,
|
|
||||||
protocol::{values::MediaTypeMarkdownOrHtml, verification::verify_domains_match},
|
protocol::{values::MediaTypeMarkdownOrHtml, verification::verify_domains_match},
|
||||||
traits::Object,
|
traits::Object,
|
||||||
};
|
};
|
||||||
|
@ -135,7 +134,7 @@ impl Object for ApubPost {
|
||||||
kind: PageType::Page,
|
kind: PageType::Page,
|
||||||
id: self.ap_id.clone().into(),
|
id: self.ap_id.clone().into(),
|
||||||
attributed_to: AttributedTo::Lemmy(creator.actor_id.into()),
|
attributed_to: AttributedTo::Lemmy(creator.actor_id.into()),
|
||||||
to: vec![community.actor_id.clone().into(), public()],
|
to: vec![generate_to(&community)?],
|
||||||
cc: vec![],
|
cc: vec![],
|
||||||
name: Some(self.name.clone()),
|
name: Some(self.name.clone()),
|
||||||
content: self.body.as_ref().map(|b| markdown_to_html(b)),
|
content: self.body.as_ref().map(|b| markdown_to_html(b)),
|
||||||
|
@ -172,7 +171,7 @@ impl Object for ApubPost {
|
||||||
check_slurs_opt(&page.name, slur_regex)?;
|
check_slurs_opt(&page.name, slur_regex)?;
|
||||||
|
|
||||||
verify_domains_match(page.creator()?.inner(), page.id.inner())?;
|
verify_domains_match(page.creator()?.inner(), page.id.inner())?;
|
||||||
verify_is_public(&page.to, &page.cc)?;
|
verify_visibility(&page.to, &page.cc, &community)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,12 @@ use activitypub_federation::{
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
utils::{get_url_blocklist, local_site_opt_to_slur_regex, process_markdown},
|
utils::{
|
||||||
|
check_private_messages_enabled,
|
||||||
|
get_url_blocklist,
|
||||||
|
local_site_opt_to_slur_regex,
|
||||||
|
process_markdown,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -28,6 +33,7 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::naive_now,
|
utils::naive_now,
|
||||||
};
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{FederationError, LemmyError, LemmyErrorType, LemmyResult},
|
error::{FederationError, LemmyError, LemmyErrorType, LemmyResult},
|
||||||
utils::markdown::markdown_to_html,
|
utils::markdown::markdown_to_html,
|
||||||
|
@ -130,9 +136,16 @@ impl Object for ApubPrivateMessage {
|
||||||
let recipient = note.to[0].dereference(context).await?;
|
let recipient = note.to[0].dereference(context).await?;
|
||||||
PersonBlock::read(&mut context.pool(), recipient.id, creator.id).await?;
|
PersonBlock::read(&mut context.pool(), recipient.id, creator.id).await?;
|
||||||
|
|
||||||
|
// Check that they can receive private messages
|
||||||
|
if let Ok(recipient_local_user) =
|
||||||
|
LocalUserView::read_person(&mut context.pool(), recipient.id).await
|
||||||
|
{
|
||||||
|
check_private_messages_enabled(&recipient_local_user)?;
|
||||||
|
}
|
||||||
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
||||||
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
||||||
let url_blocklist = get_url_blocklist(context).await?;
|
let url_blocklist = get_url_blocklist(context).await?;
|
||||||
|
|
||||||
let content = read_from_string_or_source(¬e.content, &None, ¬e.source);
|
let content = read_from_string_or_source(¬e.content, &None, ¬e.source);
|
||||||
let content = process_markdown(&content, slur_regex, &url_blocklist, context).await?;
|
let content = process_markdown(&content, slur_regex, &url_blocklist, context).await?;
|
||||||
let content = markdown_rewrite_remote_links(content, context).await;
|
let content = markdown_rewrite_remote_links(content, context).await;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub(crate) mod accept;
|
pub(crate) mod accept;
|
||||||
pub mod follow;
|
pub mod follow;
|
||||||
|
pub(crate) mod reject;
|
||||||
pub mod undo_follow;
|
pub mod undo_follow;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
24
crates/apub/src/protocol/activities/following/reject.rs
Normal file
24
crates/apub/src/protocol/activities/following/reject.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use crate::{
|
||||||
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
|
protocol::activities::following::follow::Follow,
|
||||||
|
};
|
||||||
|
use activitypub_federation::{
|
||||||
|
fetch::object_id::ObjectId,
|
||||||
|
kinds::activity::RejectType,
|
||||||
|
protocol::helpers::deserialize_skip_error,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RejectFollow {
|
||||||
|
pub(crate) actor: ObjectId<ApubCommunity>,
|
||||||
|
/// Optional, for compatibility with platforms that always expect recipient field
|
||||||
|
#[serde(deserialize_with = "deserialize_skip_error", default)]
|
||||||
|
pub(crate) to: Option<[ObjectId<ApubPerson>; 1]>,
|
||||||
|
pub(crate) object: Follow,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) kind: RejectType,
|
||||||
|
pub(crate) id: Url,
|
||||||
|
}
|
|
@ -73,6 +73,8 @@ pub struct Group {
|
||||||
pub(crate) featured: Option<CollectionId<ApubCommunityFeatured>>,
|
pub(crate) featured: Option<CollectionId<ApubCommunityFeatured>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) language: Vec<LanguageTag>,
|
pub(crate) language: Vec<LanguageTag>,
|
||||||
|
/// True if this is a private community
|
||||||
|
pub(crate) manually_approves_followers: Option<bool>,
|
||||||
pub(crate) published: Option<DateTime<Utc>>,
|
pub(crate) published: Option<DateTime<Utc>>,
|
||||||
pub(crate) updated: Option<DateTime<Utc>>,
|
pub(crate) updated: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue