mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 11:21:32 +00:00
Merge remote-tracking branch 'upstream/main' into migration-runner
This commit is contained in:
commit
f044ef3321
132 changed files with 3021 additions and 1690 deletions
|
@ -2,6 +2,10 @@
|
||||||
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
|
# When updating the rust version here, be sure to update versions in `docker/Dockerfile`
|
||||||
|
# as well. Otherwise release builds can fail if Lemmy or dependencies rely on new Rust
|
||||||
|
# features. In particular the ARM builder image needs to be updated manually in the repo below:
|
||||||
|
# https://github.com/raskyld/lemmy-cross-toolchains
|
||||||
- &rust_image "rust:1.81"
|
- &rust_image "rust:1.81"
|
||||||
- &rust_nightly_image "rustlang/rust:nightly"
|
- &rust_nightly_image "rustlang/rust:nightly"
|
||||||
- &install_pnpm "corepack enable pnpm"
|
- &install_pnpm "corepack enable pnpm"
|
||||||
|
@ -268,13 +272,15 @@ steps:
|
||||||
# using https://github.com/pksunkara/cargo-workspaces
|
# using https://github.com/pksunkara/cargo-workspaces
|
||||||
publish_to_crates_io:
|
publish_to_crates_io:
|
||||||
image: *rust_image
|
image: *rust_image
|
||||||
|
environment:
|
||||||
|
CARGO_API_TOKEN:
|
||||||
|
from_secret: cargo_api_token
|
||||||
commands:
|
commands:
|
||||||
- *install_binstall
|
- *install_binstall
|
||||||
# Install cargo-workspaces
|
# Install cargo-workspaces
|
||||||
- cargo binstall -y cargo-workspaces
|
- cargo binstall -y cargo-workspaces
|
||||||
- cp -r migrations crates/db_schema/
|
- cp -r migrations crates/db_schema/
|
||||||
- cargo workspaces publish --token "$CARGO_API_TOKEN" --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
|
- cargo workspaces publish --token "$CARGO_API_TOKEN" --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
|
||||||
secrets: [cargo_api_token]
|
|
||||||
when:
|
when:
|
||||||
- event: tag
|
- event: tag
|
||||||
|
|
||||||
|
|
32
Cargo.lock
generated
32
Cargo.lock
generated
|
@ -779,6 +779,17 @@ dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfb"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"fnv",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -1239,9 +1250,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diesel"
|
name = "diesel"
|
||||||
version = "2.2.4"
|
version = "2.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e"
|
checksum = "cbf9649c05e0a9dbd6d0b0b8301db5182b972d0fd02f0a7c6736cf632d7c0fd5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -1255,9 +1266,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "diesel-async"
|
name = "diesel-async"
|
||||||
version = "0.5.1"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c5c6ec8d5c7b8444d19a47161797cbe361e0fb1ee40c6a8124ec915b64a4125"
|
checksum = "51a307ac00f7c23f526a04a77761a0519b9f0eb2838ebf5b905a58580095bdcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"deadpool",
|
"deadpool",
|
||||||
|
@ -2360,6 +2371,15 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "infer"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847"
|
||||||
|
dependencies = [
|
||||||
|
"cfb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inout"
|
name = "inout"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
@ -2520,6 +2540,7 @@ dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"enum-map",
|
"enum-map",
|
||||||
"futures",
|
"futures",
|
||||||
|
"infer",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"lemmy_db_views",
|
"lemmy_db_views",
|
||||||
|
@ -2562,6 +2583,7 @@ dependencies = [
|
||||||
"lemmy_db_views",
|
"lemmy_db_views",
|
||||||
"lemmy_db_views_actor",
|
"lemmy_db_views_actor",
|
||||||
"lemmy_utils",
|
"lemmy_utils",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
@ -2677,8 +2699,10 @@ dependencies = [
|
||||||
"lemmy_utils",
|
"lemmy_utils",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
|
"test-context",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.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.18",
|
"lemmy-js-client": "0.20.0-api-v4.16",
|
||||||
"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.9.0)
|
version: 29.7.0(@types/node@22.9.0)
|
||||||
lemmy-js-client:
|
lemmy-js-client:
|
||||||
specifier: 0.20.0-alpha.18
|
specifier: 0.20.0-api-v4.16
|
||||||
version: 0.20.0-alpha.18
|
version: 0.20.0-api-v4.16
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
|
@ -1167,8 +1167,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.18:
|
lemmy-js-client@0.20.0-api-v4.16:
|
||||||
resolution: {integrity: sha512-oZy8DboTWfUar4mPWpi7SYrOEjTBJxkvd1e6QaVwoA5UhqQV1WhxEYbzrpi/gXnEokaVQ0i5sjtL/Y2PHMO3MQ==}
|
resolution: {integrity: sha512-9Wn7b8YT2KnEA286+RV1B3mLmecAynvAERoC0ZZiccfSgkEvd3rG9A5X9ejiPqp+JzDZJeisO57+Ut4QHr5oTw==}
|
||||||
|
|
||||||
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==}
|
||||||
|
@ -3077,7 +3077,7 @@ snapshots:
|
||||||
|
|
||||||
kleur@3.0.3: {}
|
kleur@3.0.3: {}
|
||||||
|
|
||||||
lemmy-js-client@0.20.0-alpha.18: {}
|
lemmy-js-client@0.20.0-api-v4.16: {}
|
||||||
|
|
||||||
leven@3.1.0: {}
|
leven@3.1.0: {}
|
||||||
|
|
||||||
|
|
|
@ -82,13 +82,13 @@ LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \
|
||||||
target/lemmy_server >$LOG_DIR/lemmy_epsilon.out 2>&1 &
|
target/lemmy_server >$LOG_DIR/lemmy_epsilon.out 2>&1 &
|
||||||
|
|
||||||
echo "wait for all instances to start"
|
echo "wait for all instances to start"
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-alpha:8541/api/v3/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-alpha:8541/api/v4/site')" != "200" ]]; do sleep 1; done
|
||||||
echo "alpha started"
|
echo "alpha started"
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-beta:8551/api/v3/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-beta:8551/api/v4/site')" != "200" ]]; do sleep 1; done
|
||||||
echo "beta started"
|
echo "beta started"
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-gamma:8561/api/v3/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-gamma:8561/api/v4/site')" != "200" ]]; do sleep 1; done
|
||||||
echo "gamma started"
|
echo "gamma started"
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-delta:8571/api/v3/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-delta:8571/api/v4/site')" != "200" ]]; do sleep 1; done
|
||||||
echo "delta started"
|
echo "delta started"
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-epsilon:8581/api/v3/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-epsilon:8581/api/v4/site')" != "200" ]]; do sleep 1; done
|
||||||
echo "epsilon started. All started"
|
echo "epsilon started. All started"
|
||||||
|
|
|
@ -156,7 +156,6 @@ test("Delete a comment", async () => {
|
||||||
commentRes.comment_view.comment.id,
|
commentRes.comment_view.comment.id,
|
||||||
);
|
);
|
||||||
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
||||||
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
|
||||||
|
|
||||||
// Make sure that comment is deleted on beta
|
// Make sure that comment is deleted on beta
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
|
@ -254,7 +253,6 @@ test("Remove a comment from admin and community on different instance", async ()
|
||||||
betaComment.comment.id,
|
betaComment.comment.id,
|
||||||
);
|
);
|
||||||
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
||||||
expect(removeCommentRes.comment_view.comment.content).toBe("");
|
|
||||||
|
|
||||||
// Comment text is also hidden from list
|
// Comment text is also hidden from list
|
||||||
let listComments = await getComments(
|
let listComments = await getComments(
|
||||||
|
@ -263,7 +261,6 @@ test("Remove a comment from admin and community on different instance", async ()
|
||||||
);
|
);
|
||||||
expect(listComments.comments.length).toBe(1);
|
expect(listComments.comments.length).toBe(1);
|
||||||
expect(listComments.comments[0].comment.removed).toBe(true);
|
expect(listComments.comments[0].comment.removed).toBe(true);
|
||||||
expect(listComments.comments[0].comment.content).toBe("");
|
|
||||||
|
|
||||||
// Make sure its not removed on alpha
|
// Make sure its not removed on alpha
|
||||||
let refetchedPostComments = await getComments(
|
let refetchedPostComments = await getComments(
|
||||||
|
@ -702,10 +699,10 @@ test("Check that activity from another instance is sent to third instance", asyn
|
||||||
|
|
||||||
test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.", async () => {
|
test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.", async () => {
|
||||||
// Unfollow all remote communities
|
// Unfollow all remote communities
|
||||||
let site = await unfollowRemotes(alpha);
|
let my_user = await unfollowRemotes(alpha);
|
||||||
expect(
|
expect(my_user.follows.filter(c => c.community.local == false).length).toBe(
|
||||||
site.my_user?.follows.filter(c => c.community.local == false).length,
|
0,
|
||||||
).toBe(0);
|
);
|
||||||
|
|
||||||
// B creates a post, and two comments, should be invisible to A
|
// B creates a post, and two comments, should be invisible to A
|
||||||
let postOnBetaRes = await createPost(beta, 2);
|
let postOnBetaRes = await createPost(beta, 2);
|
||||||
|
|
|
@ -25,17 +25,18 @@ import {
|
||||||
getComments,
|
getComments,
|
||||||
createComment,
|
createComment,
|
||||||
getCommunityByName,
|
getCommunityByName,
|
||||||
blockInstance,
|
|
||||||
waitUntil,
|
waitUntil,
|
||||||
alphaUrl,
|
alphaUrl,
|
||||||
delta,
|
delta,
|
||||||
betaAllowedInstances,
|
|
||||||
searchPostLocal,
|
searchPostLocal,
|
||||||
longDelay,
|
longDelay,
|
||||||
editCommunity,
|
editCommunity,
|
||||||
unfollows,
|
unfollows,
|
||||||
|
getMyUser,
|
||||||
|
userBlockInstance,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { EditCommunity, EditSite } from "lemmy-js-client";
|
import { AdminAllowInstanceParams } from "lemmy-js-client/dist/types/AdminAllowInstanceParams";
|
||||||
|
import { EditCommunity, EditSite, GetPosts } from "lemmy-js-client";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
afterAll(unfollows);
|
afterAll(unfollows);
|
||||||
|
@ -226,7 +227,7 @@ test("Admin actions in remote community are not federated to origin", async () =
|
||||||
if (!betaCommunity) {
|
if (!betaCommunity) {
|
||||||
throw "Missing beta community";
|
throw "Missing beta community";
|
||||||
}
|
}
|
||||||
let bannedUserInfo1 = (await getSite(gamma)).my_user?.local_user_view.person;
|
let bannedUserInfo1 = (await getMyUser(gamma)).local_user_view.person;
|
||||||
if (!bannedUserInfo1) {
|
if (!bannedUserInfo1) {
|
||||||
throw "Missing banned user 1";
|
throw "Missing banned user 1";
|
||||||
}
|
}
|
||||||
|
@ -363,7 +364,7 @@ test("User blocks instance, communities are hidden", async () => {
|
||||||
expect(listing_ids).toContain(postRes.post_view.post.ap_id);
|
expect(listing_ids).toContain(postRes.post_view.post.ap_id);
|
||||||
|
|
||||||
// block the beta instance
|
// block the beta instance
|
||||||
await blockInstance(alpha, alphaPost.community.instance_id, true);
|
await userBlockInstance(alpha, alphaPost.community.instance_id, true);
|
||||||
|
|
||||||
// after blocking, post should not be in listing
|
// after blocking, post should not be in listing
|
||||||
let listing2 = await getPosts(alpha, "All");
|
let listing2 = await getPosts(alpha, "All");
|
||||||
|
@ -371,7 +372,7 @@ test("User blocks instance, communities are hidden", async () => {
|
||||||
expect(listing_ids2.indexOf(postRes.post_view.post.ap_id)).toBe(-1);
|
expect(listing_ids2.indexOf(postRes.post_view.post.ap_id)).toBe(-1);
|
||||||
|
|
||||||
// unblock instance again
|
// unblock instance again
|
||||||
await blockInstance(alpha, alphaPost.community.instance_id, false);
|
await userBlockInstance(alpha, alphaPost.community.instance_id, false);
|
||||||
|
|
||||||
// post should be included in listing
|
// post should be included in listing
|
||||||
let listing3 = await getPosts(alpha, "All");
|
let listing3 = await getPosts(alpha, "All");
|
||||||
|
@ -455,9 +456,12 @@ test("Dont receive community activities after unsubscribe", async () => {
|
||||||
expect(communityRes1.community_view.counts.subscribers).toBe(2);
|
expect(communityRes1.community_view.counts.subscribers).toBe(2);
|
||||||
|
|
||||||
// temporarily block alpha, so that it doesn't know about unfollow
|
// temporarily block alpha, so that it doesn't know about unfollow
|
||||||
let editSiteForm: EditSite = {};
|
var allow_instance_params: AdminAllowInstanceParams = {
|
||||||
editSiteForm.allowed_instances = ["lemmy-epsilon"];
|
instance: "lemmy-alpha",
|
||||||
await beta.editSite(editSiteForm);
|
allow: false,
|
||||||
|
reason: undefined,
|
||||||
|
};
|
||||||
|
await beta.adminAllowInstance(allow_instance_params);
|
||||||
await longDelay();
|
await longDelay();
|
||||||
|
|
||||||
// unfollow
|
// unfollow
|
||||||
|
@ -471,8 +475,8 @@ test("Dont receive community activities after unsubscribe", async () => {
|
||||||
expect(communityRes2.community_view.counts.subscribers).toBe(2);
|
expect(communityRes2.community_view.counts.subscribers).toBe(2);
|
||||||
|
|
||||||
// unblock alpha
|
// unblock alpha
|
||||||
editSiteForm.allowed_instances = betaAllowedInstances;
|
allow_instance_params.allow = true;
|
||||||
await beta.editSite(editSiteForm);
|
await beta.adminAllowInstance(allow_instance_params);
|
||||||
await longDelay();
|
await longDelay();
|
||||||
|
|
||||||
// create a post, it shouldnt reach beta
|
// create a post, it shouldnt reach beta
|
||||||
|
@ -573,3 +577,29 @@ test("Remote mods can edit communities", async () => {
|
||||||
"Example description",
|
"Example description",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Community name with non-ascii chars", async () => {
|
||||||
|
const name = "това_ме_ядосва" + Math.random().toString().slice(2, 6);
|
||||||
|
let communityRes = await createCommunity(alpha, name);
|
||||||
|
|
||||||
|
let betaCommunity1 = await resolveCommunity(
|
||||||
|
beta,
|
||||||
|
communityRes.community_view.community.actor_id,
|
||||||
|
);
|
||||||
|
expect(betaCommunity1.community!.community.name).toBe(name);
|
||||||
|
|
||||||
|
let alphaCommunity2 = await getCommunityByName(alpha, name);
|
||||||
|
expect(alphaCommunity2.community_view.community.name).toBe(name);
|
||||||
|
|
||||||
|
let fediName = `${communityRes.community_view.community.name}@LEMMY-ALPHA:8541`;
|
||||||
|
let betaCommunity2 = await getCommunityByName(beta, fediName);
|
||||||
|
expect(betaCommunity2.community_view.community.name).toBe(name);
|
||||||
|
|
||||||
|
let postRes = await createPost(beta, betaCommunity1.community!.community.id);
|
||||||
|
|
||||||
|
let form: GetPosts = {
|
||||||
|
community_name: fediName,
|
||||||
|
};
|
||||||
|
let posts = await beta.getPosts(form);
|
||||||
|
expect(posts.posts[0].post.name).toBe(postRes.post_view.post.name);
|
||||||
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
registerUser,
|
registerUser,
|
||||||
unfollows,
|
unfollows,
|
||||||
delay,
|
delay,
|
||||||
|
getMyUser,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
|
@ -85,8 +86,8 @@ test("Follow federated community", async () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check it from local
|
// Check it from local
|
||||||
let site = await getSite(alpha);
|
let my_user = await getMyUser(alpha);
|
||||||
let remoteCommunityId = site.my_user?.follows.find(
|
let remoteCommunityId = my_user?.follows.find(
|
||||||
c =>
|
c =>
|
||||||
c.community.local == false &&
|
c.community.local == false &&
|
||||||
c.community.id === betaCommunityInitial.community.id,
|
c.community.id === betaCommunityInitial.community.id,
|
||||||
|
@ -102,9 +103,9 @@ test("Follow federated community", async () => {
|
||||||
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
|
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
|
||||||
|
|
||||||
// Make sure you are unsubbed locally
|
// Make sure you are unsubbed locally
|
||||||
let siteUnfollowCheck = await getSite(alpha);
|
let siteUnfollowCheck = await getMyUser(alpha);
|
||||||
expect(
|
expect(
|
||||||
siteUnfollowCheck.my_user?.follows.find(
|
siteUnfollowCheck.follows.find(
|
||||||
c => c.community.id === betaCommunityInitial.community.id,
|
c => c.community.id === betaCommunityInitial.community.id,
|
||||||
),
|
),
|
||||||
).toBe(undefined);
|
).toBe(undefined);
|
||||||
|
|
|
@ -32,6 +32,7 @@ import {
|
||||||
createPostWithThumbnail,
|
createPostWithThumbnail,
|
||||||
sampleImage,
|
sampleImage,
|
||||||
sampleSite,
|
sampleSite,
|
||||||
|
getMyUser,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
|
@ -129,9 +130,9 @@ test("Purge user, uploaded image removed", async () => {
|
||||||
expect(content.length).toBeGreaterThan(0);
|
expect(content.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
// purge user
|
// purge user
|
||||||
let site = await getSite(user);
|
let my_user = await getMyUser(user);
|
||||||
const purgeForm: PurgePerson = {
|
const purgeForm: PurgePerson = {
|
||||||
person_id: site.my_user!.local_user_view.person.id,
|
person_id: my_user.local_user_view.person.id,
|
||||||
};
|
};
|
||||||
const delete_ = await alphaImage.purgePerson(purgeForm);
|
const delete_ = await alphaImage.purgePerson(purgeForm);
|
||||||
expect(delete_.success).toBe(true);
|
expect(delete_.success).toBe(true);
|
||||||
|
@ -199,11 +200,11 @@ test("Images in remote image post are proxied if setting enabled", async () => {
|
||||||
// remote image gets proxied after upload
|
// remote image gets proxied after upload
|
||||||
expect(
|
expect(
|
||||||
post.thumbnail_url?.startsWith(
|
post.thumbnail_url?.startsWith(
|
||||||
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
"http://lemmy-gamma:8561/api/v4/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
post.body?.startsWith("![](http://lemmy-gamma:8561/api/v3/image_proxy?url"),
|
post.body?.startsWith("![](http://lemmy-gamma:8561/api/v4/image_proxy?url"),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
// Make sure that it ends with jpg, to be sure its an image
|
// Make sure that it ends with jpg, to be sure its an image
|
||||||
|
@ -222,12 +223,12 @@ test("Images in remote image post are proxied if setting enabled", async () => {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
epsilonPost.thumbnail_url?.startsWith(
|
epsilonPost.thumbnail_url?.startsWith(
|
||||||
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
"http://lemmy-epsilon:8581/api/v4/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
expect(
|
expect(
|
||||||
epsilonPost.body?.startsWith(
|
epsilonPost.body?.startsWith(
|
||||||
"![](http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
"![](http://lemmy-epsilon:8581/api/v4/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
|
@ -249,7 +250,7 @@ test("Thumbnail of remote image link is proxied if setting enabled", async () =>
|
||||||
// remote image gets proxied after upload
|
// remote image gets proxied after upload
|
||||||
expect(
|
expect(
|
||||||
post.thumbnail_url?.startsWith(
|
post.thumbnail_url?.startsWith(
|
||||||
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
|
"http://lemmy-gamma:8561/api/v4/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
|
@ -267,7 +268,7 @@ test("Thumbnail of remote image link is proxied if setting enabled", async () =>
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
epsilonPost.thumbnail_url?.startsWith(
|
epsilonPost.thumbnail_url?.startsWith(
|
||||||
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
|
"http://lemmy-epsilon:8581/api/v4/image_proxy?url",
|
||||||
),
|
),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,10 @@ import {
|
||||||
alphaUrl,
|
alphaUrl,
|
||||||
loginUser,
|
loginUser,
|
||||||
createCommunity,
|
createCommunity,
|
||||||
|
getMyUser,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { PostView } from "lemmy-js-client/dist/types/PostView";
|
import { PostView } from "lemmy-js-client/dist/types/PostView";
|
||||||
|
import { AdminBlockInstanceParams } from "lemmy-js-client/dist/types/AdminBlockInstanceParams";
|
||||||
import { EditSite, ResolveObject } from "lemmy-js-client";
|
import { EditSite, ResolveObject } from "lemmy-js-client";
|
||||||
|
|
||||||
let betaCommunity: CommunityView | undefined;
|
let betaCommunity: CommunityView | undefined;
|
||||||
|
@ -87,12 +89,12 @@ async function assertPostFederation(
|
||||||
}
|
}
|
||||||
|
|
||||||
test("Create a post", async () => {
|
test("Create a post", async () => {
|
||||||
// Setup some allowlists and blocklists
|
// Block alpha
|
||||||
const editSiteForm: EditSite = {};
|
var block_instance_params: AdminBlockInstanceParams = {
|
||||||
|
instance: "lemmy-alpha",
|
||||||
editSiteForm.allowed_instances = [];
|
block: true,
|
||||||
editSiteForm.blocked_instances = ["lemmy-alpha"];
|
};
|
||||||
await epsilon.editSite(editSiteForm);
|
await epsilon.adminBlockInstance(block_instance_params);
|
||||||
|
|
||||||
if (!betaCommunity) {
|
if (!betaCommunity) {
|
||||||
throw "Missing beta community";
|
throw "Missing beta community";
|
||||||
|
@ -132,11 +134,9 @@ test("Create a post", async () => {
|
||||||
resolvePost(epsilon, postRes.post_view.post),
|
resolvePost(epsilon, postRes.post_view.post),
|
||||||
).rejects.toStrictEqual(Error("not_found"));
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
|
|
||||||
// remove added allow/blocklists
|
// remove blocked instance
|
||||||
editSiteForm.allowed_instances = [];
|
block_instance_params.block = false;
|
||||||
editSiteForm.blocked_instances = [];
|
await epsilon.adminBlockInstance(block_instance_params);
|
||||||
await delta.editSite(editSiteForm);
|
|
||||||
await epsilon.editSite(editSiteForm);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create a post in a non-existent community", async () => {
|
test("Create a post in a non-existent community", async () => {
|
||||||
|
@ -452,8 +452,7 @@ test("Enforce site ban federation for local user", async () => {
|
||||||
|
|
||||||
// create a test user
|
// create a test user
|
||||||
let alphaUserHttp = await registerUser(alpha, alphaUrl);
|
let alphaUserHttp = await registerUser(alpha, alphaUrl);
|
||||||
let alphaUserPerson = (await getSite(alphaUserHttp)).my_user?.local_user_view
|
let alphaUserPerson = (await getMyUser(alphaUserHttp)).local_user_view.person;
|
||||||
.person;
|
|
||||||
let alphaUserActorId = alphaUserPerson?.actor_id;
|
let alphaUserActorId = alphaUserPerson?.actor_id;
|
||||||
if (!alphaUserActorId) {
|
if (!alphaUserActorId) {
|
||||||
throw "Missing alpha user actor id";
|
throw "Missing alpha user actor id";
|
||||||
|
@ -533,8 +532,7 @@ test("Enforce site ban federation for federated user", async () => {
|
||||||
|
|
||||||
// create a test user
|
// create a test user
|
||||||
let alphaUserHttp = await registerUser(alpha, alphaUrl);
|
let alphaUserHttp = await registerUser(alpha, alphaUrl);
|
||||||
let alphaUserPerson = (await getSite(alphaUserHttp)).my_user?.local_user_view
|
let alphaUserPerson = (await getMyUser(alphaUserHttp)).local_user_view.person;
|
||||||
.person;
|
|
||||||
let alphaUserActorId = alphaUserPerson?.actor_id;
|
let alphaUserActorId = alphaUserPerson?.actor_id;
|
||||||
if (!alphaUserActorId) {
|
if (!alphaUserActorId) {
|
||||||
throw "Missing alpha user actor id";
|
throw "Missing alpha user actor id";
|
||||||
|
@ -564,8 +562,7 @@ test("Enforce site ban federation for federated user", async () => {
|
||||||
expect(banAlphaOnBeta.banned).toBe(true);
|
expect(banAlphaOnBeta.banned).toBe(true);
|
||||||
|
|
||||||
// The beta site ban should NOT be federated to alpha
|
// The beta site ban should NOT be federated to alpha
|
||||||
let alphaPerson2 = (await getSite(alphaUserHttp)).my_user!.local_user_view
|
let alphaPerson2 = (await getMyUser(alphaUserHttp)).local_user_view.person;
|
||||||
.person;
|
|
||||||
expect(alphaPerson2.banned).toBe(false);
|
expect(alphaPerson2.banned).toBe(false);
|
||||||
|
|
||||||
// existing alpha post should be removed on beta
|
// existing alpha post should be removed on beta
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import {
|
import {
|
||||||
|
AdminBlockInstanceParams,
|
||||||
ApproveCommunityPendingFollower,
|
ApproveCommunityPendingFollower,
|
||||||
BlockCommunity,
|
BlockCommunity,
|
||||||
BlockCommunityResponse,
|
BlockCommunityResponse,
|
||||||
BlockInstance,
|
|
||||||
BlockInstanceResponse,
|
|
||||||
CommunityId,
|
CommunityId,
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
CreatePrivateMessageReport,
|
CreatePrivateMessageReport,
|
||||||
|
@ -17,15 +16,18 @@ import {
|
||||||
LemmyHttp,
|
LemmyHttp,
|
||||||
ListCommunityPendingFollows,
|
ListCommunityPendingFollows,
|
||||||
ListCommunityPendingFollowsResponse,
|
ListCommunityPendingFollowsResponse,
|
||||||
|
MyUserInfo,
|
||||||
PersonId,
|
PersonId,
|
||||||
PostView,
|
PostView,
|
||||||
PrivateMessageReportResponse,
|
PrivateMessageReportResponse,
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
|
UserBlockInstanceParams,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
||||||
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
|
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
|
||||||
import { EditPost } from "lemmy-js-client/dist/types/EditPost";
|
import { EditPost } from "lemmy-js-client/dist/types/EditPost";
|
||||||
import { EditSite } from "lemmy-js-client/dist/types/EditSite";
|
import { EditSite } from "lemmy-js-client/dist/types/EditSite";
|
||||||
|
import { AdminAllowInstanceParams } from "lemmy-js-client/dist/types/AdminAllowInstanceParams";
|
||||||
import { FeaturePost } from "lemmy-js-client/dist/types/FeaturePost";
|
import { FeaturePost } from "lemmy-js-client/dist/types/FeaturePost";
|
||||||
import { GetComments } from "lemmy-js-client/dist/types/GetComments";
|
import { GetComments } from "lemmy-js-client/dist/types/GetComments";
|
||||||
import { GetCommentsResponse } from "lemmy-js-client/dist/types/GetCommentsResponse";
|
import { GetCommentsResponse } from "lemmy-js-client/dist/types/GetCommentsResponse";
|
||||||
|
@ -104,13 +106,6 @@ export const gamma = new LemmyHttp(gammaUrl, { fetchFunction });
|
||||||
export const delta = new LemmyHttp(deltaUrl, { fetchFunction });
|
export const delta = new LemmyHttp(deltaUrl, { fetchFunction });
|
||||||
export const epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
|
export const epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
|
||||||
|
|
||||||
export const betaAllowedInstances = [
|
|
||||||
"lemmy-alpha",
|
|
||||||
"lemmy-gamma",
|
|
||||||
"lemmy-delta",
|
|
||||||
"lemmy-epsilon",
|
|
||||||
];
|
|
||||||
|
|
||||||
const password = "lemmylemmy";
|
const password = "lemmylemmy";
|
||||||
|
|
||||||
export async function setupLogins() {
|
export async function setupLogins() {
|
||||||
|
@ -168,30 +163,29 @@ export async function setupLogins() {
|
||||||
rate_limit_comment: 999,
|
rate_limit_comment: 999,
|
||||||
rate_limit_search: 999,
|
rate_limit_search: 999,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set the blocks and auths for each
|
|
||||||
editSiteForm.allowed_instances = [
|
|
||||||
"lemmy-beta",
|
|
||||||
"lemmy-gamma",
|
|
||||||
"lemmy-delta",
|
|
||||||
"lemmy-epsilon",
|
|
||||||
];
|
|
||||||
await alpha.editSite(editSiteForm);
|
await alpha.editSite(editSiteForm);
|
||||||
|
|
||||||
editSiteForm.allowed_instances = betaAllowedInstances;
|
|
||||||
await beta.editSite(editSiteForm);
|
await beta.editSite(editSiteForm);
|
||||||
|
|
||||||
editSiteForm.allowed_instances = [
|
|
||||||
"lemmy-alpha",
|
|
||||||
"lemmy-beta",
|
|
||||||
"lemmy-delta",
|
|
||||||
"lemmy-epsilon",
|
|
||||||
];
|
|
||||||
await gamma.editSite(editSiteForm);
|
await gamma.editSite(editSiteForm);
|
||||||
|
|
||||||
// Setup delta allowed instance
|
|
||||||
editSiteForm.allowed_instances = ["lemmy-beta"];
|
|
||||||
await delta.editSite(editSiteForm);
|
await delta.editSite(editSiteForm);
|
||||||
|
await epsilon.editSite(editSiteForm);
|
||||||
|
|
||||||
|
// Set the blocks for each
|
||||||
|
await allowInstance(alpha, "lemmy-beta");
|
||||||
|
await allowInstance(alpha, "lemmy-gamma");
|
||||||
|
await allowInstance(alpha, "lemmy-delta");
|
||||||
|
await allowInstance(alpha, "lemmy-epsilon");
|
||||||
|
|
||||||
|
await allowInstance(beta, "lemmy-alpha");
|
||||||
|
await allowInstance(beta, "lemmy-gamma");
|
||||||
|
await allowInstance(beta, "lemmy-delta");
|
||||||
|
await allowInstance(beta, "lemmy-epsilon");
|
||||||
|
|
||||||
|
await allowInstance(gamma, "lemmy-alpha");
|
||||||
|
await allowInstance(gamma, "lemmy-beta");
|
||||||
|
await allowInstance(gamma, "lemmy-delta");
|
||||||
|
await allowInstance(gamma, "lemmy-epsilon");
|
||||||
|
|
||||||
|
await allowInstance(delta, "lemmy-beta");
|
||||||
|
|
||||||
// Create the main alpha/beta communities
|
// Create the main alpha/beta communities
|
||||||
// Ignore thrown errors of duplicates
|
// Ignore thrown errors of duplicates
|
||||||
|
@ -208,6 +202,17 @@ export async function setupLogins() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function allowInstance(api: LemmyHttp, instance: string) {
|
||||||
|
const params: AdminAllowInstanceParams = {
|
||||||
|
instance,
|
||||||
|
allow: true,
|
||||||
|
};
|
||||||
|
// Ignore errors from duplicate allows (because setup gets called for each test file)
|
||||||
|
try {
|
||||||
|
await api.adminAllowInstance(params);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
export async function createPost(
|
export async function createPost(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
community_id: number,
|
community_id: number,
|
||||||
|
@ -757,6 +762,10 @@ export async function getSite(api: LemmyHttp): Promise<GetSiteResponse> {
|
||||||
return api.getSite();
|
return api.getSite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getMyUser(api: LemmyHttp): Promise<MyUserInfo> {
|
||||||
|
return api.getMyUser();
|
||||||
|
}
|
||||||
|
|
||||||
export async function listPrivateMessages(
|
export async function listPrivateMessages(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
): Promise<PrivateMessagesResponse> {
|
): Promise<PrivateMessagesResponse> {
|
||||||
|
@ -766,19 +775,16 @@ export async function listPrivateMessages(
|
||||||
return api.getPrivateMessages(form);
|
return api.getPrivateMessages(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unfollowRemotes(
|
export async function unfollowRemotes(api: LemmyHttp): Promise<MyUserInfo> {
|
||||||
api: LemmyHttp,
|
|
||||||
): Promise<GetSiteResponse> {
|
|
||||||
// Unfollow all remote communities
|
// Unfollow all remote communities
|
||||||
let site = await getSite(api);
|
let my_user = await getMyUser(api);
|
||||||
let remoteFollowed =
|
let remoteFollowed =
|
||||||
site.my_user?.follows.filter(c => c.community.local == false) ?? [];
|
my_user.follows.filter(c => c.community.local == false) ?? [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
remoteFollowed.map(cu => followCommunity(api, false, cu.community.id)),
|
remoteFollowed.map(cu => followCommunity(api, false, cu.community.id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let siteRes = await getSite(api);
|
return await getMyUser(api);
|
||||||
return siteRes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function followBeta(api: LemmyHttp): Promise<CommunityResponse> {
|
export async function followBeta(api: LemmyHttp): Promise<CommunityResponse> {
|
||||||
|
@ -854,16 +860,16 @@ export function getPosts(
|
||||||
return api.getPosts(form);
|
return api.getPosts(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function blockInstance(
|
export function userBlockInstance(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
instance_id: InstanceId,
|
instance_id: InstanceId,
|
||||||
block: boolean,
|
block: boolean,
|
||||||
): Promise<BlockInstanceResponse> {
|
): Promise<SuccessResponse> {
|
||||||
let form: BlockInstance = {
|
let form: UserBlockInstanceParams = {
|
||||||
instance_id,
|
instance_id,
|
||||||
block,
|
block,
|
||||||
};
|
};
|
||||||
return api.blockInstance(form);
|
return api.userBlockInstance(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function blockCommunity(
|
export function blockCommunity(
|
||||||
|
|
|
@ -22,8 +22,15 @@ import {
|
||||||
alphaImage,
|
alphaImage,
|
||||||
unfollows,
|
unfollows,
|
||||||
saveUserSettingsBio,
|
saveUserSettingsBio,
|
||||||
|
getMyUser,
|
||||||
|
getPersonDetails,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
import {
|
||||||
|
EditSite,
|
||||||
|
LemmyHttp,
|
||||||
|
SaveUserSettings,
|
||||||
|
UploadImage,
|
||||||
|
} from "lemmy-js-client";
|
||||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
|
@ -44,12 +51,9 @@ function assertUserFederation(userOne?: PersonView, userTwo?: PersonView) {
|
||||||
test("Create user", async () => {
|
test("Create user", async () => {
|
||||||
let user = await registerUser(alpha, alphaUrl);
|
let user = await registerUser(alpha, alphaUrl);
|
||||||
|
|
||||||
let site = await getSite(user);
|
let my_user = await getMyUser(user);
|
||||||
expect(site.my_user).toBeDefined();
|
expect(my_user).toBeDefined();
|
||||||
if (!site.my_user) {
|
apShortname = `${my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
||||||
throw "Missing site user";
|
|
||||||
}
|
|
||||||
apShortname = `${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Set some user settings, check that they are federated", async () => {
|
test("Set some user settings, check that they are federated", async () => {
|
||||||
|
@ -64,12 +68,15 @@ test("Set some user settings, check that they are federated", async () => {
|
||||||
};
|
};
|
||||||
await saveUserSettings(beta, form);
|
await saveUserSettings(beta, form);
|
||||||
|
|
||||||
let site = await getSite(beta);
|
let my_user = await getMyUser(beta);
|
||||||
expect(site.my_user?.local_user_view.local_user.theme).toBe("test");
|
expect(my_user.local_user_view.local_user.theme).toBe("test");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Delete user", async () => {
|
test("Delete user", async () => {
|
||||||
let user = await registerUser(alpha, alphaUrl);
|
let user = await registerUser(alpha, alphaUrl);
|
||||||
|
let user_profile = await getMyUser(user);
|
||||||
|
let person_id = user_profile.local_user_view.person.id;
|
||||||
|
let actor_id = user_profile.local_user_view.person.actor_id;
|
||||||
|
|
||||||
// make a local post and comment
|
// make a local post and comment
|
||||||
let alphaCommunity = (await resolveCommunity(user, "main@lemmy-alpha:8541"))
|
let alphaCommunity = (await resolveCommunity(user, "main@lemmy-alpha:8541"))
|
||||||
|
@ -97,6 +104,10 @@ test("Delete user", async () => {
|
||||||
expect(remoteComment).toBeDefined();
|
expect(remoteComment).toBeDefined();
|
||||||
|
|
||||||
await deleteUser(user);
|
await deleteUser(user);
|
||||||
|
await expect(getMyUser(user)).rejects.toStrictEqual(Error("incorrect_login"));
|
||||||
|
await expect(getPersonDetails(user, person_id)).rejects.toStrictEqual(
|
||||||
|
Error("not_found"),
|
||||||
|
);
|
||||||
|
|
||||||
// check that posts and comments are marked as deleted on other instances.
|
// check that posts and comments are marked as deleted on other instances.
|
||||||
// use get methods to avoid refetching from origin instance
|
// use get methods to avoid refetching from origin instance
|
||||||
|
@ -114,6 +125,9 @@ test("Delete user", async () => {
|
||||||
(await getComments(alpha, remoteComment.post_id)).comments[0].comment
|
(await getComments(alpha, remoteComment.post_id)).comments[0].comment
|
||||||
.deleted,
|
.deleted,
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
await expect(
|
||||||
|
getPersonDetails(user, remoteComment.creator_id),
|
||||||
|
).rejects.toStrictEqual(Error("not_found"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Requests with invalid auth should be treated as unauthenticated", async () => {
|
test("Requests with invalid auth should be treated as unauthenticated", async () => {
|
||||||
|
@ -121,8 +135,10 @@ test("Requests with invalid auth should be treated as unauthenticated", async ()
|
||||||
headers: { Authorization: "Bearer foobar" },
|
headers: { Authorization: "Bearer foobar" },
|
||||||
fetchFunction,
|
fetchFunction,
|
||||||
});
|
});
|
||||||
|
await expect(getMyUser(invalid_auth)).rejects.toStrictEqual(
|
||||||
|
Error("incorrect_login"),
|
||||||
|
);
|
||||||
let site = await getSite(invalid_auth);
|
let site = await getSite(invalid_auth);
|
||||||
expect(site.my_user).toBeUndefined();
|
|
||||||
expect(site.site_view).toBeDefined();
|
expect(site.site_view).toBeDefined();
|
||||||
|
|
||||||
let form: GetPosts = {};
|
let form: GetPosts = {};
|
||||||
|
@ -131,37 +147,39 @@ test("Requests with invalid auth should be treated as unauthenticated", async ()
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create user with Arabic name", async () => {
|
test("Create user with Arabic name", async () => {
|
||||||
let user = await registerUser(
|
// less than actor_name_max_length
|
||||||
alpha,
|
const name = "تجريب" + Math.random().toString().slice(2, 10);
|
||||||
alphaUrl,
|
let user = await registerUser(alpha, alphaUrl, name);
|
||||||
"تجريب" + Math.random().toString().slice(2, 10), // less than actor_name_max_length
|
|
||||||
);
|
|
||||||
|
|
||||||
let site = await getSite(user);
|
let my_user = await getMyUser(user);
|
||||||
expect(site.my_user).toBeDefined();
|
expect(my_user).toBeDefined();
|
||||||
if (!site.my_user) {
|
apShortname = `${my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
||||||
throw "Missing site user";
|
|
||||||
}
|
|
||||||
apShortname = `${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
|
||||||
|
|
||||||
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
|
let betaPerson1 = (await resolvePerson(beta, apShortname)).person;
|
||||||
expect(alphaPerson).toBeDefined();
|
expect(betaPerson1!.person.name).toBe(name);
|
||||||
|
|
||||||
|
let betaPerson2 = await getPersonDetails(beta, betaPerson1!.person.id);
|
||||||
|
expect(betaPerson2!.person_view.person.name).toBe(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create user with accept-language", async () => {
|
test("Create user with accept-language", async () => {
|
||||||
|
const edit: EditSite = {
|
||||||
|
discussion_languages: [32],
|
||||||
|
};
|
||||||
|
await alpha.editSite(edit);
|
||||||
|
|
||||||
let lemmy_http = new LemmyHttp(alphaUrl, {
|
let lemmy_http = new LemmyHttp(alphaUrl, {
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language#syntax
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language#syntax
|
||||||
headers: { "Accept-Language": "fr-CH, en;q=0.8, de;q=0.7, *;q=0.5" },
|
headers: { "Accept-Language": "fr-CH, en;q=0.8, *;q=0.5" },
|
||||||
});
|
});
|
||||||
let user = await registerUser(lemmy_http, alphaUrl);
|
let user = await registerUser(lemmy_http, alphaUrl);
|
||||||
|
|
||||||
|
let my_user = await getMyUser(user);
|
||||||
|
expect(my_user).toBeDefined();
|
||||||
|
expect(my_user?.local_user_view.local_user.interface_language).toBe("fr");
|
||||||
let site = await getSite(user);
|
let site = await getSite(user);
|
||||||
expect(site.my_user).toBeDefined();
|
|
||||||
expect(site.my_user?.local_user_view.local_user.interface_language).toBe(
|
|
||||||
"fr",
|
|
||||||
);
|
|
||||||
let langs = site.all_languages
|
let langs = site.all_languages
|
||||||
.filter(a => site.my_user?.discussion_languages.includes(a.id))
|
.filter(a => my_user.discussion_languages.includes(a.id))
|
||||||
.map(l => l.code);
|
.map(l => l.code);
|
||||||
// should have languages from accept header, as well as "undetermined"
|
// should have languages from accept header, as well as "undetermined"
|
||||||
// which is automatically enabled by backend
|
// which is automatically enabled by backend
|
||||||
|
@ -207,8 +225,8 @@ test("Set a new avatar, old avatar is deleted", async () => {
|
||||||
// Now try to save a user settings, with the icon missing,
|
// Now try to save a user settings, with the icon missing,
|
||||||
// and make sure it doesn't clear the data, or delete the image
|
// and make sure it doesn't clear the data, or delete the image
|
||||||
await saveUserSettingsBio(alpha);
|
await saveUserSettingsBio(alpha);
|
||||||
let site = await getSite(alpha);
|
let my_user = await getMyUser(alpha);
|
||||||
expect(site.my_user?.local_user_view.person.avatar).toBe(upload2.url);
|
expect(my_user.local_user_view.person.avatar).toBe(upload2.url);
|
||||||
|
|
||||||
// make sure only the new avatar is kept
|
// make sure only the new avatar is kept
|
||||||
const listMediaRes4 = await alphaImage.listMedia();
|
const listMediaRes4 = await alphaImage.listMedia();
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
{
|
{
|
||||||
# settings related to the postgresql database
|
# settings related to the postgresql database
|
||||||
database: {
|
database: {
|
||||||
# Configure the database by specifying a URI
|
# Configure the database by specifying URI pointing to a postgres instance
|
||||||
#
|
|
||||||
# This is the preferred method to specify database connection details since
|
|
||||||
# it is the most flexible.
|
|
||||||
# Connection URI pointing to a postgres instance
|
|
||||||
#
|
#
|
||||||
# This example uses peer authentication to obviate the need for creating,
|
# This example uses peer authentication to obviate the need for creating,
|
||||||
# configuring, and managing passwords.
|
# configuring, and managing passwords.
|
||||||
|
@ -14,25 +10,7 @@
|
||||||
# PostgreSQL's documentation.
|
# PostgreSQL's documentation.
|
||||||
#
|
#
|
||||||
# [0]: https://www.postgresql.org/docs/current/libpq-connect.html#id-1.7.3.8.3.6
|
# [0]: https://www.postgresql.org/docs/current/libpq-connect.html#id-1.7.3.8.3.6
|
||||||
uri: "postgresql:///lemmy?user=lemmy&host=/var/run/postgresql"
|
connection: "postgres://lemmy:password@localhost:5432/lemmy"
|
||||||
|
|
||||||
# or
|
|
||||||
|
|
||||||
# Configure the database by specifying parts of a URI
|
|
||||||
#
|
|
||||||
# Note that specifying the `uri` field should be preferred since it provides
|
|
||||||
# greater control over how the connection is made. This merely exists for
|
|
||||||
# backwards-compatibility.
|
|
||||||
# Username to connect to postgres
|
|
||||||
user: "string"
|
|
||||||
# Password to connect to postgres
|
|
||||||
password: "string"
|
|
||||||
# Host where postgres is running
|
|
||||||
host: "string"
|
|
||||||
# Port where postgres can be accessed
|
|
||||||
port: 123
|
|
||||||
# Name of the postgres database for lemmy
|
|
||||||
database: "string"
|
|
||||||
# Maximum number of active sql connections
|
# Maximum number of active sql connections
|
||||||
pool_size: 30
|
pool_size: 30
|
||||||
}
|
}
|
||||||
|
@ -66,13 +44,22 @@
|
||||||
# or
|
# or
|
||||||
|
|
||||||
# If enabled, all images from remote domains are rewritten to pass through
|
# If enabled, all images from remote domains are rewritten to pass through
|
||||||
# `/api/v3/image_proxy`, including embedded images in markdown. Images are stored temporarily
|
# `/api/v4/image_proxy`, including embedded images in markdown. Images are stored temporarily
|
||||||
# in pict-rs for caching. This improves privacy as users don't expose their IP to untrusted
|
# in pict-rs for caching. This improves privacy as users don't expose their IP to untrusted
|
||||||
# servers, and decreases load on other servers. However it increases bandwidth use for the
|
# servers, and decreases load on other servers. However it increases bandwidth use for the
|
||||||
# local server.
|
# local server.
|
||||||
#
|
#
|
||||||
# Requires pict-rs 0.5
|
# Requires pict-rs 0.5
|
||||||
"ProxyAllImages"
|
"ProxyAllImages"
|
||||||
|
# Allows bypassing proxy for specific image hosts when using ProxyAllImages.
|
||||||
|
#
|
||||||
|
# imgur.com is bypassed by default to avoid rate limit errors. When specifying any bypass
|
||||||
|
# in the config, this default is ignored and you need to list imgur explicitly. To proxy imgur
|
||||||
|
# requests, specify a noop bypass list, eg `proxy_bypass_domains ["example.org"]`.
|
||||||
|
proxy_bypass_domains: [
|
||||||
|
"i.imgur.com"
|
||||||
|
/* ... */
|
||||||
|
]
|
||||||
# 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.
|
||||||
|
|
|
@ -10,7 +10,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
mod_log::moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Joinable},
|
traits::{Crud, Joinable},
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ use lemmy_db_schema::{
|
||||||
CommunityPersonBanForm,
|
CommunityPersonBanForm,
|
||||||
},
|
},
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
mod_log::moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
||||||
},
|
},
|
||||||
traits::{Bannable, Crud, Followable},
|
traits::{Bannable, Crud, Followable},
|
||||||
};
|
};
|
||||||
|
@ -110,7 +110,7 @@ pub async fn ban_from_community(
|
||||||
|
|
||||||
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
|
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
|
let person_view = PersonView::read(&mut context.pool(), data.person_id, false).await?;
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::BanFromCommunity {
|
SendActivityData::BanFromCommunity {
|
||||||
|
|
|
@ -17,7 +17,7 @@ use lemmy_db_views_actor::structs::CommunityView;
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn block_community(
|
pub async fn user_block_community(
|
||||||
data: Json<BlockCommunity>,
|
data: Json<BlockCommunity>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityUpdateForm},
|
community::{Community, CommunityUpdateForm},
|
||||||
moderator::{ModHideCommunity, ModHideCommunityForm},
|
mod_log::moderator::{ModHideCommunity, ModHideCommunityForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
moderator::{ModTransferCommunity, ModTransferCommunityForm},
|
mod_log::moderator::{ModTransferCommunity, ModTransferCommunityForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Joinable},
|
traits::{Crud, Joinable},
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,7 @@ use lemmy_db_schema::{
|
||||||
CommunityPersonBanForm,
|
CommunityPersonBanForm,
|
||||||
},
|
},
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
mod_log::moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
|
||||||
person::Person,
|
person::Person,
|
||||||
},
|
},
|
||||||
traits::{Bannable, Crud, Followable},
|
traits::{Bannable, Crud, Followable},
|
||||||
|
|
|
@ -7,7 +7,7 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
local_user::{LocalUser, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
moderator::{ModAdd, ModAddForm},
|
mod_log::moderator::{ModAdd, ModAddForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
login_token::LoginToken,
|
login_token::LoginToken,
|
||||||
moderator::{ModBan, ModBanForm},
|
mod_log::moderator::{ModBan, ModBanForm},
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
@ -88,7 +88,7 @@ pub async fn ban_from_site(
|
||||||
|
|
||||||
ModBan::create(&mut context.pool(), &form).await?;
|
ModBan::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
let person_view = PersonView::read(&mut context.pool(), person.id).await?;
|
let person_view = PersonView::read(&mut context.pool(), person.id, false).await?;
|
||||||
|
|
||||||
ban_nonlocal_user_from_local_communities(
|
ban_nonlocal_user_from_local_communities(
|
||||||
&local_user_view,
|
&local_user_view,
|
||||||
|
|
|
@ -12,7 +12,7 @@ use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn block_person(
|
pub async fn user_block_person(
|
||||||
data: Json<BlockPerson>,
|
data: Json<BlockPerson>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
|
@ -48,7 +48,7 @@ pub async fn block_person(
|
||||||
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
|
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let person_view = PersonView::read(&mut context.pool(), target_id).await?;
|
let person_view = PersonView::read(&mut context.pool(), target_id, false).await?;
|
||||||
Ok(Json(BlockPersonResponse {
|
Ok(Json(BlockPersonResponse {
|
||||||
person_view,
|
person_view,
|
||||||
blocked: data.block,
|
blocked: data.block,
|
||||||
|
|
|
@ -15,5 +15,6 @@ pub mod report_count;
|
||||||
pub mod reset_password;
|
pub mod reset_password;
|
||||||
pub mod save_settings;
|
pub mod save_settings;
|
||||||
pub mod update_totp;
|
pub mod update_totp;
|
||||||
|
pub mod user_block_instance;
|
||||||
pub mod validate_auth;
|
pub mod validate_auth;
|
||||||
pub mod verify_email;
|
pub mod verify_email;
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, site::UserBlockInstanceParams, SuccessResponse};
|
||||||
context::LemmyContext,
|
|
||||||
site::{BlockInstance, BlockInstanceResponse},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::instance_block::{InstanceBlock, InstanceBlockForm},
|
source::instance_block::{InstanceBlock, InstanceBlockForm},
|
||||||
traits::Blockable,
|
traits::Blockable,
|
||||||
|
@ -12,11 +9,11 @@ use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn block_instance(
|
pub async fn user_block_instance(
|
||||||
data: Json<BlockInstance>,
|
data: Json<UserBlockInstanceParams>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<Json<BlockInstanceResponse>> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let instance_id = data.instance_id;
|
let instance_id = data.instance_id;
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
if local_user_view.person.instance_id == instance_id {
|
if local_user_view.person.instance_id == instance_id {
|
||||||
|
@ -38,7 +35,5 @@ pub async fn block_instance(
|
||||||
.with_lemmy_type(LemmyErrorType::InstanceBlockAlreadyExists)?;
|
.with_lemmy_type(LemmyErrorType::InstanceBlockAlreadyExists)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(BlockInstanceResponse {
|
Ok(Json(SuccessResponse::default()))
|
||||||
blocked: data.block,
|
|
||||||
}))
|
|
||||||
}
|
}
|
|
@ -10,7 +10,7 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
moderator::{ModFeaturePost, ModFeaturePostForm},
|
mod_log::moderator::{ModFeaturePost, ModFeaturePostForm},
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
|
|
@ -9,7 +9,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
moderator::{ModLockPost, ModLockPostForm},
|
mod_log::moderator::{ModLockPost, ModLockPostForm},
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
|
53
crates/api/src/site/admin_allow_instance.rs
Normal file
53
crates/api/src/site/admin_allow_instance.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
site::AdminAllowInstanceParams,
|
||||||
|
utils::is_admin,
|
||||||
|
LemmyErrorType,
|
||||||
|
SuccessResponse,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::source::{
|
||||||
|
federation_allowlist::{FederationAllowList, FederationAllowListForm},
|
||||||
|
instance::Instance,
|
||||||
|
mod_log::admin::{AdminAllowInstance, AdminAllowInstanceForm},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn admin_allow_instance(
|
||||||
|
data: Json<AdminAllowInstanceParams>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let blocklist = Instance::blocklist(&mut context.pool()).await?;
|
||||||
|
if !blocklist.is_empty() {
|
||||||
|
Err(LemmyErrorType::CannotCombineFederationBlocklistAndAllowlist)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance_id = Instance::read_or_create(&mut context.pool(), data.instance.clone())
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
let form = FederationAllowListForm {
|
||||||
|
instance_id,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
if data.allow {
|
||||||
|
FederationAllowList::allow(&mut context.pool(), &form).await?;
|
||||||
|
} else {
|
||||||
|
FederationAllowList::unallow(&mut context.pool(), instance_id).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mod_log_form = AdminAllowInstanceForm {
|
||||||
|
instance_id,
|
||||||
|
admin_person_id: local_user_view.person.id,
|
||||||
|
reason: data.reason.clone(),
|
||||||
|
allowed: data.allow,
|
||||||
|
};
|
||||||
|
AdminAllowInstance::insert(&mut context.pool(), &mod_log_form).await?;
|
||||||
|
|
||||||
|
Ok(Json(SuccessResponse::default()))
|
||||||
|
}
|
56
crates/api/src/site/admin_block_instance.rs
Normal file
56
crates/api/src/site/admin_block_instance.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
site::AdminBlockInstanceParams,
|
||||||
|
utils::is_admin,
|
||||||
|
LemmyErrorType,
|
||||||
|
SuccessResponse,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::source::{
|
||||||
|
federation_blocklist::{FederationBlockList, FederationBlockListForm},
|
||||||
|
instance::Instance,
|
||||||
|
mod_log::admin::{AdminBlockInstance, AdminBlockInstanceForm},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn admin_block_instance(
|
||||||
|
data: Json<AdminBlockInstanceParams>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let allowlist = Instance::allowlist(&mut context.pool()).await?;
|
||||||
|
if !allowlist.is_empty() {
|
||||||
|
Err(LemmyErrorType::CannotCombineFederationBlocklistAndAllowlist)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance_id = Instance::read_or_create(&mut context.pool(), data.instance.clone())
|
||||||
|
.await?
|
||||||
|
.id;
|
||||||
|
let form = FederationBlockListForm {
|
||||||
|
instance_id,
|
||||||
|
expires: data.expires,
|
||||||
|
updated: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if data.block {
|
||||||
|
FederationBlockList::block(&mut context.pool(), &form).await?;
|
||||||
|
} else {
|
||||||
|
FederationBlockList::unblock(&mut context.pool(), instance_id).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mod_log_form = AdminBlockInstanceForm {
|
||||||
|
instance_id,
|
||||||
|
admin_person_id: local_user_view.person.id,
|
||||||
|
blocked: data.block,
|
||||||
|
reason: data.reason.clone(),
|
||||||
|
when_: data.expires,
|
||||||
|
};
|
||||||
|
AdminBlockInstance::insert(&mut context.pool(), &mod_log_form).await?;
|
||||||
|
|
||||||
|
Ok(Json(SuccessResponse::default()))
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ use lemmy_db_schema::{
|
||||||
language::Language,
|
language::Language,
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
local_user::{LocalUser, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
moderator::{ModAdd, ModAddForm},
|
mod_log::moderator::{ModAdd, ModAddForm},
|
||||||
oauth_provider::OAuthProvider,
|
oauth_provider::OAuthProvider,
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
|
@ -69,14 +69,12 @@ pub async fn leave_admin(
|
||||||
site_view,
|
site_view,
|
||||||
admins,
|
admins,
|
||||||
version: VERSION.to_string(),
|
version: VERSION.to_string(),
|
||||||
my_user: None,
|
|
||||||
all_languages,
|
all_languages,
|
||||||
discussion_languages,
|
discussion_languages,
|
||||||
oauth_providers: Some(oauth_providers),
|
oauth_providers: Some(oauth_providers),
|
||||||
admin_oauth_providers: None,
|
admin_oauth_providers: None,
|
||||||
blocked_urls,
|
blocked_urls,
|
||||||
tagline,
|
tagline,
|
||||||
taglines: vec![],
|
my_user: None,
|
||||||
custom_emojis: vec![],
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod block;
|
pub mod admin_allow_instance;
|
||||||
|
pub mod admin_block_instance;
|
||||||
pub mod federated_instances;
|
pub mod federated_instances;
|
||||||
pub mod leave_admin;
|
pub mod leave_admin;
|
||||||
pub mod list_all_media;
|
pub mod list_all_media;
|
||||||
|
|
|
@ -7,6 +7,8 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{source::local_site::LocalSite, ModlogActionType};
|
use lemmy_db_schema::{source::local_site::LocalSite, ModlogActionType};
|
||||||
use lemmy_db_views::structs::LocalUserView;
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
use lemmy_db_views_moderator::structs::{
|
use lemmy_db_views_moderator::structs::{
|
||||||
|
AdminAllowInstanceView,
|
||||||
|
AdminBlockInstanceView,
|
||||||
AdminPurgeCommentView,
|
AdminPurgeCommentView,
|
||||||
AdminPurgeCommunityView,
|
AdminPurgeCommunityView,
|
||||||
AdminPurgePersonView,
|
AdminPurgePersonView,
|
||||||
|
@ -121,6 +123,8 @@ pub async fn get_mod_log(
|
||||||
admin_purged_communities,
|
admin_purged_communities,
|
||||||
admin_purged_posts,
|
admin_purged_posts,
|
||||||
admin_purged_comments,
|
admin_purged_comments,
|
||||||
|
admin_block_instance,
|
||||||
|
admin_allow_instance,
|
||||||
) = if data.community_id.is_none() {
|
) = if data.community_id.is_none() {
|
||||||
(
|
(
|
||||||
match type_ {
|
match type_ {
|
||||||
|
@ -161,6 +165,18 @@ pub async fn get_mod_log(
|
||||||
}
|
}
|
||||||
_ => Default::default(),
|
_ => Default::default(),
|
||||||
},
|
},
|
||||||
|
match type_ {
|
||||||
|
All | AdminBlockInstance if other_person_id.is_none() => {
|
||||||
|
AdminBlockInstanceView::list(&mut context.pool(), params).await?
|
||||||
|
}
|
||||||
|
_ => Default::default(),
|
||||||
|
},
|
||||||
|
match type_ {
|
||||||
|
All | AdminAllowInstance if other_person_id.is_none() => {
|
||||||
|
AdminAllowInstanceView::list(&mut context.pool(), params).await?
|
||||||
|
}
|
||||||
|
_ => Default::default(),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Default::default()
|
Default::default()
|
||||||
|
@ -183,5 +199,7 @@ pub async fn get_mod_log(
|
||||||
admin_purged_posts,
|
admin_purged_posts,
|
||||||
admin_purged_comments,
|
admin_purged_comments,
|
||||||
hidden_communities,
|
hidden_communities,
|
||||||
|
admin_block_instance,
|
||||||
|
admin_allow_instance,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgeComment, AdminPurgeCommentForm},
|
mod_log::admin::{AdminPurgeComment, AdminPurgeCommentForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgeCommunity, AdminPurgeCommunityForm},
|
mod_log::admin::{AdminPurgeCommunity, AdminPurgeCommunityForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgePerson, AdminPurgePersonForm},
|
mod_log::admin::{AdminPurgePerson, AdminPurgePersonForm},
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{AdminPurgePost, AdminPurgePostForm},
|
mod_log::admin::{AdminPurgePost, AdminPurgePostForm},
|
||||||
post::Post,
|
post::Post,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
|
|
@ -66,6 +66,7 @@ enum-map = { workspace = true }
|
||||||
urlencoding = { workspace = true }
|
urlencoding = { workspace = true }
|
||||||
mime = { version = "0.3.17", optional = true }
|
mime = { version = "0.3.17", optional = true }
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
|
infer = "0.16.0"
|
||||||
webpage = { version = "2.0", default-features = false, features = [
|
webpage = { version = "2.0", default-features = false, features = [
|
||||||
"serde",
|
"serde",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
|
|
|
@ -11,7 +11,7 @@ Here is an example using [reqwest](https://crates.io/crates/reqwest):
|
||||||
};
|
};
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.get("https://lemmy.ml/api/v3/post/list")
|
.get("https://lemmy.ml/api/v4/post/list")
|
||||||
.query(¶ms)
|
.query(¶ms)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -25,6 +25,8 @@ pub struct CreateOAuthProvider {
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +56,8 @@ pub struct EditOAuthProvider {
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,4 +86,6 @@ pub struct AuthenticateWithOauth {
|
||||||
/// 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))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub answer: Option<String>,
|
pub answer: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub pkce_code_verifier: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
|
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId, TagId},
|
||||||
ListingType,
|
ListingType,
|
||||||
PostFeatureType,
|
PostFeatureType,
|
||||||
PostSortType,
|
PostSortType,
|
||||||
|
@ -37,6 +37,8 @@ pub struct CreatePost {
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub custom_thumbnail: Option<String>,
|
pub custom_thumbnail: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub tags: Option<Vec<TagId>>,
|
||||||
/// 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))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub scheduled_publish_time: Option<i64>,
|
pub scheduled_publish_time: Option<i64>,
|
||||||
|
@ -164,6 +166,8 @@ pub struct EditPost {
|
||||||
/// Instead of fetching a thumbnail, use a custom one.
|
/// Instead of fetching a thumbnail, use a custom one.
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub custom_thumbnail: Option<String>,
|
pub custom_thumbnail: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub tags: Option<Vec<TagId>>,
|
||||||
/// 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))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub scheduled_publish_time: Option<i64>,
|
pub scheduled_publish_time: Option<i64>,
|
||||||
|
|
|
@ -23,6 +23,7 @@ use lemmy_utils::{
|
||||||
REQWEST_TIMEOUT,
|
REQWEST_TIMEOUT,
|
||||||
VERSION,
|
VERSION,
|
||||||
};
|
};
|
||||||
|
use mime::{Mime, TEXT_HTML};
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
header::{CONTENT_TYPE, RANGE},
|
header::{CONTENT_TYPE, RANGE},
|
||||||
Client,
|
Client,
|
||||||
|
@ -50,9 +51,11 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn fetch_link_metadata(url: &Url, context: &LemmyContext) -> LemmyResult<LinkMetadata> {
|
pub async fn fetch_link_metadata(url: &Url, context: &LemmyContext) -> LemmyResult<LinkMetadata> {
|
||||||
info!("Fetching site metadata for url: {}", url);
|
info!("Fetching site metadata for url: {}", url);
|
||||||
// We only fetch the first 64kB of data in order to not waste bandwidth especially for large
|
// We only fetch the first MB of data in order to not waste bandwidth especially for large
|
||||||
// binary files
|
// binary files. This high limit is particularly needed for youtube, which includes a lot of
|
||||||
let bytes_to_fetch = 64 * 1024;
|
// javascript code before the opengraph tags. Mastodon also uses a 1 MB limit:
|
||||||
|
// https://github.com/mastodon/mastodon/blob/295ad6f19a016b3f16e1201ffcbb1b3ad6b455a2/app/lib/request.rb#L213
|
||||||
|
let bytes_to_fetch = 1024 * 1024;
|
||||||
let response = context
|
let response = context
|
||||||
.client()
|
.client()
|
||||||
.get(url.as_str())
|
.get(url.as_str())
|
||||||
|
@ -63,47 +66,54 @@ pub async fn fetch_link_metadata(url: &Url, context: &LemmyContext) -> LemmyResu
|
||||||
.await?
|
.await?
|
||||||
.error_for_status()?;
|
.error_for_status()?;
|
||||||
|
|
||||||
// In some cases servers send a wrong mime type for images, which prevents thumbnail
|
let mut content_type: Option<Mime> = response
|
||||||
// generation. To avoid this we also try to guess the mime type from file extension.
|
|
||||||
let content_type = mime_guess::from_path(url.path())
|
|
||||||
.first()
|
|
||||||
// If you can guess that its an image type, then return that first.
|
|
||||||
.filter(|guess| guess.type_() == mime::IMAGE)
|
|
||||||
// Otherwise, get the content type from the headers
|
|
||||||
.or(
|
|
||||||
response
|
|
||||||
.headers()
|
.headers()
|
||||||
.get(CONTENT_TYPE)
|
.get(CONTENT_TYPE)
|
||||||
.and_then(|h| h.to_str().ok())
|
.and_then(|h| h.to_str().ok())
|
||||||
.and_then(|h| h.parse().ok()),
|
.and_then(|h| h.parse().ok())
|
||||||
);
|
// If we don't get a content_type from the response (e.g. if the server is down),
|
||||||
|
// then try to infer the content_type from the file extension.
|
||||||
|
.or(mime_guess::from_path(url.path()).first());
|
||||||
|
|
||||||
let opengraph_data = {
|
let opengraph_data = {
|
||||||
// if the content type is not text/html, we don't need to parse it
|
|
||||||
let is_html = content_type
|
let is_html = content_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
(c.type_() == mime::TEXT && c.subtype() == mime::HTML)
|
|
||||||
||
|
|
||||||
// application/xhtml+xml is a subset of HTML
|
// application/xhtml+xml is a subset of HTML
|
||||||
(c.type_() == mime::APPLICATION && c.subtype() == "xhtml")
|
let application_xhtml: Mime = "application/xhtml+xml".parse::<Mime>().unwrap_or(TEXT_HTML);
|
||||||
|
let allowed_mime_types = [TEXT_HTML.essence_str(), application_xhtml.essence_str()];
|
||||||
|
allowed_mime_types.contains(&c.essence_str())
|
||||||
})
|
})
|
||||||
.unwrap_or(false);
|
.unwrap_or_default();
|
||||||
if !is_html {
|
|
||||||
Default::default()
|
if is_html {
|
||||||
} else {
|
|
||||||
// Can't use .text() here, because it only checks the content header, not the actual bytes
|
// Can't use .text() here, because it only checks the content header, not the actual bytes
|
||||||
// https://github.com/LemmyNet/lemmy/issues/1964
|
// https://github.com/LemmyNet/lemmy/issues/1964
|
||||||
// So we want to do deep inspection of the actually returned bytes but need to be careful not
|
// So we want to do deep inspection of the actually returned bytes but need to be careful
|
||||||
// spend too much time parsing binary data as HTML
|
// not spend too much time parsing binary data as HTML
|
||||||
|
|
||||||
// only take first bytes regardless of how many bytes the server returns
|
// only take first bytes regardless of how many bytes the server returns
|
||||||
let html_bytes = collect_bytes_until_limit(response, bytes_to_fetch).await?;
|
let html_bytes = collect_bytes_until_limit(response, bytes_to_fetch).await?;
|
||||||
extract_opengraph_data(&html_bytes, url)
|
extract_opengraph_data(&html_bytes, url)
|
||||||
.map_err(|e| info!("{e}"))
|
.map_err(|e| info!("{e}"))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
let is_octet_type = content_type
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.subtype() == "octet-stream")
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Overwrite the content type if its an octet type
|
||||||
|
if is_octet_type {
|
||||||
|
// Don't need to fetch as much data for this as we do with opengraph
|
||||||
|
let octet_bytes = collect_bytes_until_limit(response, 512).await?;
|
||||||
|
content_type =
|
||||||
|
infer::get(&octet_bytes).map_or(content_type, |t| t.mime_type().parse().ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
Default::default()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(LinkMetadata {
|
Ok(LinkMetadata {
|
||||||
opengraph_data,
|
opengraph_data,
|
||||||
content_type: content_type.map(|c| c.to_string()),
|
content_type: content_type.map(|c| c.to_string()),
|
||||||
|
|
|
@ -43,6 +43,8 @@ use lemmy_db_views_actor::structs::{
|
||||||
PersonView,
|
PersonView,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_moderator::structs::{
|
use lemmy_db_views_moderator::structs::{
|
||||||
|
AdminAllowInstanceView,
|
||||||
|
AdminBlockInstanceView,
|
||||||
AdminPurgeCommentView,
|
AdminPurgeCommentView,
|
||||||
AdminPurgeCommunityView,
|
AdminPurgeCommunityView,
|
||||||
AdminPurgePersonView,
|
AdminPurgePersonView,
|
||||||
|
@ -183,6 +185,8 @@ pub struct GetModlogResponse {
|
||||||
pub admin_purged_posts: Vec<AdminPurgePostView>,
|
pub admin_purged_posts: Vec<AdminPurgePostView>,
|
||||||
pub admin_purged_comments: Vec<AdminPurgeCommentView>,
|
pub admin_purged_comments: Vec<AdminPurgeCommentView>,
|
||||||
pub hidden_communities: Vec<ModHideCommunityView>,
|
pub hidden_communities: Vec<ModHideCommunityView>,
|
||||||
|
pub admin_block_instance: Vec<AdminBlockInstanceView>,
|
||||||
|
pub admin_allow_instance: Vec<AdminAllowInstanceView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
@ -265,10 +269,6 @@ pub struct CreateSite {
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub captcha_difficulty: Option<String>,
|
pub captcha_difficulty: Option<String>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub allowed_instances: Option<Vec<String>>,
|
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
|
||||||
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))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub oauth_registration: Option<bool>,
|
pub oauth_registration: Option<bool>,
|
||||||
|
@ -394,12 +394,6 @@ pub struct EditSite {
|
||||||
/// The captcha difficulty. Can be easy, medium, or hard
|
/// The captcha difficulty. Can be easy, medium, or hard
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[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.
|
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
|
||||||
pub allowed_instances: Option<Vec<String>>,
|
|
||||||
/// A list of blocked instances.
|
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
|
||||||
pub blocked_instances: Option<Vec<String>>,
|
|
||||||
/// A list of blocked URLs
|
/// A list of blocked URLs
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub blocked_urls: Option<Vec<String>>,
|
pub blocked_urls: Option<Vec<String>>,
|
||||||
|
@ -435,7 +429,7 @@ pub struct EditSite {
|
||||||
/// The response for a site.
|
/// The response for a site.
|
||||||
pub struct SiteResponse {
|
pub struct SiteResponse {
|
||||||
pub site_view: SiteView,
|
pub site_view: SiteView,
|
||||||
/// deprecated, use field `tagline` or /api/v3/tagline/list
|
/// deprecated, use field `tagline` or /api/v4/tagline/list
|
||||||
pub taglines: Vec<()>,
|
pub taglines: Vec<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,14 +442,10 @@ 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))]
|
#[cfg_attr(feature = "full", ts(skip))]
|
||||||
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>,
|
||||||
/// deprecated, use field `tagline` or /api/v3/tagline/list
|
|
||||||
pub taglines: Vec<()>,
|
|
||||||
/// deprecated, use /api/v3/custom_emoji/list
|
|
||||||
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))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub tagline: Option<Tagline>,
|
pub tagline: Option<Tagline>,
|
||||||
|
@ -648,15 +638,29 @@ pub struct GetUnreadRegistrationApplicationCountResponse {
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
/// Block an instance as user
|
/// Block an instance as user
|
||||||
pub struct BlockInstance {
|
pub struct UserBlockInstanceParams {
|
||||||
pub instance_id: InstanceId,
|
pub instance_id: InstanceId,
|
||||||
pub block: bool,
|
pub block: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
pub struct BlockInstanceResponse {
|
pub struct AdminBlockInstanceParams {
|
||||||
pub blocked: bool,
|
pub instance: String,
|
||||||
|
pub block: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub expires: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct AdminAllowInstanceParams {
|
||||||
|
pub instance: String,
|
||||||
|
pub allow: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,12 @@ use lemmy_db_schema::{
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
local_site_rate_limit::LocalSiteRateLimit,
|
local_site_rate_limit::LocalSiteRateLimit,
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
moderator::{ModRemoveComment, ModRemoveCommentForm, ModRemovePost, ModRemovePostForm},
|
mod_log::moderator::{
|
||||||
|
ModRemoveComment,
|
||||||
|
ModRemoveCommentForm,
|
||||||
|
ModRemovePost,
|
||||||
|
ModRemovePostForm,
|
||||||
|
},
|
||||||
oauth_account::OAuthAccount,
|
oauth_account::OAuthAccount,
|
||||||
password_reset_request::PasswordResetRequest,
|
password_reset_request::PasswordResetRequest,
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
|
@ -71,7 +76,7 @@ use tracing::warn;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
use urlencoding::encode;
|
use urlencoding::encode;
|
||||||
|
|
||||||
pub static AUTH_COOKIE_NAME: &str = "jwt";
|
pub const AUTH_COOKIE_NAME: &str = "jwt";
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn is_mod_or_admin(
|
pub async fn is_mod_or_admin(
|
||||||
|
@ -118,8 +123,6 @@ pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> {
|
||||||
check_user_valid(&local_user_view.person)?;
|
check_user_valid(&local_user_view.person)?;
|
||||||
if !local_user_view.local_user.admin {
|
if !local_user_view.local_user.admin {
|
||||||
Err(LemmyErrorType::NotAnAdmin)?
|
Err(LemmyErrorType::NotAnAdmin)?
|
||||||
} else if local_user_view.person.banned {
|
|
||||||
Err(LemmyErrorType::Banned)?
|
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1120,7 +1123,7 @@ async fn proxy_image_link_internal(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewrite a link to go through `/api/v3/image_proxy` endpoint. This is only for remote urls and
|
/// Rewrite a link to go through `/api/v4/image_proxy` endpoint. This is only for remote urls and
|
||||||
/// if image_proxy setting is enabled.
|
/// if image_proxy setting is enabled.
|
||||||
pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
|
pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult<DbUrl> {
|
||||||
proxy_image_link_internal(
|
proxy_image_link_internal(
|
||||||
|
@ -1172,7 +1175,7 @@ fn build_proxied_image_url(
|
||||||
protocol_and_hostname: &str,
|
protocol_and_hostname: &str,
|
||||||
) -> Result<Url, url::ParseError> {
|
) -> Result<Url, url::ParseError> {
|
||||||
Url::parse(&format!(
|
Url::parse(&format!(
|
||||||
"{}/api/v3/image_proxy?url={}",
|
"{}/api/v4/image_proxy?url={}",
|
||||||
protocol_and_hostname,
|
protocol_and_hostname,
|
||||||
encode(link.as_str())
|
encode(link.as_str())
|
||||||
))
|
))
|
||||||
|
@ -1251,7 +1254,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
|
"https://lemmy-alpha/api/v4/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
|
||||||
proxied.as_str()
|
proxied.as_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ anyhow.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
webmention = "0.6.0"
|
webmention = "0.6.0"
|
||||||
accept-language = "3.1.0"
|
accept-language = "3.1.0"
|
||||||
|
regex = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
|
|
|
@ -12,7 +12,7 @@ use lemmy_db_schema::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
comment_report::CommentReport,
|
comment_report::CommentReport,
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{ModRemoveComment, ModRemoveCommentForm},
|
mod_log::moderator::{ModRemoveComment, ModRemoveCommentForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Reportable},
|
traits::{Crud, Reportable},
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::{Community, CommunityUpdateForm},
|
community::{Community, CommunityUpdateForm},
|
||||||
moderator::{ModRemoveCommunity, ModRemoveCommunityForm},
|
mod_log::moderator::{ModRemoveCommunity, ModRemoveCommunityForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,6 +35,7 @@ pub async fn create_oauth_provider(
|
||||||
scopes: data.scopes.to_string(),
|
scopes: data.scopes.to_string(),
|
||||||
auto_verify_email: data.auto_verify_email,
|
auto_verify_email: data.auto_verify_email,
|
||||||
account_linking_enabled: data.account_linking_enabled,
|
account_linking_enabled: data.account_linking_enabled,
|
||||||
|
use_pkce: data.use_pkce,
|
||||||
enabled: data.enabled,
|
enabled: data.enabled,
|
||||||
};
|
};
|
||||||
let oauth_provider = OAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?;
|
let oauth_provider = OAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?;
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub async fn update_oauth_provider(
|
||||||
auto_verify_email: data.auto_verify_email,
|
auto_verify_email: data.auto_verify_email,
|
||||||
account_linking_enabled: data.account_linking_enabled,
|
account_linking_enabled: data.account_linking_enabled,
|
||||||
enabled: data.enabled,
|
enabled: data.enabled,
|
||||||
|
use_pkce: data.use_pkce,
|
||||||
updated: Some(Some(Utc::now())),
|
updated: Some(Some(Utc::now())),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
local_user::LocalUser,
|
local_user::LocalUser,
|
||||||
moderator::{ModRemovePost, ModRemovePostForm},
|
mod_log::moderator::{ModRemovePost, ModRemovePostForm},
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
post_report::PostReport,
|
post_report::PostReport,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,30 +1,32 @@
|
||||||
|
use crate::user::my_user::get_my_user;
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{context::LemmyContext, site::GetSiteResponse};
|
||||||
context::LemmyContext,
|
|
||||||
site::{GetSiteResponse, MyUserInfo},
|
|
||||||
};
|
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::{
|
||||||
actor_language::{LocalUserLanguage, SiteLanguage},
|
actor_language::SiteLanguage,
|
||||||
community_block::CommunityBlock,
|
|
||||||
instance_block::InstanceBlock,
|
|
||||||
language::Language,
|
language::Language,
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
oauth_provider::OAuthProvider,
|
oauth_provider::OAuthProvider,
|
||||||
person_block::PersonBlock,
|
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView};
|
use lemmy_db_views_actor::structs::PersonView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{build_cache, error::LemmyResult, CacheLock, VERSION};
|
||||||
build_cache,
|
|
||||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
|
||||||
CacheLock,
|
|
||||||
VERSION,
|
|
||||||
};
|
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
#[tracing::instrument(skip(context))]
|
||||||
pub async fn get_site(
|
pub async fn get_site_v3(
|
||||||
|
local_user_view: Option<LocalUserView>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> LemmyResult<Json<GetSiteResponse>> {
|
||||||
|
let mut site = get_site_v4(local_user_view.clone(), context.clone()).await?;
|
||||||
|
if let Some(local_user_view) = local_user_view {
|
||||||
|
site.my_user = Some(get_my_user(local_user_view, context).await?.0);
|
||||||
|
}
|
||||||
|
Ok(site)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn get_site_v4(
|
||||||
local_user_view: Option<LocalUserView>,
|
local_user_view: Option<LocalUserView>,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<Json<GetSiteResponse>> {
|
) -> LemmyResult<Json<GetSiteResponse>> {
|
||||||
|
@ -35,42 +37,6 @@ pub async fn get_site(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to construct site response: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("Failed to construct site response: {e}"))?;
|
||||||
|
|
||||||
// Build the local user with parallel queries and add it to site response
|
|
||||||
site_response.my_user = if let Some(ref local_user_view) = local_user_view {
|
|
||||||
let person_id = local_user_view.person.id;
|
|
||||||
let local_user_id = local_user_view.local_user.id;
|
|
||||||
let pool = &mut context.pool();
|
|
||||||
|
|
||||||
let (
|
|
||||||
follows,
|
|
||||||
community_blocks,
|
|
||||||
instance_blocks,
|
|
||||||
person_blocks,
|
|
||||||
moderates,
|
|
||||||
discussion_languages,
|
|
||||||
) = lemmy_db_schema::try_join_with_pool!(pool => (
|
|
||||||
|pool| CommunityFollowerView::for_person(pool, person_id),
|
|
||||||
|pool| CommunityBlock::for_person(pool, person_id),
|
|
||||||
|pool| InstanceBlock::for_person(pool, person_id),
|
|
||||||
|pool| PersonBlock::for_person(pool, person_id),
|
|
||||||
|pool| CommunityModeratorView::for_person(pool, person_id, Some(&local_user_view.local_user)),
|
|
||||||
|pool| LocalUserLanguage::read(pool, local_user_id)
|
|
||||||
))
|
|
||||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
|
||||||
|
|
||||||
Some(MyUserInfo {
|
|
||||||
local_user_view: local_user_view.clone(),
|
|
||||||
follows,
|
|
||||||
moderates,
|
|
||||||
community_blocks,
|
|
||||||
instance_blocks,
|
|
||||||
person_blocks,
|
|
||||||
discussion_languages,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// filter oauth_providers for public access
|
// filter oauth_providers for public access
|
||||||
if !local_user_view
|
if !local_user_view
|
||||||
.map(|l| l.local_user.admin)
|
.map(|l| l.local_user.admin)
|
||||||
|
@ -103,7 +69,5 @@ async fn read_site(context: &LemmyContext) -> LemmyResult<GetSiteResponse> {
|
||||||
tagline,
|
tagline,
|
||||||
oauth_providers: Some(oauth_providers),
|
oauth_providers: Some(oauth_providers),
|
||||||
admin_oauth_providers: Some(admin_oauth_providers),
|
admin_oauth_providers: Some(admin_oauth_providers),
|
||||||
taglines: vec![],
|
|
||||||
custom_emojis: vec![],
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,6 @@ use lemmy_api_common::{
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
actor_language::SiteLanguage,
|
actor_language::SiteLanguage,
|
||||||
federation_allowlist::FederationAllowList,
|
|
||||||
federation_blocklist::FederationBlockList,
|
|
||||||
local_site::{LocalSite, LocalSiteUpdateForm},
|
local_site::{LocalSite, LocalSiteUpdateForm},
|
||||||
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm},
|
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm},
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
|
@ -152,12 +150,6 @@ pub async fn update_site(
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
// Replace the blocked and allowed instances
|
|
||||||
let allowed = data.allowed_instances.clone();
|
|
||||||
FederationAllowList::replace(&mut context.pool(), allowed).await?;
|
|
||||||
let blocked = data.blocked_instances.clone();
|
|
||||||
FederationBlockList::replace(&mut context.pool(), blocked).await?;
|
|
||||||
|
|
||||||
if let Some(url_blocklist) = data.blocked_urls.clone() {
|
if let Some(url_blocklist) = data.blocked_urls.clone() {
|
||||||
let parsed_urls = check_urls_are_valid(&url_blocklist)?;
|
let parsed_urls = check_urls_are_valid(&url_blocklist)?;
|
||||||
LocalSiteUrlBlocklist::replace(&mut context.pool(), parsed_urls).await?;
|
LocalSiteUrlBlocklist::replace(&mut context.pool(), parsed_urls).await?;
|
||||||
|
|
|
@ -21,8 +21,9 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::PersonAggregates,
|
aggregates::structs::PersonAggregates,
|
||||||
newtypes::{InstanceId, OAuthProviderId},
|
newtypes::{InstanceId, OAuthProviderId, SiteId},
|
||||||
source::{
|
source::{
|
||||||
|
actor_language::SiteLanguage,
|
||||||
captcha_answer::{CaptchaAnswer, CheckCaptchaAnswer},
|
captcha_answer::{CaptchaAnswer, CheckCaptchaAnswer},
|
||||||
language::Language,
|
language::Language,
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
|
@ -44,9 +45,10 @@ use lemmy_utils::{
|
||||||
validation::is_valid_actor_name,
|
validation::is_valid_actor_name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, sync::LazyLock};
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
@ -145,7 +147,13 @@ pub async fn register(
|
||||||
..LocalUserInsertForm::new(inserted_person.id, Some(data.password.to_string()))
|
..LocalUserInsertForm::new(inserted_person.id, Some(data.password.to_string()))
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_local_user = create_local_user(&context, language_tags, &local_user_form).await?;
|
let inserted_local_user = create_local_user(
|
||||||
|
&context,
|
||||||
|
language_tags,
|
||||||
|
&local_user_form,
|
||||||
|
local_site.site_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if local_site.site_setup && require_registration_application {
|
if local_site.site_setup && require_registration_application {
|
||||||
if let Some(answer) = data.answer.clone() {
|
if let Some(answer) = data.answer.clone() {
|
||||||
|
@ -218,6 +226,11 @@ pub async fn authenticate_with_oauth(
|
||||||
Err(LemmyErrorType::OauthAuthorizationInvalid)?
|
Err(LemmyErrorType::OauthAuthorizationInvalid)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate the PKCE challenge
|
||||||
|
if let Some(code_verifier) = &data.pkce_code_verifier {
|
||||||
|
check_code_verifier(code_verifier)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the OAUTH provider and make sure it's enabled
|
// Fetch the OAUTH provider and make sure it's enabled
|
||||||
let oauth_provider_id = data.oauth_provider_id;
|
let oauth_provider_id = data.oauth_provider_id;
|
||||||
let oauth_provider = OAuthProvider::read(&mut context.pool(), oauth_provider_id)
|
let oauth_provider = OAuthProvider::read(&mut context.pool(), oauth_provider_id)
|
||||||
|
@ -229,8 +242,13 @@ pub async fn authenticate_with_oauth(
|
||||||
return Err(LemmyErrorType::OauthAuthorizationInvalid)?;
|
return Err(LemmyErrorType::OauthAuthorizationInvalid)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_response =
|
let token_response = oauth_request_access_token(
|
||||||
oauth_request_access_token(&context, &oauth_provider, &data.code, redirect_uri.as_str())
|
&context,
|
||||||
|
&oauth_provider,
|
||||||
|
&data.code,
|
||||||
|
data.pkce_code_verifier.as_deref(),
|
||||||
|
redirect_uri.as_str(),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let user_info = oidc_get_user_info(
|
let user_info = oidc_get_user_info(
|
||||||
|
@ -358,7 +376,13 @@ pub async fn authenticate_with_oauth(
|
||||||
..LocalUserInsertForm::new(person.id, None)
|
..LocalUserInsertForm::new(person.id, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
local_user = create_local_user(&context, language_tags, &local_user_form).await?;
|
local_user = create_local_user(
|
||||||
|
&context,
|
||||||
|
language_tags,
|
||||||
|
&local_user_form,
|
||||||
|
local_site.site_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// Create the oauth account
|
// Create the oauth account
|
||||||
let oauth_account_form =
|
let oauth_account_form =
|
||||||
|
@ -449,15 +473,23 @@ async fn create_local_user(
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
language_tags: Vec<String>,
|
language_tags: Vec<String>,
|
||||||
local_user_form: &LocalUserInsertForm,
|
local_user_form: &LocalUserInsertForm,
|
||||||
|
local_site_id: SiteId,
|
||||||
) -> Result<LocalUser, LemmyError> {
|
) -> Result<LocalUser, LemmyError> {
|
||||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||||
// use hashset to avoid duplicates
|
// use hashset to avoid duplicates
|
||||||
let mut language_ids = HashSet::new();
|
let mut language_ids = HashSet::new();
|
||||||
|
|
||||||
|
// Enable languages from `Accept-Language` header
|
||||||
for l in language_tags {
|
for l in language_tags {
|
||||||
if let Some(found) = all_languages.iter().find(|all| all.code == l) {
|
if let Some(found) = all_languages.iter().find(|all| all.code == l) {
|
||||||
language_ids.insert(found.id);
|
language_ids.insert(found.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable site languages. Ignored if all languages are enabled.
|
||||||
|
let discussion_languages = SiteLanguage::read(&mut context.pool(), local_site_id).await?;
|
||||||
|
language_ids.extend(discussion_languages);
|
||||||
|
|
||||||
let language_ids = language_ids.into_iter().collect();
|
let language_ids = language_ids.into_iter().collect();
|
||||||
|
|
||||||
let inserted_local_user =
|
let inserted_local_user =
|
||||||
|
@ -512,20 +544,27 @@ async fn oauth_request_access_token(
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
oauth_provider: &OAuthProvider,
|
oauth_provider: &OAuthProvider,
|
||||||
code: &str,
|
code: &str,
|
||||||
|
pkce_code_verifier: Option<&str>,
|
||||||
redirect_uri: &str,
|
redirect_uri: &str,
|
||||||
) -> LemmyResult<TokenResponse> {
|
) -> LemmyResult<TokenResponse> {
|
||||||
|
let mut form = vec![
|
||||||
|
("client_id", &*oauth_provider.client_id),
|
||||||
|
("client_secret", &*oauth_provider.client_secret),
|
||||||
|
("code", code),
|
||||||
|
("grant_type", "authorization_code"),
|
||||||
|
("redirect_uri", redirect_uri),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(code_verifier) = pkce_code_verifier {
|
||||||
|
form.push(("code_verifier", code_verifier));
|
||||||
|
}
|
||||||
|
|
||||||
// Request an Access Token from the OAUTH provider
|
// Request an Access Token from the OAUTH provider
|
||||||
let response = context
|
let response = context
|
||||||
.client()
|
.client()
|
||||||
.post(oauth_provider.token_endpoint.as_str())
|
.post(oauth_provider.token_endpoint.as_str())
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.form(&[
|
.form(&form[..])
|
||||||
("grant_type", "authorization_code"),
|
|
||||||
("code", code),
|
|
||||||
("redirect_uri", redirect_uri),
|
|
||||||
("client_id", &oauth_provider.client_id),
|
|
||||||
("client_secret", &oauth_provider.client_secret),
|
|
||||||
])
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::OauthLoginFailed)?
|
.with_lemmy_type(LemmyErrorType::OauthLoginFailed)?
|
||||||
|
@ -575,3 +614,17 @@ fn read_user_info(user_info: &serde_json::Value, key: &str) -> LemmyResult<Strin
|
||||||
}
|
}
|
||||||
Err(LemmyErrorType::OauthLoginFailed)?
|
Err(LemmyErrorType::OauthLoginFailed)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
fn check_code_verifier(code_verifier: &str) -> LemmyResult<()> {
|
||||||
|
static VALID_CODE_VERIFIER_REGEX: LazyLock<Regex> =
|
||||||
|
LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9\-._~]{43,128}$").expect("compile regex"));
|
||||||
|
|
||||||
|
let check = VALID_CODE_VERIFIER_REGEX.is_match(code_verifier);
|
||||||
|
|
||||||
|
if check {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(LemmyErrorType::InvalidCodeVerifier.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod create;
|
pub mod create;
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
|
pub mod my_user;
|
||||||
|
|
45
crates/api_crud/src/user/my_user.rs
Normal file
45
crates/api_crud/src/user/my_user.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use actix_web::web::{Data, Json};
|
||||||
|
use lemmy_api_common::{context::LemmyContext, site::MyUserInfo, utils::check_user_valid};
|
||||||
|
use lemmy_db_schema::source::{
|
||||||
|
actor_language::LocalUserLanguage,
|
||||||
|
community_block::CommunityBlock,
|
||||||
|
instance_block::InstanceBlock,
|
||||||
|
person_block::PersonBlock,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView};
|
||||||
|
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn get_my_user(
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> LemmyResult<Json<MyUserInfo>> {
|
||||||
|
check_user_valid(&local_user_view.person)?;
|
||||||
|
|
||||||
|
// Build the local user with parallel queries and add it to site response
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let local_user_id = local_user_view.local_user.id;
|
||||||
|
let pool = &mut context.pool();
|
||||||
|
|
||||||
|
let (follows, community_blocks, instance_blocks, person_blocks, moderates, discussion_languages) =
|
||||||
|
lemmy_db_schema::try_join_with_pool!(pool => (
|
||||||
|
|pool| CommunityFollowerView::for_person(pool, person_id),
|
||||||
|
|pool| CommunityBlock::for_person(pool, person_id),
|
||||||
|
|pool| InstanceBlock::for_person(pool, person_id),
|
||||||
|
|pool| PersonBlock::for_person(pool, person_id),
|
||||||
|
|pool| CommunityModeratorView::for_person(pool, person_id, Some(&local_user_view.local_user)),
|
||||||
|
|pool| LocalUserLanguage::read(pool, local_user_id)
|
||||||
|
))
|
||||||
|
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||||
|
|
||||||
|
Ok(Json(MyUserInfo {
|
||||||
|
local_user_view: local_user_view.clone(),
|
||||||
|
follows,
|
||||||
|
moderates,
|
||||||
|
community_blocks,
|
||||||
|
instance_blocks,
|
||||||
|
person_blocks,
|
||||||
|
discussion_languages,
|
||||||
|
}))
|
||||||
|
}
|
15
crates/apub/assets/pleroma/objects/chat_message.json
Normal file
15
crates/apub/assets/pleroma/objects/chat_message.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://queer.hacktivis.me/schemas/litepub-0.1.jsonld",
|
||||||
|
{
|
||||||
|
"@language": "und"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributedTo": "https://queer.hacktivis.me/users/lanodan",
|
||||||
|
"content": "Hi!",
|
||||||
|
"id": "https://queer.hacktivis.me/objects/2",
|
||||||
|
"published": "2020-02-12T14:08:20Z",
|
||||||
|
"to": ["https://enterprise.lemmy.ml/u/picard"],
|
||||||
|
"type": "ChatMessage"
|
||||||
|
}
|
|
@ -36,7 +36,7 @@ use lemmy_db_schema::{
|
||||||
CommunityPersonBan,
|
CommunityPersonBan,
|
||||||
CommunityPersonBanForm,
|
CommunityPersonBanForm,
|
||||||
},
|
},
|
||||||
moderator::{ModBan, ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm},
|
mod_log::moderator::{ModBan, ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm},
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
},
|
},
|
||||||
traits::{Bannable, Crud, Followable},
|
traits::{Bannable, Crud, Followable},
|
||||||
|
|
|
@ -27,7 +27,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
community::{CommunityPersonBan, CommunityPersonBanForm},
|
community::{CommunityPersonBan, CommunityPersonBanForm},
|
||||||
moderator::{ModBan, ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm},
|
mod_log::moderator::{ModBan, ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm},
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
},
|
},
|
||||||
traits::{Bannable, Crud},
|
traits::{Bannable, Crud},
|
||||||
|
|
|
@ -31,7 +31,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
mod_log::moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||||
person::Person,
|
person::Person,
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
|
|
|
@ -27,7 +27,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
community::{Community, CommunityModerator, CommunityModeratorForm},
|
community::{Community, CommunityModerator, CommunityModeratorForm},
|
||||||
moderator::{ModAddCommunity, ModAddCommunityForm},
|
mod_log::moderator::{ModAddCommunity, ModAddCommunityForm},
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
traits::{Crud, Joinable},
|
traits::{Crud, Joinable},
|
||||||
|
|
|
@ -27,7 +27,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
community::Community,
|
community::Community,
|
||||||
moderator::{ModLockPost, ModLockPostForm},
|
mod_log::moderator::{ModLockPost, ModLockPostForm},
|
||||||
person::Person,
|
person::Person,
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
},
|
},
|
||||||
|
|
|
@ -99,8 +99,11 @@ impl CreateOrUpdateNote {
|
||||||
inboxes.add_inbox(person.shared_inbox_or_inbox());
|
inboxes.add_inbox(person.shared_inbox_or_inbox());
|
||||||
}
|
}
|
||||||
|
|
||||||
let activity =
|
// AnnouncableActivities doesnt contain Comment activity but only NoteWrapper,
|
||||||
AnnouncableActivities::CreateOrUpdateNoteWrapper(from_value(to_value(create_or_update)?)?);
|
// to be able to handle both comment and private message. So to send this out we need
|
||||||
|
// to convert this to NoteWrapper, by serializing and then deserializing again.
|
||||||
|
let converted = from_value(to_value(create_or_update)?)?;
|
||||||
|
let activity = AnnouncableActivities::CreateOrUpdateNoteWrapper(converted);
|
||||||
send_activity_in_community(activity, &person, &community, inboxes, false, &context).await
|
send_activity_in_community(activity, &person, &community, inboxes, false, &context).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::{community::ApubCommunity, note_wrapper::is_public},
|
objects::community::ApubCommunity,
|
||||||
protocol::{
|
protocol::{
|
||||||
activities::create_or_update::{
|
activities::create_or_update::{
|
||||||
note::CreateOrUpdateNote,
|
note::CreateOrUpdateNote,
|
||||||
|
@ -11,10 +11,13 @@ use crate::{
|
||||||
};
|
};
|
||||||
use activitypub_federation::{config::Data, traits::ActivityHandler};
|
use activitypub_federation::{config::Data, traits::ActivityHandler};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_utils::error::{FederationError, LemmyError, LemmyResult};
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
use serde_json::{from_value, to_value};
|
use serde_json::{from_value, to_value};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
/// In Activitypub, both private messages and comments are represented by `type: Note` which
|
||||||
|
/// makes it difficult to distinguish them. This wrapper handles receiving of both types, and
|
||||||
|
/// routes them to the correct handler.
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ActivityHandler for CreateOrUpdateNoteWrapper {
|
impl ActivityHandler for CreateOrUpdateNoteWrapper {
|
||||||
type DataType = LemmyContext;
|
type DataType = LemmyContext;
|
||||||
|
@ -29,38 +32,43 @@ impl ActivityHandler for CreateOrUpdateNoteWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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<()> {
|
||||||
let val = to_value(self)?;
|
// Do everything in receive to avoid extra checks.
|
||||||
if is_public(&self.to, &self.cc) {
|
|
||||||
CreateOrUpdateNote::verify(&from_value(val)?, context).await?;
|
|
||||||
} else {
|
|
||||||
CreateOrUpdatePrivateMessage::verify(&from_value(val)?, context).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn receive(self, context: &Data<Self::DataType>) -> LemmyResult<()> {
|
async fn receive(self, context: &Data<Self::DataType>) -> LemmyResult<()> {
|
||||||
let is_public = is_public(&self.to, &self.cc);
|
// Use serde to convert NoteWrapper either into Comment or PrivateMessage,
|
||||||
|
// depending on conditions below. This works because NoteWrapper keeps all
|
||||||
|
// additional data in field `other: Map<String, Value>`.
|
||||||
let val = to_value(self)?;
|
let val = to_value(self)?;
|
||||||
if is_public {
|
|
||||||
CreateOrUpdateNote::receive(from_value(val)?, context).await?;
|
// Convert self to a comment and get the community. If the conversion is
|
||||||
} else {
|
// successful and a community is returned, this is a comment.
|
||||||
CreateOrUpdatePrivateMessage::receive(from_value(val)?, context).await?;
|
let comment = from_value::<CreateOrUpdateNote>(val.clone());
|
||||||
|
if let Ok(comment) = comment {
|
||||||
|
if comment.community(context).await.is_ok() {
|
||||||
|
CreateOrUpdateNote::verify(&comment, context).await?;
|
||||||
|
CreateOrUpdateNote::receive(comment, context).await?;
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the previous checks failed, we are dealing with a private message.
|
||||||
|
let private_message = from_value(val)?;
|
||||||
|
CreateOrUpdatePrivateMessage::verify(&private_message, context).await?;
|
||||||
|
CreateOrUpdatePrivateMessage::receive(private_message, context).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl InCommunity for CreateOrUpdateNoteWrapper {
|
impl InCommunity for CreateOrUpdateNoteWrapper {
|
||||||
#[tracing::instrument(skip(self, context))]
|
|
||||||
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
|
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
|
||||||
if is_public(&self.to, &self.cc) {
|
// Same logic as in receive. In case this is a private message, an error is returned.
|
||||||
let comment: CreateOrUpdateNote = from_value(to_value(self)?)?;
|
let val = to_value(self)?;
|
||||||
|
let comment: CreateOrUpdateNote = from_value(val.clone())?;
|
||||||
comment.community(context).await
|
comment.community(context).await
|
||||||
} else {
|
|
||||||
Err(FederationError::ObjectIsNotPublic.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ use lemmy_db_schema::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
comment_report::CommentReport,
|
comment_report::CommentReport,
|
||||||
community::{Community, CommunityUpdateForm},
|
community::{Community, CommunityUpdateForm},
|
||||||
moderator::{
|
mod_log::moderator::{
|
||||||
ModRemoveComment,
|
ModRemoveComment,
|
||||||
ModRemoveCommentForm,
|
ModRemoveCommentForm,
|
||||||
ModRemoveCommunity,
|
ModRemoveCommunity,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
community::{Community, CommunityUpdateForm},
|
community::{Community, CommunityUpdateForm},
|
||||||
moderator::{
|
mod_log::moderator::{
|
||||||
ModRemoveComment,
|
ModRemoveComment,
|
||||||
ModRemoveCommentForm,
|
ModRemoveCommentForm,
|
||||||
ModRemoveCommunity,
|
ModRemoveCommunity,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use actix_web::web::{Json, Query};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{GetPersonDetails, GetPersonDetailsResponse},
|
person::{GetPersonDetails, GetPersonDetailsResponse},
|
||||||
utils::{check_private_instance, read_site_for_actor},
|
utils::{check_private_instance, is_admin, read_site_for_actor},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::person::Person, utils::post_to_comment_sort_type};
|
use lemmy_db_schema::{source::person::Person, utils::post_to_comment_sort_type};
|
||||||
use lemmy_db_views::{
|
use lemmy_db_views::{
|
||||||
|
@ -45,7 +45,11 @@ pub async fn read_person(
|
||||||
|
|
||||||
// You don't need to return settings for the user, since this comes back with GetSite
|
// You don't need to return settings for the user, since this comes back with GetSite
|
||||||
// `my_user`
|
// `my_user`
|
||||||
let person_view = PersonView::read(&mut context.pool(), person_details_id).await?;
|
let is_admin = local_user_view
|
||||||
|
.as_ref()
|
||||||
|
.map(|l| is_admin(l).is_ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let person_view = PersonView::read(&mut context.pool(), person_details_id, is_admin).await?;
|
||||||
|
|
||||||
let sort = data.sort;
|
let sort = data.sort;
|
||||||
let page = data.page;
|
let page = data.page;
|
||||||
|
|
|
@ -60,7 +60,7 @@ async fn convert_response(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SearchableObjects::PersonOrCommunity(pc) => match *pc {
|
SearchableObjects::PersonOrCommunity(pc) => match *pc {
|
||||||
UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id).await?),
|
UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id, is_admin).await?),
|
||||||
UserOrCommunity::Community(c) => {
|
UserOrCommunity::Community(c) => {
|
||||||
res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?)
|
res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,7 +322,7 @@ pub(crate) mod tests {
|
||||||
CommunityFollowerState,
|
CommunityFollowerState,
|
||||||
CommunityInsertForm,
|
CommunityInsertForm,
|
||||||
},
|
},
|
||||||
local_user::LocalUser,
|
person::Person,
|
||||||
},
|
},
|
||||||
traits::{Crud, Followable},
|
traits::{Crud, Followable},
|
||||||
};
|
};
|
||||||
|
@ -376,8 +376,8 @@ pub(crate) mod tests {
|
||||||
assert_eq!(follows.len(), 1);
|
assert_eq!(follows.len(), 1);
|
||||||
assert_eq!(follows[0].community.actor_id, community.actor_id);
|
assert_eq!(follows[0].community.actor_id, community.actor_id);
|
||||||
|
|
||||||
LocalUser::delete(pool, export_user.local_user.id).await?;
|
Person::delete(pool, export_user.person.id).await?;
|
||||||
LocalUser::delete(pool, import_user.local_user.id).await?;
|
Person::delete(pool, import_user.person.id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -412,8 +412,8 @@ pub(crate) mod tests {
|
||||||
Some(LemmyErrorType::TooManyItems)
|
Some(LemmyErrorType::TooManyItems)
|
||||||
);
|
);
|
||||||
|
|
||||||
LocalUser::delete(pool, export_user.local_user.id).await?;
|
Person::delete(pool, export_user.person.id).await?;
|
||||||
LocalUser::delete(pool, import_user.local_user.id).await?;
|
Person::delete(pool, import_user.person.id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ pub async fn markdown_rewrite_remote_links(
|
||||||
let mut local_url = local_url.to_string();
|
let mut local_url = local_url.to_string();
|
||||||
// restore title
|
// restore title
|
||||||
if let Some(extra) = extra {
|
if let Some(extra) = extra {
|
||||||
local_url = format!("{local_url} {extra}");
|
local_url.push(' ');
|
||||||
|
local_url.push_str(extra);
|
||||||
}
|
}
|
||||||
src.replace_range(start..end, local_url.as_str());
|
src.replace_range(start..end, local_url.as_str());
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ use std::fmt::Debug;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
pub mod note_wrapper;
|
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
use super::comment::ApubComment;
|
|
||||||
use crate::{
|
|
||||||
objects::private_message::ApubPrivateMessage,
|
|
||||||
protocol::objects::note_wrapper::NoteWrapper,
|
|
||||||
};
|
|
||||||
use activitypub_federation::{config::Data, kinds::public, traits::Object};
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use lemmy_api_common::{context::LemmyContext, LemmyErrorType};
|
|
||||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
|
||||||
use serde_json::{from_value, to_value};
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
/// Private messages and public comments are quite awkward in Activitypub, because the json
|
|
||||||
/// format looks identical. They only way to differentiate them is to check for the presence
|
|
||||||
/// or absence of `https://www.w3.org/ns/activitystreams#Public` in `to` or `cc` which this
|
|
||||||
/// wrapper does.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct ApubNote {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl Object for ApubNote {
|
|
||||||
type DataType = LemmyContext;
|
|
||||||
type Kind = NoteWrapper;
|
|
||||||
type Error = LemmyError;
|
|
||||||
|
|
||||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
async fn read_from_id(
|
|
||||||
_object_id: Url,
|
|
||||||
_context: &Data<Self::DataType>,
|
|
||||||
) -> LemmyResult<Option<Self>> {
|
|
||||||
Err(LemmyErrorType::Unknown("not implemented".to_string()).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
async fn delete(self, _context: &Data<Self::DataType>) -> LemmyResult<()> {
|
|
||||||
Err(LemmyErrorType::Unknown("not implemented".to_string()).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn verify(
|
|
||||||
note: &NoteWrapper,
|
|
||||||
expected_domain: &Url,
|
|
||||||
context: &Data<LemmyContext>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
let val = to_value(note)?;
|
|
||||||
if is_public(¬e.to, ¬e.cc) {
|
|
||||||
ApubComment::verify(&from_value(val)?, expected_domain, context).await?;
|
|
||||||
} else {
|
|
||||||
ApubPrivateMessage::verify(&from_value(val)?, expected_domain, context).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn from_json(note: NoteWrapper, context: &Data<LemmyContext>) -> LemmyResult<ApubNote> {
|
|
||||||
let is_public = is_public(¬e.to, ¬e.cc);
|
|
||||||
let val = to_value(note)?;
|
|
||||||
if is_public {
|
|
||||||
ApubComment::from_json(from_value(val)?, context).await?;
|
|
||||||
} else {
|
|
||||||
ApubPrivateMessage::from_json(from_value(val)?, context).await?;
|
|
||||||
}
|
|
||||||
Ok(ApubNote {})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn into_json(self, _context: &Data<Self::DataType>) -> LemmyResult<NoteWrapper> {
|
|
||||||
Err(LemmyErrorType::Unknown("not implemented".to_string()).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_public(to: &Option<Vec<Url>>, cc: &Option<Vec<Url>>) -> bool {
|
|
||||||
if let Some(to) = to {
|
|
||||||
if to.contains(&public()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(cc) = cc {
|
|
||||||
if cc.contains(&public()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
|
@ -242,4 +242,24 @@ mod tests {
|
||||||
cleanup(data, &context).await?;
|
cleanup(data, &context).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_parse_pleroma_pm() -> LemmyResult<()> {
|
||||||
|
let context = LemmyContext::init_test_context().await;
|
||||||
|
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621")?;
|
||||||
|
let data = prepare_comment_test(&url, &context).await?;
|
||||||
|
let pleroma_url = Url::parse("https://queer.hacktivis.me/objects/2")?;
|
||||||
|
let json = file_to_json_object("assets/pleroma/objects/chat_message.json")?;
|
||||||
|
ApubPrivateMessage::verify(&json, &pleroma_url, &context).await?;
|
||||||
|
let pm = ApubPrivateMessage::from_json(json, &context).await?;
|
||||||
|
|
||||||
|
assert_eq!(pm.ap_id, pleroma_url.into());
|
||||||
|
assert_eq!(pm.content.len(), 3);
|
||||||
|
assert_eq!(context.request_count(), 0);
|
||||||
|
|
||||||
|
DbPrivateMessage::delete(&mut context.pool(), pm.id).await?;
|
||||||
|
cleanup(data, &context).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::protocol::objects::note_wrapper::NoteWrapper;
|
use activitypub_federation::kinds::object::NoteType;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -6,11 +6,21 @@ use url::Url;
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreateOrUpdateNoteWrapper {
|
pub struct CreateOrUpdateNoteWrapper {
|
||||||
object: NoteWrapper,
|
pub(crate) object: NoteWrapper,
|
||||||
pub(crate) id: Url,
|
pub(crate) id: Url,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) to: Vec<Url>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) cc: Vec<Url>,
|
||||||
pub(crate) actor: Url,
|
pub(crate) actor: Url,
|
||||||
pub(crate) to: Option<Vec<Url>>,
|
#[serde(flatten)]
|
||||||
pub(crate) cc: Option<Vec<Url>>,
|
other: Map<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct NoteWrapper {
|
||||||
|
pub(crate) r#type: NoteType,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
other: Map<String, Value>,
|
other: Map<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,6 @@ pub(crate) mod tests {
|
||||||
// parse file into hashmap, which ensures that every field is included
|
// parse file into hashmap, which ensures that every field is included
|
||||||
let raw = file_to_json_object::<HashMap<String, serde_json::Value>>(path)?;
|
let raw = file_to_json_object::<HashMap<String, serde_json::Value>>(path)?;
|
||||||
// assert that all fields are identical, otherwise print diff
|
// assert that all fields are identical, otherwise print diff
|
||||||
//dbg!(&parsed, &raw);
|
|
||||||
assert_json_include!(actual: &parsed, expected: raw);
|
assert_json_include!(actual: &parsed, expected: raw);
|
||||||
Ok(parsed)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ use url::Url;
|
||||||
pub(crate) mod group;
|
pub(crate) mod group;
|
||||||
pub(crate) mod instance;
|
pub(crate) mod instance;
|
||||||
pub(crate) mod note;
|
pub(crate) mod note;
|
||||||
pub(crate) mod note_wrapper;
|
|
||||||
pub(crate) mod page;
|
pub(crate) mod page;
|
||||||
pub(crate) mod person;
|
pub(crate) mod person;
|
||||||
pub(crate) mod private_message;
|
pub(crate) mod private_message;
|
||||||
|
@ -102,8 +101,8 @@ impl LanguageTag {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::note_wrapper::NoteWrapper;
|
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
|
activities::create_or_update::note_wrapper::NoteWrapper,
|
||||||
objects::{
|
objects::{
|
||||||
group::Group,
|
group::Group,
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
use activitypub_federation::kinds::object::NoteType;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::{Map, Value};
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub(crate) struct NoteWrapper {
|
|
||||||
pub(crate) r#type: NoteType,
|
|
||||||
pub(crate) to: Option<Vec<Url>>,
|
|
||||||
pub(crate) cc: Option<Vec<Url>>,
|
|
||||||
#[serde(flatten)]
|
|
||||||
other: Map<String, Value>,
|
|
||||||
}
|
|
|
@ -34,7 +34,7 @@ pub struct PrivateMessage {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum PrivateMessageType {
|
pub enum PrivateMessageType {
|
||||||
/// For compatibility with Lemmy 0.19 and earlier
|
/// Deprecated, for compatibility with Lemmy 0.19 and earlier
|
||||||
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
|
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
Note,
|
Note,
|
||||||
|
|
|
@ -1,60 +1,51 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
schema::federation_allowlist,
|
newtypes::InstanceId,
|
||||||
|
schema::{admin_allow_instance, federation_allowlist},
|
||||||
source::{
|
source::{
|
||||||
federation_allowlist::{FederationAllowList, FederationAllowListForm},
|
federation_allowlist::{FederationAllowList, FederationAllowListForm},
|
||||||
instance::Instance,
|
mod_log::admin::{AdminAllowInstance, AdminAllowInstanceForm},
|
||||||
},
|
},
|
||||||
utils::{get_conn, DbPool},
|
utils::{get_conn, DbPool},
|
||||||
};
|
};
|
||||||
use diesel::{dsl::insert_into, result::Error};
|
use diesel::{delete, dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||||
use diesel_async::{AsyncPgConnection, RunQueryDsl};
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
impl AdminAllowInstance {
|
||||||
|
pub async fn insert(pool: &mut DbPool<'_>, form: &AdminAllowInstanceForm) -> Result<(), Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(admin_allow_instance::table)
|
||||||
|
.values(form)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FederationAllowList {
|
impl FederationAllowList {
|
||||||
pub async fn replace(pool: &mut DbPool<'_>, list_opt: Option<Vec<String>>) -> Result<(), Error> {
|
pub async fn allow(pool: &mut DbPool<'_>, form: &FederationAllowListForm) -> Result<(), Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
conn
|
|
||||||
.build_transaction()
|
|
||||||
.run(|conn| {
|
|
||||||
Box::pin(async move {
|
|
||||||
if let Some(list) = list_opt {
|
|
||||||
Self::clear(conn).await?;
|
|
||||||
|
|
||||||
for domain in list {
|
|
||||||
// Upsert all of these as instances
|
|
||||||
let instance = Instance::read_or_create(&mut conn.into(), domain).await?;
|
|
||||||
|
|
||||||
let form = FederationAllowListForm {
|
|
||||||
instance_id: instance.id,
|
|
||||||
updated: None,
|
|
||||||
};
|
|
||||||
insert_into(federation_allowlist::table)
|
insert_into(federation_allowlist::table)
|
||||||
.values(form)
|
.values(form)
|
||||||
.get_result::<Self>(conn)
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub async fn unallow(pool: &mut DbPool<'_>, instance_id_: InstanceId) -> Result<(), Error> {
|
||||||
|
use federation_allowlist::dsl::instance_id;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
delete(federation_allowlist::table.filter(instance_id.eq(instance_id_)))
|
||||||
|
.execute(conn)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}) as _
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clear(conn: &mut AsyncPgConnection) -> Result<usize, Error> {
|
|
||||||
diesel::delete(federation_allowlist::table)
|
|
||||||
.execute(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::{
|
use super::*;
|
||||||
source::{federation_allowlist::FederationAllowList, instance::Instance},
|
use crate::{source::instance::Instance, utils::build_db_pool_for_tests};
|
||||||
utils::build_db_pool_for_tests,
|
|
||||||
};
|
|
||||||
use diesel::result::Error;
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
|
@ -63,31 +54,33 @@ mod tests {
|
||||||
async fn test_allowlist_insert_and_clear() -> Result<(), Error> {
|
async fn test_allowlist_insert_and_clear() -> Result<(), Error> {
|
||||||
let pool = &build_db_pool_for_tests();
|
let pool = &build_db_pool_for_tests();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let domains = vec![
|
let instances = vec![
|
||||||
"tld1.xyz".to_string(),
|
Instance::read_or_create(pool, "tld1.xyz".to_string()).await?,
|
||||||
"tld2.xyz".to_string(),
|
Instance::read_or_create(pool, "tld2.xyz".to_string()).await?,
|
||||||
"tld3.xyz".to_string(),
|
Instance::read_or_create(pool, "tld3.xyz".to_string()).await?,
|
||||||
];
|
];
|
||||||
|
let forms: Vec<_> = instances
|
||||||
|
.iter()
|
||||||
|
.map(|i| FederationAllowListForm {
|
||||||
|
instance_id: i.id,
|
||||||
|
updated: None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let allowed = Some(domains.clone());
|
for f in &forms {
|
||||||
|
FederationAllowList::allow(pool, f).await?;
|
||||||
FederationAllowList::replace(pool, allowed).await?;
|
}
|
||||||
|
|
||||||
let allows = Instance::allowlist(pool).await?;
|
let allows = Instance::allowlist(pool).await?;
|
||||||
let allows_domains = allows
|
|
||||||
.iter()
|
|
||||||
.map(|i| i.domain.clone())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
assert_eq!(3, allows.len());
|
assert_eq!(3, allows.len());
|
||||||
assert_eq!(domains, allows_domains);
|
assert_eq!(instances, allows);
|
||||||
|
|
||||||
// Now test clearing them via Some(empty vec)
|
// Now test clearing them
|
||||||
let clear_allows = Some(Vec::new());
|
for f in forms {
|
||||||
|
FederationAllowList::unallow(pool, f.instance_id).await?;
|
||||||
FederationAllowList::replace(pool, clear_allows).await?;
|
}
|
||||||
let allows = Instance::allowlist(pool).await?;
|
let allows = Instance::allowlist(pool).await?;
|
||||||
|
|
||||||
assert_eq!(0, allows.len());
|
assert_eq!(0, allows.len());
|
||||||
|
|
||||||
Instance::delete_all(pool).await?;
|
Instance::delete_all(pool).await?;
|
||||||
|
|
|
@ -1,49 +1,42 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
schema::federation_blocklist,
|
newtypes::InstanceId,
|
||||||
|
schema::{admin_block_instance, federation_blocklist},
|
||||||
source::{
|
source::{
|
||||||
federation_blocklist::{FederationBlockList, FederationBlockListForm},
|
federation_blocklist::{FederationBlockList, FederationBlockListForm},
|
||||||
instance::Instance,
|
mod_log::admin::{AdminBlockInstance, AdminBlockInstanceForm},
|
||||||
},
|
},
|
||||||
utils::{get_conn, DbPool},
|
utils::{get_conn, DbPool},
|
||||||
};
|
};
|
||||||
use diesel::{dsl::insert_into, result::Error};
|
use diesel::{delete, dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||||
use diesel_async::{AsyncPgConnection, RunQueryDsl};
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
impl AdminBlockInstance {
|
||||||
|
pub async fn insert(pool: &mut DbPool<'_>, form: &AdminBlockInstanceForm) -> Result<(), Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(admin_block_instance::table)
|
||||||
|
.values(form)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FederationBlockList {
|
impl FederationBlockList {
|
||||||
pub async fn replace(pool: &mut DbPool<'_>, list_opt: Option<Vec<String>>) -> Result<(), Error> {
|
pub async fn block(pool: &mut DbPool<'_>, form: &FederationBlockListForm) -> Result<(), Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
conn
|
|
||||||
.build_transaction()
|
|
||||||
.run(|conn| {
|
|
||||||
Box::pin(async move {
|
|
||||||
if let Some(list) = list_opt {
|
|
||||||
Self::clear(conn).await?;
|
|
||||||
|
|
||||||
for domain in list {
|
|
||||||
// Upsert all of these as instances
|
|
||||||
let instance = Instance::read_or_create(&mut conn.into(), domain).await?;
|
|
||||||
|
|
||||||
let form = FederationBlockListForm {
|
|
||||||
instance_id: instance.id,
|
|
||||||
updated: None,
|
|
||||||
};
|
|
||||||
insert_into(federation_blocklist::table)
|
insert_into(federation_blocklist::table)
|
||||||
.values(form)
|
.values(form)
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}) as _
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn clear(conn: &mut AsyncPgConnection) -> Result<usize, Error> {
|
|
||||||
diesel::delete(federation_blocklist::table)
|
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub async fn unblock(pool: &mut DbPool<'_>, instance_id_: InstanceId) -> Result<(), Error> {
|
||||||
|
use federation_blocklist::dsl::instance_id;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
delete(federation_blocklist::table.filter(instance_id.eq(instance_id_)))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub mod local_site_url_blocklist;
|
||||||
pub mod local_user;
|
pub mod local_user;
|
||||||
pub mod local_user_vote_display_mode;
|
pub mod local_user_vote_display_mode;
|
||||||
pub mod login_token;
|
pub mod login_token;
|
||||||
pub mod moderator;
|
pub mod mod_log;
|
||||||
pub mod oauth_account;
|
pub mod oauth_account;
|
||||||
pub mod oauth_provider;
|
pub mod oauth_provider;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
|
@ -35,4 +35,5 @@ pub mod private_message_report;
|
||||||
pub mod registration_application;
|
pub mod registration_application;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
pub mod tag;
|
||||||
pub mod tagline;
|
pub mod tagline;
|
||||||
|
|
132
crates/db_schema/src/impls/mod_log/admin.rs
Normal file
132
crates/db_schema/src/impls/mod_log/admin.rs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
use crate::{
|
||||||
|
source::mod_log::admin::{
|
||||||
|
AdminPurgeComment,
|
||||||
|
AdminPurgeCommentForm,
|
||||||
|
AdminPurgeCommunity,
|
||||||
|
AdminPurgeCommunityForm,
|
||||||
|
AdminPurgePerson,
|
||||||
|
AdminPurgePersonForm,
|
||||||
|
AdminPurgePost,
|
||||||
|
AdminPurgePostForm,
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
|
utils::{get_conn, DbPool},
|
||||||
|
};
|
||||||
|
use diesel::{dsl::insert_into, result::Error, QueryDsl};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Crud for AdminPurgePerson {
|
||||||
|
type InsertForm = AdminPurgePersonForm;
|
||||||
|
type UpdateForm = AdminPurgePersonForm;
|
||||||
|
type IdType = i32;
|
||||||
|
|
||||||
|
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_person::dsl::admin_purge_person;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(admin_purge_person)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
from_id: i32,
|
||||||
|
form: &Self::InsertForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_person::dsl::admin_purge_person;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
diesel::update(admin_purge_person.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Crud for AdminPurgeCommunity {
|
||||||
|
type InsertForm = AdminPurgeCommunityForm;
|
||||||
|
type UpdateForm = AdminPurgeCommunityForm;
|
||||||
|
type IdType = i32;
|
||||||
|
|
||||||
|
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_community::dsl::admin_purge_community;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(admin_purge_community)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
from_id: i32,
|
||||||
|
form: &Self::InsertForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_community::dsl::admin_purge_community;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
diesel::update(admin_purge_community.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Crud for AdminPurgePost {
|
||||||
|
type InsertForm = AdminPurgePostForm;
|
||||||
|
type UpdateForm = AdminPurgePostForm;
|
||||||
|
type IdType = i32;
|
||||||
|
|
||||||
|
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_post::dsl::admin_purge_post;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(admin_purge_post)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
from_id: i32,
|
||||||
|
form: &Self::InsertForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_post::dsl::admin_purge_post;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
diesel::update(admin_purge_post.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Crud for AdminPurgeComment {
|
||||||
|
type InsertForm = AdminPurgeCommentForm;
|
||||||
|
type UpdateForm = AdminPurgeCommentForm;
|
||||||
|
type IdType = i32;
|
||||||
|
|
||||||
|
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_comment::dsl::admin_purge_comment;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(admin_purge_comment)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
from_id: i32,
|
||||||
|
form: &Self::InsertForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::admin_purge_comment::dsl::admin_purge_comment;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
diesel::update(admin_purge_comment.find(from_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
2
crates/db_schema/src/impls/mod_log/mod.rs
Normal file
2
crates/db_schema/src/impls/mod_log/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod admin;
|
||||||
|
pub mod moderator;
|
|
@ -1,13 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
source::moderator::{
|
source::mod_log::moderator::{
|
||||||
AdminPurgeComment,
|
|
||||||
AdminPurgeCommentForm,
|
|
||||||
AdminPurgeCommunity,
|
|
||||||
AdminPurgeCommunityForm,
|
|
||||||
AdminPurgePerson,
|
|
||||||
AdminPurgePersonForm,
|
|
||||||
AdminPurgePost,
|
|
||||||
AdminPurgePostForm,
|
|
||||||
ModAdd,
|
ModAdd,
|
||||||
ModAddCommunity,
|
ModAddCommunity,
|
||||||
ModAddCommunityForm,
|
ModAddCommunityForm,
|
||||||
|
@ -376,157 +368,20 @@ impl Crud for ModAdd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Crud for AdminPurgePerson {
|
|
||||||
type InsertForm = AdminPurgePersonForm;
|
|
||||||
type UpdateForm = AdminPurgePersonForm;
|
|
||||||
type IdType = i32;
|
|
||||||
|
|
||||||
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_person::dsl::admin_purge_person;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
insert_into(admin_purge_person)
|
|
||||||
.values(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
from_id: i32,
|
|
||||||
form: &Self::InsertForm,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_person::dsl::admin_purge_person;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
diesel::update(admin_purge_person.find(from_id))
|
|
||||||
.set(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Crud for AdminPurgeCommunity {
|
|
||||||
type InsertForm = AdminPurgeCommunityForm;
|
|
||||||
type UpdateForm = AdminPurgeCommunityForm;
|
|
||||||
type IdType = i32;
|
|
||||||
|
|
||||||
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_community::dsl::admin_purge_community;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
insert_into(admin_purge_community)
|
|
||||||
.values(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
from_id: i32,
|
|
||||||
form: &Self::InsertForm,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_community::dsl::admin_purge_community;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
diesel::update(admin_purge_community.find(from_id))
|
|
||||||
.set(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Crud for AdminPurgePost {
|
|
||||||
type InsertForm = AdminPurgePostForm;
|
|
||||||
type UpdateForm = AdminPurgePostForm;
|
|
||||||
type IdType = i32;
|
|
||||||
|
|
||||||
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_post::dsl::admin_purge_post;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
insert_into(admin_purge_post)
|
|
||||||
.values(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
from_id: i32,
|
|
||||||
form: &Self::InsertForm,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_post::dsl::admin_purge_post;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
diesel::update(admin_purge_post.find(from_id))
|
|
||||||
.set(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Crud for AdminPurgeComment {
|
|
||||||
type InsertForm = AdminPurgeCommentForm;
|
|
||||||
type UpdateForm = AdminPurgeCommentForm;
|
|
||||||
type IdType = i32;
|
|
||||||
|
|
||||||
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_comment::dsl::admin_purge_comment;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
insert_into(admin_purge_comment)
|
|
||||||
.values(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update(
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
from_id: i32,
|
|
||||||
form: &Self::InsertForm,
|
|
||||||
) -> Result<Self, Error> {
|
|
||||||
use crate::schema::admin_purge_comment::dsl::admin_purge_comment;
|
|
||||||
let conn = &mut get_conn(pool).await?;
|
|
||||||
diesel::update(admin_purge_comment.find(from_id))
|
|
||||||
.set(form)
|
|
||||||
.get_result::<Self>(conn)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentInsertForm},
|
comment::{Comment, CommentInsertForm},
|
||||||
community::{Community, CommunityInsertForm},
|
community::{Community, CommunityInsertForm},
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
moderator::{
|
|
||||||
ModAdd,
|
|
||||||
ModAddCommunity,
|
|
||||||
ModAddCommunityForm,
|
|
||||||
ModAddForm,
|
|
||||||
ModBan,
|
|
||||||
ModBanForm,
|
|
||||||
ModBanFromCommunity,
|
|
||||||
ModBanFromCommunityForm,
|
|
||||||
ModFeaturePost,
|
|
||||||
ModFeaturePostForm,
|
|
||||||
ModLockPost,
|
|
||||||
ModLockPostForm,
|
|
||||||
ModRemoveComment,
|
|
||||||
ModRemoveCommentForm,
|
|
||||||
ModRemoveCommunity,
|
|
||||||
ModRemoveCommunityForm,
|
|
||||||
ModRemovePost,
|
|
||||||
ModRemovePostForm,
|
|
||||||
},
|
|
||||||
person::{Person, PersonInsertForm},
|
person::{Person, PersonInsertForm},
|
||||||
post::{Post, PostInsertForm},
|
post::{Post, PostInsertForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
|
||||||
utils::build_db_pool_for_tests,
|
utils::build_db_pool_for_tests,
|
||||||
};
|
};
|
||||||
use diesel::result::Error;
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
53
crates/db_schema/src/impls/tag.rs
Normal file
53
crates/db_schema/src/impls/tag.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use crate::{
|
||||||
|
newtypes::TagId,
|
||||||
|
schema::{post_tag, tag},
|
||||||
|
source::tag::{PostTagInsertForm, Tag, TagInsertForm},
|
||||||
|
traits::Crud,
|
||||||
|
utils::{get_conn, DbPool},
|
||||||
|
};
|
||||||
|
use diesel::{insert_into, result::Error, QueryDsl};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Crud for Tag {
|
||||||
|
type InsertForm = TagInsertForm;
|
||||||
|
|
||||||
|
type UpdateForm = TagInsertForm;
|
||||||
|
|
||||||
|
type IdType = TagId;
|
||||||
|
|
||||||
|
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(tag::table)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
pid: TagId,
|
||||||
|
form: &Self::UpdateForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
diesel::update(tag::table.find(pid))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PostTagInsertForm {
|
||||||
|
pub async fn insert_tag_associations(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
tags: &[PostTagInsertForm],
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(post_tag::table)
|
||||||
|
.values(tags)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -210,6 +210,8 @@ pub enum ModlogActionType {
|
||||||
AdminPurgeCommunity,
|
AdminPurgeCommunity,
|
||||||
AdminPurgePost,
|
AdminPurgePost,
|
||||||
AdminPurgeComment,
|
AdminPurgeComment,
|
||||||
|
AdminBlockInstance,
|
||||||
|
AdminAllowInstance,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|
|
@ -283,3 +283,9 @@ impl InstanceId {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// The internal tag id.
|
||||||
|
pub struct TagId(pub i32);
|
||||||
|
|
|
@ -42,6 +42,29 @@ pub mod sql_types {
|
||||||
pub struct RegistrationModeEnum;
|
pub struct RegistrationModeEnum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
admin_allow_instance (id) {
|
||||||
|
id -> Int4,
|
||||||
|
instance_id -> Int4,
|
||||||
|
admin_person_id -> Int4,
|
||||||
|
allowed -> Bool,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
when_ -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
admin_block_instance (id) {
|
||||||
|
id -> Int4,
|
||||||
|
instance_id -> Int4,
|
||||||
|
admin_person_id -> Int4,
|
||||||
|
blocked -> Bool,
|
||||||
|
reason -> Nullable<Text>,
|
||||||
|
expires -> Nullable<Timestamptz>,
|
||||||
|
when_ -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
admin_purge_comment (id) {
|
admin_purge_comment (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -284,6 +307,7 @@ diesel::table! {
|
||||||
instance_id -> Int4,
|
instance_id -> Int4,
|
||||||
published -> Timestamptz,
|
published -> Timestamptz,
|
||||||
updated -> Nullable<Timestamptz>,
|
updated -> Nullable<Timestamptz>,
|
||||||
|
expires -> Nullable<Timestamptz>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,6 +660,7 @@ diesel::table! {
|
||||||
enabled -> Bool,
|
enabled -> Bool,
|
||||||
published -> Timestamptz,
|
published -> Timestamptz,
|
||||||
updated -> Nullable<Timestamptz>,
|
updated -> Nullable<Timestamptz>,
|
||||||
|
use_pkce -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -801,6 +826,14 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
post_tag (post_id, tag_id) {
|
||||||
|
post_id -> Int4,
|
||||||
|
tag_id -> Int4,
|
||||||
|
published -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
previously_run_sql (id) {
|
previously_run_sql (id) {
|
||||||
id -> Bool,
|
id -> Bool,
|
||||||
|
@ -933,6 +966,18 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
tag (id) {
|
||||||
|
id -> Int4,
|
||||||
|
ap_id -> Text,
|
||||||
|
name -> Text,
|
||||||
|
community_id -> Int4,
|
||||||
|
published -> Timestamptz,
|
||||||
|
updated -> Nullable<Timestamptz>,
|
||||||
|
deleted -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
tagline (id) {
|
tagline (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -942,6 +987,10 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(admin_allow_instance -> instance (instance_id));
|
||||||
|
diesel::joinable!(admin_allow_instance -> person (admin_person_id));
|
||||||
|
diesel::joinable!(admin_block_instance -> instance (instance_id));
|
||||||
|
diesel::joinable!(admin_block_instance -> person (admin_person_id));
|
||||||
diesel::joinable!(admin_purge_comment -> person (admin_person_id));
|
diesel::joinable!(admin_purge_comment -> person (admin_person_id));
|
||||||
diesel::joinable!(admin_purge_comment -> post (post_id));
|
diesel::joinable!(admin_purge_comment -> post (post_id));
|
||||||
diesel::joinable!(admin_purge_community -> person (admin_person_id));
|
diesel::joinable!(admin_purge_community -> person (admin_person_id));
|
||||||
|
@ -1010,6 +1059,8 @@ diesel::joinable!(post_aggregates -> instance (instance_id));
|
||||||
diesel::joinable!(post_aggregates -> person (creator_id));
|
diesel::joinable!(post_aggregates -> person (creator_id));
|
||||||
diesel::joinable!(post_aggregates -> post (post_id));
|
diesel::joinable!(post_aggregates -> post (post_id));
|
||||||
diesel::joinable!(post_report -> post (post_id));
|
diesel::joinable!(post_report -> post (post_id));
|
||||||
|
diesel::joinable!(post_tag -> post (post_id));
|
||||||
|
diesel::joinable!(post_tag -> tag (tag_id));
|
||||||
diesel::joinable!(private_message_report -> private_message (private_message_id));
|
diesel::joinable!(private_message_report -> private_message (private_message_id));
|
||||||
diesel::joinable!(registration_application -> local_user (local_user_id));
|
diesel::joinable!(registration_application -> local_user (local_user_id));
|
||||||
diesel::joinable!(registration_application -> person (admin_id));
|
diesel::joinable!(registration_application -> person (admin_id));
|
||||||
|
@ -1017,8 +1068,11 @@ diesel::joinable!(site -> instance (instance_id));
|
||||||
diesel::joinable!(site_aggregates -> site (site_id));
|
diesel::joinable!(site_aggregates -> site (site_id));
|
||||||
diesel::joinable!(site_language -> language (language_id));
|
diesel::joinable!(site_language -> language (language_id));
|
||||||
diesel::joinable!(site_language -> site (site_id));
|
diesel::joinable!(site_language -> site (site_id));
|
||||||
|
diesel::joinable!(tag -> community (community_id));
|
||||||
|
|
||||||
diesel::allow_tables_to_appear_in_same_query!(
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
admin_allow_instance,
|
||||||
|
admin_block_instance,
|
||||||
admin_purge_comment,
|
admin_purge_comment,
|
||||||
admin_purge_community,
|
admin_purge_community,
|
||||||
admin_purge_person,
|
admin_purge_person,
|
||||||
|
@ -1074,6 +1128,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
post_actions,
|
post_actions,
|
||||||
post_aggregates,
|
post_aggregates,
|
||||||
post_report,
|
post_report,
|
||||||
|
post_tag,
|
||||||
previously_run_sql,
|
previously_run_sql,
|
||||||
private_message,
|
private_message,
|
||||||
private_message_report,
|
private_message_report,
|
||||||
|
@ -1085,5 +1140,6 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
site,
|
site,
|
||||||
site_aggregates,
|
site_aggregates,
|
||||||
site_language,
|
site_language,
|
||||||
|
tag,
|
||||||
tagline,
|
tagline,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::newtypes::InstanceId;
|
use crate::newtypes::InstanceId;
|
||||||
#[cfg(feature = "full")]
|
|
||||||
use crate::schema::federation_blocklist;
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use {crate::schema::federation_blocklist, ts_rs::TS};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "full",
|
feature = "full",
|
||||||
derive(Queryable, Selectable, Associations, Identifiable)
|
derive(TS, Queryable, Selectable, Associations, Identifiable)
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "full",
|
feature = "full",
|
||||||
|
@ -17,10 +17,14 @@ use std::fmt::Debug;
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = federation_blocklist))]
|
#[cfg_attr(feature = "full", diesel(table_name = federation_blocklist))]
|
||||||
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
|
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
|
||||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
pub struct FederationBlockList {
|
pub struct FederationBlockList {
|
||||||
pub instance_id: InstanceId,
|
pub instance_id: InstanceId,
|
||||||
pub published: DateTime<Utc>,
|
pub published: DateTime<Utc>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub updated: Option<DateTime<Utc>>,
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub expires: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
|
@ -29,4 +33,5 @@ pub struct FederationBlockList {
|
||||||
pub struct FederationBlockListForm {
|
pub struct FederationBlockListForm {
|
||||||
pub instance_id: InstanceId,
|
pub instance_id: InstanceId,
|
||||||
pub updated: Option<DateTime<Utc>>,
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
pub expires: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub mod local_site_url_blocklist;
|
||||||
pub mod local_user;
|
pub mod local_user;
|
||||||
pub mod local_user_vote_display_mode;
|
pub mod local_user_vote_display_mode;
|
||||||
pub mod login_token;
|
pub mod login_token;
|
||||||
pub mod moderator;
|
pub mod mod_log;
|
||||||
pub mod oauth_account;
|
pub mod oauth_account;
|
||||||
pub mod oauth_provider;
|
pub mod oauth_provider;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
|
@ -40,6 +40,7 @@ pub mod private_message_report;
|
||||||
pub mod registration_application;
|
pub mod registration_application;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
pub mod tag;
|
||||||
pub mod tagline;
|
pub mod tagline;
|
||||||
|
|
||||||
/// Default value for columns like [community::Community.inbox_url] which are marked as serde(skip).
|
/// Default value for columns like [community::Community.inbox_url] which are marked as serde(skip).
|
||||||
|
|
176
crates/db_schema/src/source/mod_log/admin.rs
Normal file
176
crates/db_schema/src/source/mod_log/admin.rs
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
use crate::newtypes::{CommunityId, InstanceId, PersonId, PostId};
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use crate::schema::{
|
||||||
|
admin_allow_instance,
|
||||||
|
admin_block_instance,
|
||||||
|
admin_purge_comment,
|
||||||
|
admin_purge_community,
|
||||||
|
admin_purge_person,
|
||||||
|
admin_purge_post,
|
||||||
|
};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_person))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// When an admin purges a person.
|
||||||
|
pub struct AdminPurgePerson {
|
||||||
|
pub id: i32,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub when_: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_person))]
|
||||||
|
pub struct AdminPurgePersonForm {
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_community))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// When an admin purges a community.
|
||||||
|
pub struct AdminPurgeCommunity {
|
||||||
|
pub id: i32,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub when_: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_community))]
|
||||||
|
pub struct AdminPurgeCommunityForm {
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_post))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// When an admin purges a post.
|
||||||
|
pub struct AdminPurgePost {
|
||||||
|
pub id: i32,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub when_: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_post))]
|
||||||
|
pub struct AdminPurgePostForm {
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_comment))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// When an admin purges a comment.
|
||||||
|
pub struct AdminPurgeComment {
|
||||||
|
pub id: i32,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub post_id: PostId,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub when_: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_comment))]
|
||||||
|
pub struct AdminPurgeCommentForm {
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub post_id: PostId,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "full",
|
||||||
|
derive(TS, Queryable, Selectable, Associations, Identifiable)
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "full",
|
||||||
|
diesel(belongs_to(crate::source::instance::Instance))
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct AdminAllowInstance {
|
||||||
|
pub id: i32,
|
||||||
|
pub instance_id: InstanceId,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub allowed: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub when_: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
|
||||||
|
pub struct AdminAllowInstanceForm {
|
||||||
|
pub instance_id: InstanceId,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub allowed: bool,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "full",
|
||||||
|
derive(TS, Queryable, Selectable, Associations, Identifiable)
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "full",
|
||||||
|
diesel(belongs_to(crate::source::instance::Instance))
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_block_instance))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct AdminBlockInstance {
|
||||||
|
pub id: i32,
|
||||||
|
pub instance_id: InstanceId,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub blocked: bool,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub reason: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub expires: Option<DateTime<Utc>>,
|
||||||
|
pub when_: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = admin_block_instance))]
|
||||||
|
pub struct AdminBlockInstanceForm {
|
||||||
|
pub instance_id: InstanceId,
|
||||||
|
pub admin_person_id: PersonId,
|
||||||
|
pub blocked: bool,
|
||||||
|
pub reason: Option<String>,
|
||||||
|
pub when_: Option<DateTime<Utc>>,
|
||||||
|
}
|
2
crates/db_schema/src/source/mod_log/mod.rs
Normal file
2
crates/db_schema/src/source/mod_log/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod admin;
|
||||||
|
pub mod moderator;
|
|
@ -1,10 +1,6 @@
|
||||||
use crate::newtypes::{CommentId, CommunityId, PersonId, PostId};
|
use crate::newtypes::{CommentId, CommunityId, PersonId, PostId};
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use crate::schema::{
|
use crate::schema::{
|
||||||
admin_purge_comment,
|
|
||||||
admin_purge_community,
|
|
||||||
admin_purge_person,
|
|
||||||
admin_purge_post,
|
|
||||||
mod_add,
|
mod_add,
|
||||||
mod_add_community,
|
mod_add_community,
|
||||||
mod_ban,
|
mod_ban,
|
||||||
|
@ -300,95 +296,3 @@ pub struct ModAddForm {
|
||||||
pub other_person_id: PersonId,
|
pub other_person_id: PersonId,
|
||||||
pub removed: Option<bool>,
|
pub removed: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_person))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// When an admin purges a person.
|
|
||||||
pub struct AdminPurgePerson {
|
|
||||||
pub id: i32,
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
|
||||||
pub reason: Option<String>,
|
|
||||||
pub when_: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_person))]
|
|
||||||
pub struct AdminPurgePersonForm {
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_community))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// When an admin purges a community.
|
|
||||||
pub struct AdminPurgeCommunity {
|
|
||||||
pub id: i32,
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
|
||||||
pub reason: Option<String>,
|
|
||||||
pub when_: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_community))]
|
|
||||||
pub struct AdminPurgeCommunityForm {
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_post))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// When an admin purges a post.
|
|
||||||
pub struct AdminPurgePost {
|
|
||||||
pub id: i32,
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
pub community_id: CommunityId,
|
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
|
||||||
pub reason: Option<String>,
|
|
||||||
pub when_: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_post))]
|
|
||||||
pub struct AdminPurgePostForm {
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
pub community_id: CommunityId,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_comment))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
|
||||||
/// When an admin purges a comment.
|
|
||||||
pub struct AdminPurgeComment {
|
|
||||||
pub id: i32,
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
pub post_id: PostId,
|
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
|
||||||
pub reason: Option<String>,
|
|
||||||
pub when_: DateTime<Utc>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = admin_purge_comment))]
|
|
||||||
pub struct AdminPurgeCommentForm {
|
|
||||||
pub admin_person_id: PersonId,
|
|
||||||
pub post_id: PostId,
|
|
||||||
pub reason: Option<String>,
|
|
||||||
}
|
|
|
@ -62,6 +62,8 @@ pub struct OAuthProvider {
|
||||||
pub published: DateTime<Utc>,
|
pub published: DateTime<Utc>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub updated: Option<DateTime<Utc>>,
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
/// switch to enable or disable PKCE
|
||||||
|
pub use_pkce: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
|
||||||
|
@ -83,6 +85,7 @@ impl Serialize for PublicOAuthProvider {
|
||||||
state.serialize_field("authorization_endpoint", &self.0.authorization_endpoint)?;
|
state.serialize_field("authorization_endpoint", &self.0.authorization_endpoint)?;
|
||||||
state.serialize_field("client_id", &self.0.client_id)?;
|
state.serialize_field("client_id", &self.0.client_id)?;
|
||||||
state.serialize_field("scopes", &self.0.scopes)?;
|
state.serialize_field("scopes", &self.0.scopes)?;
|
||||||
|
state.serialize_field("use_pkce", &self.0.use_pkce)?;
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +105,7 @@ pub struct OAuthProviderInsertForm {
|
||||||
pub scopes: String,
|
pub scopes: String,
|
||||||
pub auto_verify_email: Option<bool>,
|
pub auto_verify_email: Option<bool>,
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +122,7 @@ pub struct OAuthProviderUpdateForm {
|
||||||
pub scopes: Option<String>,
|
pub scopes: Option<String>,
|
||||||
pub auto_verify_email: Option<bool>,
|
pub auto_verify_email: Option<bool>,
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
pub updated: Option<Option<DateTime<Utc>>>,
|
pub updated: Option<Option<DateTime<Utc>>>,
|
||||||
}
|
}
|
||||||
|
|
57
crates/db_schema/src/source/tag.rs
Normal file
57
crates/db_schema/src/source/tag.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use crate::newtypes::{CommunityId, DbUrl, PostId, TagId};
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use crate::schema::{post_tag, tag};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
/// A tag that can be assigned to a post within a community.
|
||||||
|
/// The tag object is created by the community moderators.
|
||||||
|
/// The assignment happens by the post creator and can be updated by the community moderators.
|
||||||
|
///
|
||||||
|
/// A tag is a federatable object that gives additional context to another object, which can be
|
||||||
|
/// displayed and filtered on currently, we only have community post tags, which is a tag that is
|
||||||
|
/// created by post authors as well as mods of a community, to categorize a post. in the future we
|
||||||
|
/// may add more tag types, depending on the requirements, this will lead to either expansion of
|
||||||
|
/// this table (community_id optional, addition of tag_type enum) or split of this table / creation
|
||||||
|
/// of new tables.
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS, Queryable, Selectable, Identifiable))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = tag))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct Tag {
|
||||||
|
pub id: TagId,
|
||||||
|
pub ap_id: DbUrl,
|
||||||
|
pub name: String,
|
||||||
|
/// the community that owns this tag
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
pub published: DateTime<Utc>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
pub deleted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = tag))]
|
||||||
|
pub struct TagInsertForm {
|
||||||
|
pub ap_id: DbUrl,
|
||||||
|
pub name: String,
|
||||||
|
pub community_id: CommunityId,
|
||||||
|
// default now
|
||||||
|
pub published: Option<DateTime<Utc>>,
|
||||||
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
pub deleted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = post_tag))]
|
||||||
|
pub struct PostTagInsertForm {
|
||||||
|
pub post_id: PostId,
|
||||||
|
pub tag_id: TagId,
|
||||||
|
}
|
|
@ -547,6 +547,11 @@ pub mod functions {
|
||||||
|
|
||||||
// really this function is variadic, this just adds the two-argument version
|
// really this function is variadic, this just adds the two-argument version
|
||||||
define_sql_function!(fn coalesce<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(x: diesel::sql_types::Nullable<T>, y: T) -> T);
|
define_sql_function!(fn coalesce<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(x: diesel::sql_types::Nullable<T>, y: T) -> T);
|
||||||
|
|
||||||
|
define_sql_function! {
|
||||||
|
#[aggregate]
|
||||||
|
fn json_agg<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(obj: T) -> Json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DELETED_REPLACEMENT_TEXT: &str = "*Permanently Deleted*";
|
pub const DELETED_REPLACEMENT_TEXT: &str = "*Permanently Deleted*";
|
||||||
|
|
|
@ -35,6 +35,7 @@ diesel-async = { workspace = true, optional = true }
|
||||||
diesel_ltree = { workspace = true, optional = true }
|
diesel_ltree = { workspace = true, optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
tracing = { workspace = true, optional = true }
|
tracing = { workspace = true, optional = true }
|
||||||
ts-rs = { workspace = true, optional = true }
|
ts-rs = { workspace = true, optional = true }
|
||||||
actix-web = { workspace = true, optional = true }
|
actix-web = { workspace = true, optional = true }
|
||||||
|
@ -46,3 +47,4 @@ serial_test = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
test-context = "0.3.0"
|
||||||
|
|
|
@ -316,17 +316,14 @@ impl CommentView {
|
||||||
comment_id: CommentId,
|
comment_id: CommentId,
|
||||||
my_local_user: Option<&'_ LocalUser>,
|
my_local_user: Option<&'_ LocalUser>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
let is_admin = my_local_user.map(|u| u.admin).unwrap_or(false);
|
||||||
// If a person is given, then my_vote (res.9), if None, should be 0, not null
|
// If a person is given, then my_vote (res.9), if None, should be 0, not null
|
||||||
// Necessary to differentiate between other person's votes
|
// Necessary to differentiate between other person's votes
|
||||||
let res = queries().read(pool, (comment_id, my_local_user)).await?;
|
let mut res = queries().read(pool, (comment_id, my_local_user)).await?;
|
||||||
let mut new_view = res.clone();
|
|
||||||
if my_local_user.is_some() && res.my_vote.is_none() {
|
if my_local_user.is_some() && res.my_vote.is_none() {
|
||||||
new_view.my_vote = Some(0);
|
res.my_vote = Some(0);
|
||||||
}
|
}
|
||||||
if res.comment.deleted || res.comment.removed {
|
Ok(handle_deleted(res, is_admin))
|
||||||
new_view.comment.content = String::new();
|
|
||||||
}
|
|
||||||
Ok(new_view)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,22 +347,25 @@ pub struct CommentQuery<'a> {
|
||||||
|
|
||||||
impl CommentQuery<'_> {
|
impl CommentQuery<'_> {
|
||||||
pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> {
|
pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> {
|
||||||
|
let is_admin = self.local_user.map(|u| u.admin).unwrap_or(false);
|
||||||
Ok(
|
Ok(
|
||||||
queries()
|
queries()
|
||||||
.list(pool, (self, site))
|
.list(pool, (self, site))
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut c| {
|
.map(|c| handle_deleted(c, is_admin))
|
||||||
if c.comment.deleted || c.comment.removed {
|
|
||||||
c.comment.content = String::new();
|
|
||||||
}
|
|
||||||
c
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_deleted(mut c: CommentView, is_admin: bool) -> CommentView {
|
||||||
|
if !is_admin && (c.comment.deleted || c.comment.removed) {
|
||||||
|
c.comment.content = String::new();
|
||||||
|
}
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[expect(clippy::indexing_slicing)]
|
#[expect(clippy::indexing_slicing)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -1301,4 +1301,65 @@ mod tests {
|
||||||
|
|
||||||
cleanup(data, pool).await
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn comment_removed() -> LemmyResult<()> {
|
||||||
|
let pool = &build_db_pool_for_tests();
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let mut data = init_data(pool).await?;
|
||||||
|
|
||||||
|
// Mark a comment as removed
|
||||||
|
let form = CommentUpdateForm {
|
||||||
|
removed: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Comment::update(pool, data.inserted_comment_0.id, &form).await?;
|
||||||
|
|
||||||
|
// Read as normal user, content is cleared
|
||||||
|
data.timmy_local_user_view.local_user.admin = false;
|
||||||
|
let comment_view = CommentView::read(
|
||||||
|
pool,
|
||||||
|
data.inserted_comment_0.id,
|
||||||
|
Some(&data.timmy_local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
assert_eq!("", comment_view.comment.content);
|
||||||
|
let comment_listing = CommentQuery {
|
||||||
|
community_id: Some(data.inserted_community.id),
|
||||||
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
|
sort: Some(CommentSortType::Old),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.list(&data.site, pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!("", comment_listing[0].comment.content);
|
||||||
|
|
||||||
|
// Read as admin, content is returned
|
||||||
|
data.timmy_local_user_view.local_user.admin = true;
|
||||||
|
let comment_view = CommentView::read(
|
||||||
|
pool,
|
||||||
|
data.inserted_comment_0.id,
|
||||||
|
Some(&data.timmy_local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
data.inserted_comment_0.content,
|
||||||
|
comment_view.comment.content
|
||||||
|
);
|
||||||
|
let comment_listing = CommentQuery {
|
||||||
|
community_id: Some(data.inserted_community.id),
|
||||||
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
|
sort: Some(CommentSortType::Old),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.list(&data.site, pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
data.inserted_comment_0.content,
|
||||||
|
comment_listing[0].comment.content
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup(data, pool).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ pub mod local_user_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod post_report_view;
|
pub mod post_report_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
pub mod post_tags_view;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod post_view;
|
pub mod post_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod private_message_report_view;
|
pub mod private_message_report_view;
|
||||||
|
|
30
crates/db_views/src/post_tags_view.rs
Normal file
30
crates/db_views/src/post_tags_view.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
//! see post_view.rs for the reason for this json decoding
|
||||||
|
use crate::structs::PostTags;
|
||||||
|
use diesel::{
|
||||||
|
deserialize::FromSql,
|
||||||
|
pg::{Pg, PgValue},
|
||||||
|
serialize::ToSql,
|
||||||
|
sql_types::{self, Nullable},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl FromSql<Nullable<sql_types::Json>, Pg> for PostTags {
|
||||||
|
fn from_sql(bytes: PgValue) -> diesel::deserialize::Result<Self> {
|
||||||
|
let value = <serde_json::Value as FromSql<sql_types::Json, Pg>>::from_sql(bytes)?;
|
||||||
|
Ok(serde_json::from_value::<PostTags>(value)?)
|
||||||
|
}
|
||||||
|
fn from_nullable_sql(
|
||||||
|
bytes: Option<<Pg as diesel::backend::Backend>::RawValue<'_>>,
|
||||||
|
) -> diesel::deserialize::Result<Self> {
|
||||||
|
match bytes {
|
||||||
|
Some(bytes) => Self::from_sql(bytes),
|
||||||
|
None => Ok(Self { tags: vec![] }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToSql<Nullable<sql_types::Json>, Pg> for PostTags {
|
||||||
|
fn to_sql(&self, out: &mut diesel::serialize::Output<Pg>) -> diesel::serialize::Result {
|
||||||
|
let value = serde_json::to_value(self)?;
|
||||||
|
<serde_json::Value as ToSql<sql_types::Json, Pg>>::to_sql(&value, &mut out.reborrow())
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,9 @@ use diesel::{
|
||||||
pg::Pg,
|
pg::Pg,
|
||||||
query_builder::AsQuery,
|
query_builder::AsQuery,
|
||||||
result::Error,
|
result::Error,
|
||||||
|
sql_types,
|
||||||
BoolExpressionMethods,
|
BoolExpressionMethods,
|
||||||
|
BoxableExpression,
|
||||||
ExpressionMethods,
|
ExpressionMethods,
|
||||||
JoinOnDsl,
|
JoinOnDsl,
|
||||||
NullableExpressionMethods,
|
NullableExpressionMethods,
|
||||||
|
@ -32,6 +34,8 @@ use lemmy_db_schema::{
|
||||||
post,
|
post,
|
||||||
post_actions,
|
post_actions,
|
||||||
post_aggregates,
|
post_aggregates,
|
||||||
|
post_tag,
|
||||||
|
tag,
|
||||||
},
|
},
|
||||||
source::{
|
source::{
|
||||||
community::{CommunityFollower, CommunityFollowerState},
|
community::{CommunityFollower, CommunityFollowerState},
|
||||||
|
@ -80,6 +84,31 @@ fn queries<'a>() -> Queries<
|
||||||
// TODO maybe this should go to localuser also
|
// TODO maybe this should go to localuser also
|
||||||
let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>,
|
let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>,
|
||||||
my_person_id: Option<PersonId>| {
|
my_person_id: Option<PersonId>| {
|
||||||
|
// We fetch post tags by letting postgresql aggregate them internally in a subquery into JSON.
|
||||||
|
// This is a simple way to join m rows into n rows without duplicating the data and getting
|
||||||
|
// complex diesel types. In pure SQL you would usually do this either using a LEFT JOIN + then
|
||||||
|
// aggregating the results in the application code. But this results in a lot of duplicate
|
||||||
|
// data transferred (since each post will be returned once per tag that it has) and more
|
||||||
|
// complicated application code. The diesel docs suggest doing three separate sequential queries
|
||||||
|
// in this case (see https://diesel.rs/guides/relations.html#many-to-many-or-mn ): First fetch
|
||||||
|
// the posts, then fetch all relevant post-tag-association tuples from the db, and then fetch
|
||||||
|
// all the relevant tag objects.
|
||||||
|
//
|
||||||
|
// If we want to filter by post tag we will have to add
|
||||||
|
// separate logic below since this subquery can't affect filtering, but it is simple (`WHERE
|
||||||
|
// exists (select 1 from post_community_post_tags where community_post_tag_id in (1,2,3,4)`).
|
||||||
|
let post_tags: Box<
|
||||||
|
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Json>>,
|
||||||
|
> = Box::new(
|
||||||
|
post_tag::table
|
||||||
|
.inner_join(tag::table)
|
||||||
|
.select(diesel::dsl::sql::<diesel::sql_types::Json>(
|
||||||
|
"json_agg(tag.*)",
|
||||||
|
))
|
||||||
|
.filter(post_tag::post_id.eq(post_aggregates::post_id))
|
||||||
|
.filter(tag::deleted.eq(false))
|
||||||
|
.single_value(),
|
||||||
|
);
|
||||||
query
|
query
|
||||||
.inner_join(person::table)
|
.inner_join(person::table)
|
||||||
.inner_join(community::table)
|
.inner_join(community::table)
|
||||||
|
@ -136,6 +165,7 @@ fn queries<'a>() -> Queries<
|
||||||
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
||||||
post_aggregates::comments,
|
post_aggregates::comments,
|
||||||
),
|
),
|
||||||
|
post_tags,
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -603,11 +633,13 @@ impl<'a> PostQuery<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::indexing_slicing)]
|
||||||
|
#[expect(clippy::expect_used)]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
post_view::{PaginationCursorData, PostQuery, PostView},
|
post_view::{PaginationCursorData, PostQuery, PostView},
|
||||||
structs::LocalUserView,
|
structs::{LocalUserView, PostTags},
|
||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel_async::SimpleAsyncConnection;
|
use diesel_async::SimpleAsyncConnection;
|
||||||
|
@ -651,29 +683,33 @@ mod tests {
|
||||||
PostUpdateForm,
|
PostUpdateForm,
|
||||||
},
|
},
|
||||||
site::Site,
|
site::Site,
|
||||||
|
tag::{PostTagInsertForm, Tag, TagInsertForm},
|
||||||
},
|
},
|
||||||
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable},
|
traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable},
|
||||||
utils::{build_db_pool, build_db_pool_for_tests, get_conn, uplete, DbPool, RANK_DEFAULT},
|
utils::{build_db_pool, get_conn, uplete, ActualDbPool, DbPool, RANK_DEFAULT},
|
||||||
CommunityVisibility,
|
CommunityVisibility,
|
||||||
PostSortType,
|
PostSortType,
|
||||||
SubscribedType,
|
SubscribedType,
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
use test_context::{test_context, AsyncTestContext};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const POST_WITH_ANOTHER_TITLE: &str = "Another title";
|
const POST_WITH_ANOTHER_TITLE: &str = "Another title";
|
||||||
const POST_BY_BLOCKED_PERSON: &str = "post by blocked person";
|
const POST_BY_BLOCKED_PERSON: &str = "post by blocked person";
|
||||||
const POST_BY_BOT: &str = "post by bot";
|
const POST_BY_BOT: &str = "post by bot";
|
||||||
const POST: &str = "post";
|
const POST: &str = "post";
|
||||||
|
const POST_WITH_TAGS: &str = "post with tags";
|
||||||
|
|
||||||
fn names(post_views: &[PostView]) -> Vec<&str> {
|
fn names(post_views: &[PostView]) -> Vec<&str> {
|
||||||
post_views.iter().map(|i| i.post.name.as_str()).collect()
|
post_views.iter().map(|i| i.post.name.as_str()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Data {
|
struct Data {
|
||||||
|
pool: ActualDbPool,
|
||||||
inserted_instance: Instance,
|
inserted_instance: Instance,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
blocked_local_user_view: LocalUserView,
|
blocked_local_user_view: LocalUserView,
|
||||||
|
@ -681,10 +717,19 @@ mod tests {
|
||||||
inserted_community: Community,
|
inserted_community: Community,
|
||||||
inserted_post: Post,
|
inserted_post: Post,
|
||||||
inserted_bot_post: Post,
|
inserted_bot_post: Post,
|
||||||
|
inserted_post_with_tags: Post,
|
||||||
|
tag_1: Tag,
|
||||||
|
tag_2: Tag,
|
||||||
site: Site,
|
site: Site,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
|
fn pool(&self) -> ActualDbPool {
|
||||||
|
self.pool.clone()
|
||||||
|
}
|
||||||
|
pub fn pool2(&self) -> DbPool<'_> {
|
||||||
|
DbPool::Pool(&self.pool)
|
||||||
|
}
|
||||||
fn default_post_query(&self) -> PostQuery<'_> {
|
fn default_post_query(&self) -> PostQuery<'_> {
|
||||||
PostQuery {
|
PostQuery {
|
||||||
sort: Some(PostSortType::New),
|
sort: Some(PostSortType::New),
|
||||||
|
@ -692,9 +737,10 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
async fn setup() -> LemmyResult<Data> {
|
||||||
|
let actual_pool = build_db_pool()?;
|
||||||
|
let pool = &mut (&actual_pool).into();
|
||||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
let new_person = PersonInsertForm::test_form(inserted_instance.id, "tegan");
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "tegan");
|
||||||
|
@ -752,11 +798,38 @@ mod tests {
|
||||||
|
|
||||||
PersonBlock::block(pool, &person_block).await?;
|
PersonBlock::block(pool, &person_block).await?;
|
||||||
|
|
||||||
|
// Two community post tags
|
||||||
|
let tag_1 = Tag::create(
|
||||||
|
pool,
|
||||||
|
&TagInsertForm {
|
||||||
|
ap_id: Url::parse(&format!("{}/tags/test_tag1", inserted_community.actor_id))?.into(),
|
||||||
|
name: "Test Tag 1".into(),
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
published: None,
|
||||||
|
updated: None,
|
||||||
|
deleted: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let tag_2 = Tag::create(
|
||||||
|
pool,
|
||||||
|
&TagInsertForm {
|
||||||
|
ap_id: Url::parse(&format!("{}/tags/test_tag2", inserted_community.actor_id))?.into(),
|
||||||
|
name: "Test Tag 2".into(),
|
||||||
|
community_id: inserted_community.id,
|
||||||
|
published: None,
|
||||||
|
updated: None,
|
||||||
|
deleted: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
// A sample post
|
// A sample post
|
||||||
let new_post = PostInsertForm {
|
let new_post = PostInsertForm {
|
||||||
language_id: Some(LanguageId(47)),
|
language_id: Some(LanguageId(47)),
|
||||||
..PostInsertForm::new(POST.to_string(), inserted_person.id, inserted_community.id)
|
..PostInsertForm::new(POST.to_string(), inserted_person.id, inserted_community.id)
|
||||||
};
|
};
|
||||||
|
|
||||||
let inserted_post = Post::create(pool, &new_post).await?;
|
let inserted_post = Post::create(pool, &new_post).await?;
|
||||||
|
|
||||||
let new_bot_post = PostInsertForm::new(
|
let new_bot_post = PostInsertForm::new(
|
||||||
|
@ -766,6 +839,29 @@ mod tests {
|
||||||
);
|
);
|
||||||
let inserted_bot_post = Post::create(pool, &new_bot_post).await?;
|
let inserted_bot_post = Post::create(pool, &new_bot_post).await?;
|
||||||
|
|
||||||
|
// A sample post with tags
|
||||||
|
let new_post = PostInsertForm {
|
||||||
|
language_id: Some(LanguageId(47)),
|
||||||
|
..PostInsertForm::new(
|
||||||
|
POST_WITH_TAGS.to_string(),
|
||||||
|
inserted_person.id,
|
||||||
|
inserted_community.id,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_post_with_tags = Post::create(pool, &new_post).await?;
|
||||||
|
let inserted_tags = vec![
|
||||||
|
PostTagInsertForm {
|
||||||
|
post_id: inserted_post_with_tags.id,
|
||||||
|
tag_id: tag_1.id,
|
||||||
|
},
|
||||||
|
PostTagInsertForm {
|
||||||
|
post_id: inserted_post_with_tags.id,
|
||||||
|
tag_id: tag_2.id,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
PostTagInsertForm::insert_tag_associations(pool, &inserted_tags).await?;
|
||||||
|
|
||||||
let local_user_view = LocalUserView {
|
let local_user_view = LocalUserView {
|
||||||
local_user: inserted_local_user,
|
local_user: inserted_local_user,
|
||||||
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
||||||
|
@ -798,6 +894,7 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Data {
|
Ok(Data {
|
||||||
|
pool: actual_pool,
|
||||||
inserted_instance,
|
inserted_instance,
|
||||||
local_user_view,
|
local_user_view,
|
||||||
blocked_local_user_view,
|
blocked_local_user_view,
|
||||||
|
@ -805,16 +902,41 @@ mod tests {
|
||||||
inserted_community,
|
inserted_community,
|
||||||
inserted_post,
|
inserted_post,
|
||||||
inserted_bot_post,
|
inserted_bot_post,
|
||||||
|
inserted_post_with_tags,
|
||||||
|
tag_1,
|
||||||
|
tag_2,
|
||||||
site,
|
site,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
async fn teardown(data: Data) -> LemmyResult<()> {
|
||||||
|
let pool = &mut data.pool2();
|
||||||
|
// let pool = &mut (&pool).into();
|
||||||
|
let num_deleted = Post::delete(pool, data.inserted_post.id).await?;
|
||||||
|
Community::delete(pool, data.inserted_community.id).await?;
|
||||||
|
Person::delete(pool, data.local_user_view.person.id).await?;
|
||||||
|
Person::delete(pool, data.inserted_bot.id).await?;
|
||||||
|
Person::delete(pool, data.blocked_local_user_view.person.id).await?;
|
||||||
|
Instance::delete(pool, data.inserted_instance.id).await?;
|
||||||
|
assert_eq!(1, num_deleted);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsyncTestContext for Data {
|
||||||
|
async fn setup() -> Self {
|
||||||
|
Data::setup().await.expect("setup failed")
|
||||||
|
}
|
||||||
|
async fn teardown(self) {
|
||||||
|
Data::teardown(self).await.expect("teardown failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_with_person() -> LemmyResult<()> {
|
async fn post_listing_with_person(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let mut data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let local_user_form = LocalUserUpdateForm {
|
let local_user_form = LocalUserUpdateForm {
|
||||||
show_bot_accounts: Some(false),
|
show_bot_accounts: Some(false),
|
||||||
|
@ -823,12 +945,14 @@ mod tests {
|
||||||
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
||||||
data.local_user_view.local_user.show_bot_accounts = false;
|
data.local_user_view.local_user.show_bot_accounts = false;
|
||||||
|
|
||||||
let read_post_listing = PostQuery {
|
let mut read_post_listing = PostQuery {
|
||||||
community_id: Some(data.inserted_community.id),
|
community_id: Some(data.inserted_community.id),
|
||||||
..data.default_post_query()
|
..data.default_post_query()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
// remove tags post
|
||||||
|
read_post_listing.remove(0);
|
||||||
|
|
||||||
let post_listing_single_with_person = PostView::read(
|
let post_listing_single_with_person = PostView::read(
|
||||||
pool,
|
pool,
|
||||||
|
@ -838,7 +962,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let expected_post_listing_with_user = expected_post_view(&data, pool).await?;
|
let expected_post_listing_with_user = expected_post_view(data, pool).await?;
|
||||||
|
|
||||||
// Should be only one person, IE the bot post, and blocked should be missing
|
// Should be only one person, IE the bot post, and blocked should be missing
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -864,17 +988,19 @@ mod tests {
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
// should include bot post which has "undetermined" language
|
// should include bot post which has "undetermined" language
|
||||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_with_bots));
|
assert_eq!(
|
||||||
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
cleanup(data, pool).await
|
names(&post_listings_with_bots)
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_no_person() -> LemmyResult<()> {
|
async fn post_listing_no_person(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let read_post_listing_multiple_no_person = PostQuery {
|
let read_post_listing_multiple_no_person = PostQuery {
|
||||||
community_id: Some(data.inserted_community.id),
|
community_id: Some(data.inserted_community.id),
|
||||||
|
@ -887,32 +1013,31 @@ mod tests {
|
||||||
let read_post_listing_single_no_person =
|
let read_post_listing_single_no_person =
|
||||||
PostView::read(pool, data.inserted_post.id, None, false).await?;
|
PostView::read(pool, data.inserted_post.id, None, false).await?;
|
||||||
|
|
||||||
let expected_post_listing_no_person = expected_post_view(&data, pool).await?;
|
let expected_post_listing_no_person = expected_post_view(data, pool).await?;
|
||||||
|
|
||||||
// Should be 2 posts, with the bot post, and the blocked
|
// Should be 2 posts, with the bot post, and the blocked
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![POST_BY_BOT, POST, POST_BY_BLOCKED_PERSON],
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST, POST_BY_BLOCKED_PERSON],
|
||||||
names(&read_post_listing_multiple_no_person)
|
names(&read_post_listing_multiple_no_person)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(&expected_post_listing_no_person),
|
Some(&expected_post_listing_no_person),
|
||||||
read_post_listing_multiple_no_person.get(1)
|
read_post_listing_multiple_no_person.get(2)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected_post_listing_no_person,
|
expected_post_listing_no_person,
|
||||||
read_post_listing_single_no_person
|
read_post_listing_single_no_person
|
||||||
);
|
);
|
||||||
|
Ok(())
|
||||||
cleanup(data, pool).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_title_only() -> LemmyResult<()> {
|
async fn post_listing_title_only(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// A post which contains the search them 'Post' not in the title (but in the body)
|
// A post which contains the search them 'Post' not in the title (but in the body)
|
||||||
let new_post = PostInsertForm {
|
let new_post = PostInsertForm {
|
||||||
|
@ -950,6 +1075,7 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![
|
vec![
|
||||||
POST_WITH_ANOTHER_TITLE,
|
POST_WITH_ANOTHER_TITLE,
|
||||||
|
POST_WITH_TAGS,
|
||||||
POST_BY_BOT,
|
POST_BY_BOT,
|
||||||
POST,
|
POST,
|
||||||
POST_BY_BLOCKED_PERSON
|
POST_BY_BLOCKED_PERSON
|
||||||
|
@ -959,19 +1085,19 @@ mod tests {
|
||||||
|
|
||||||
// Should be 3 posts when we search for title only
|
// Should be 3 posts when we search for title only
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![POST_BY_BOT, POST, POST_BY_BLOCKED_PERSON],
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST, POST_BY_BLOCKED_PERSON],
|
||||||
names(&read_post_listing_by_title_only)
|
names(&read_post_listing_by_title_only)
|
||||||
);
|
);
|
||||||
Post::delete(pool, inserted_post.id).await?;
|
Post::delete(pool, inserted_post.id).await?;
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_block_community() -> LemmyResult<()> {
|
async fn post_listing_block_community(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let community_block = CommunityBlockForm {
|
let community_block = CommunityBlockForm {
|
||||||
person_id: data.local_user_view.person.id,
|
person_id: data.local_user_view.person.id,
|
||||||
|
@ -989,15 +1115,15 @@ mod tests {
|
||||||
assert_eq!(read_post_listings_with_person_after_block, vec![]);
|
assert_eq!(read_post_listings_with_person_after_block, vec![]);
|
||||||
|
|
||||||
CommunityBlock::unblock(pool, &community_block).await?;
|
CommunityBlock::unblock(pool, &community_block).await?;
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_like() -> LemmyResult<()> {
|
async fn post_listing_like(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let mut data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let post_like_form =
|
let post_like_form =
|
||||||
PostLikeForm::new(data.inserted_post.id, data.local_user_view.person.id, 1);
|
PostLikeForm::new(data.inserted_post.id, data.local_user_view.person.id, 1);
|
||||||
|
@ -1020,7 +1146,7 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut expected_post_with_upvote = expected_post_view(&data, pool).await?;
|
let mut expected_post_with_upvote = expected_post_view(data, pool).await?;
|
||||||
expected_post_with_upvote.my_vote = Some(1);
|
expected_post_with_upvote.my_vote = Some(1);
|
||||||
expected_post_with_upvote.counts.score = 1;
|
expected_post_with_upvote.counts.score = 1;
|
||||||
expected_post_with_upvote.counts.upvotes = 1;
|
expected_post_with_upvote.counts.upvotes = 1;
|
||||||
|
@ -1033,26 +1159,27 @@ mod tests {
|
||||||
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
LocalUser::update(pool, data.local_user_view.local_user.id, &local_user_form).await?;
|
||||||
data.local_user_view.local_user.show_bot_accounts = false;
|
data.local_user_view.local_user.show_bot_accounts = false;
|
||||||
|
|
||||||
let read_post_listing = PostQuery {
|
let mut read_post_listing = PostQuery {
|
||||||
community_id: Some(data.inserted_community.id),
|
community_id: Some(data.inserted_community.id),
|
||||||
..data.default_post_query()
|
..data.default_post_query()
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
read_post_listing.remove(0);
|
||||||
assert_eq!(vec![expected_post_with_upvote], read_post_listing);
|
assert_eq!(vec![expected_post_with_upvote], read_post_listing);
|
||||||
|
|
||||||
let like_removed =
|
let like_removed =
|
||||||
PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id).await?;
|
PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id).await?;
|
||||||
assert_eq!(uplete::Count::only_deleted(1), like_removed);
|
assert_eq!(uplete::Count::only_deleted(1), like_removed);
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_liked_only() -> LemmyResult<()> {
|
async fn post_listing_liked_only(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Like both the bot post, and your own
|
// Like both the bot post, and your own
|
||||||
// The liked_only should not show your own post
|
// The liked_only should not show your own post
|
||||||
|
@ -1087,15 +1214,15 @@ mod tests {
|
||||||
// Should be no posts
|
// Should be no posts
|
||||||
assert_eq!(read_disliked_post_listing, vec![]);
|
assert_eq!(read_disliked_post_listing, vec![]);
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_saved_only() -> LemmyResult<()> {
|
async fn post_listing_saved_only(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Save only the bot post
|
// Save only the bot post
|
||||||
// The saved_only should only show the bot post
|
// The saved_only should only show the bot post
|
||||||
|
@ -1115,15 +1242,15 @@ mod tests {
|
||||||
// This should only include the bot post, not the one you created
|
// This should only include the bot post, not the one you created
|
||||||
assert_eq!(vec![POST_BY_BOT], names(&read_saved_post_listing));
|
assert_eq!(vec![POST_BY_BOT], names(&read_saved_post_listing));
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn creator_info() -> LemmyResult<()> {
|
async fn creator_info(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Make one of the inserted persons a moderator
|
// Make one of the inserted persons a moderator
|
||||||
let person_id = data.local_user_view.person.id;
|
let person_id = data.local_user_view.person.id;
|
||||||
|
@ -1145,23 +1272,24 @@ mod tests {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let expected_post_listing = vec![
|
let expected_post_listing = vec![
|
||||||
|
("tegan".to_owned(), true, true),
|
||||||
("mybot".to_owned(), false, false),
|
("mybot".to_owned(), false, false),
|
||||||
("tegan".to_owned(), true, true),
|
("tegan".to_owned(), true, true),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_eq!(expected_post_listing, post_listing);
|
assert_eq!(expected_post_listing, post_listing);
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_person_language() -> LemmyResult<()> {
|
async fn post_listing_person_language(data: &mut Data) -> LemmyResult<()> {
|
||||||
const EL_POSTO: &str = "el posto";
|
const EL_POSTO: &str = "el posto";
|
||||||
|
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let spanish_id = Language::read_id_from_code(pool, "es").await?;
|
let spanish_id = Language::read_id_from_code(pool, "es").await?;
|
||||||
|
|
||||||
|
@ -1180,17 +1308,23 @@ mod tests {
|
||||||
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
||||||
|
|
||||||
// no language filters specified, all posts should be returned
|
// no language filters specified, all posts should be returned
|
||||||
assert_eq!(vec![EL_POSTO, POST_BY_BOT, POST], names(&post_listings_all));
|
assert_eq!(
|
||||||
|
vec![EL_POSTO, POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
|
names(&post_listings_all)
|
||||||
|
);
|
||||||
|
|
||||||
LocalUserLanguage::update(pool, vec![french_id], data.local_user_view.local_user.id).await?;
|
LocalUserLanguage::update(pool, vec![french_id], data.local_user_view.local_user.id).await?;
|
||||||
|
|
||||||
let post_listing_french = data.default_post_query().list(&data.site, pool).await?;
|
let post_listing_french = data.default_post_query().list(&data.site, pool).await?;
|
||||||
|
|
||||||
// only one post in french and one undetermined should be returned
|
// only one post in french and one undetermined should be returned
|
||||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listing_french));
|
assert_eq!(
|
||||||
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
|
names(&post_listing_french)
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Some(french_id),
|
Some(french_id),
|
||||||
post_listing_french.get(1).map(|p| p.post.language_id)
|
post_listing_french.get(2).map(|p| p.post.language_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
LocalUserLanguage::update(
|
LocalUserLanguage::update(
|
||||||
|
@ -1207,6 +1341,7 @@ mod tests {
|
||||||
.map(|p| (p.post.name, p.post.language_id))
|
.map(|p| (p.post.name, p.post.language_id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let expected_post_listings_french_und = vec![
|
let expected_post_listings_french_und = vec![
|
||||||
|
(POST_WITH_TAGS.to_owned(), french_id),
|
||||||
(POST_BY_BOT.to_owned(), UNDETERMINED_ID),
|
(POST_BY_BOT.to_owned(), UNDETERMINED_ID),
|
||||||
(POST.to_owned(), french_id),
|
(POST.to_owned(), french_id),
|
||||||
];
|
];
|
||||||
|
@ -1214,15 +1349,15 @@ mod tests {
|
||||||
// french post and undetermined language post should be returned
|
// french post and undetermined language post should be returned
|
||||||
assert_eq!(expected_post_listings_french_und, post_listings_french_und);
|
assert_eq!(expected_post_listings_french_und, post_listings_french_und);
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_removed() -> LemmyResult<()> {
|
async fn post_listings_removed(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let mut data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Remove the post
|
// Remove the post
|
||||||
Post::update(
|
Post::update(
|
||||||
|
@ -1237,7 +1372,7 @@ mod tests {
|
||||||
|
|
||||||
// Make sure you don't see the removed post in the results
|
// Make sure you don't see the removed post in the results
|
||||||
let post_listings_no_admin = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_no_admin = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(vec![POST], names(&post_listings_no_admin));
|
assert_eq!(vec![POST_WITH_TAGS, POST], names(&post_listings_no_admin));
|
||||||
|
|
||||||
// Removed bot post is shown to admins on its profile page
|
// Removed bot post is shown to admins on its profile page
|
||||||
data.local_user_view.local_user.admin = true;
|
data.local_user_view.local_user.admin = true;
|
||||||
|
@ -1249,15 +1384,15 @@ mod tests {
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(vec![POST_BY_BOT], names(&post_listings_is_admin));
|
assert_eq!(vec![POST_BY_BOT], names(&post_listings_is_admin));
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_deleted() -> LemmyResult<()> {
|
async fn post_listings_deleted(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Delete the post
|
// Delete the post
|
||||||
Post::update(
|
Post::update(
|
||||||
|
@ -1288,15 +1423,15 @@ mod tests {
|
||||||
assert_eq!(expect_contains_deleted, contains_deleted);
|
assert_eq!(expect_contains_deleted, contains_deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_hidden_community() -> LemmyResult<()> {
|
async fn post_listings_hidden_community(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
Community::update(
|
Community::update(
|
||||||
pool,
|
pool,
|
||||||
|
@ -1324,17 +1459,17 @@ mod tests {
|
||||||
let posts = data.default_post_query().list(&data.site, pool).await?;
|
let posts = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert!(!posts.is_empty());
|
assert!(!posts.is_empty());
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_instance_block() -> LemmyResult<()> {
|
async fn post_listing_instance_block(data: &mut Data) -> LemmyResult<()> {
|
||||||
const POST_FROM_BLOCKED_INSTANCE: &str = "post on blocked instance";
|
const POST_FROM_BLOCKED_INSTANCE: &str = "post on blocked instance";
|
||||||
|
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let blocked_instance = Instance::read_or_create(pool, "another_domain.tld".to_string()).await?;
|
let blocked_instance = Instance::read_or_create(pool, "another_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
|
@ -1359,7 +1494,12 @@ mod tests {
|
||||||
// no instance block, should return all posts
|
// no instance block, should return all posts
|
||||||
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_all = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![POST_FROM_BLOCKED_INSTANCE, POST_BY_BOT, POST],
|
vec![
|
||||||
|
POST_FROM_BLOCKED_INSTANCE,
|
||||||
|
POST_WITH_TAGS,
|
||||||
|
POST_BY_BOT,
|
||||||
|
POST
|
||||||
|
],
|
||||||
names(&post_listings_all)
|
names(&post_listings_all)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1372,7 +1512,10 @@ mod tests {
|
||||||
|
|
||||||
// now posts from communities on that instance should be hidden
|
// now posts from communities on that instance should be hidden
|
||||||
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_blocked));
|
assert_eq!(
|
||||||
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
|
names(&post_listings_blocked)
|
||||||
|
);
|
||||||
assert!(post_listings_blocked
|
assert!(post_listings_blocked
|
||||||
.iter()
|
.iter()
|
||||||
.all(|p| p.post.id != post_from_blocked_instance.id));
|
.all(|p| p.post.id != post_from_blocked_instance.id));
|
||||||
|
@ -1381,20 +1524,25 @@ mod tests {
|
||||||
InstanceBlock::unblock(pool, &block_form).await?;
|
InstanceBlock::unblock(pool, &block_form).await?;
|
||||||
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_blocked = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![POST_FROM_BLOCKED_INSTANCE, POST_BY_BOT, POST],
|
vec![
|
||||||
|
POST_FROM_BLOCKED_INSTANCE,
|
||||||
|
POST_WITH_TAGS,
|
||||||
|
POST_BY_BOT,
|
||||||
|
POST
|
||||||
|
],
|
||||||
names(&post_listings_blocked)
|
names(&post_listings_blocked)
|
||||||
);
|
);
|
||||||
|
|
||||||
Instance::delete(pool, blocked_instance.id).await?;
|
Instance::delete(pool, blocked_instance.id).await?;
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn pagination_includes_each_post_once() -> LemmyResult<()> {
|
async fn pagination_includes_each_post_once(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let community_form = CommunityInsertForm::new(
|
let community_form = CommunityInsertForm::new(
|
||||||
data.inserted_instance.id,
|
data.inserted_instance.id,
|
||||||
|
@ -1496,15 +1644,15 @@ mod tests {
|
||||||
assert_eq!(inserted_post_ids, listed_post_ids);
|
assert_eq!(inserted_post_ids, listed_post_ids);
|
||||||
|
|
||||||
Community::delete(pool, inserted_community.id).await?;
|
Community::delete(pool, inserted_community.id).await?;
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_hide_read() -> LemmyResult<()> {
|
async fn post_listings_hide_read(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let mut data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Make sure local user hides read posts
|
// Make sure local user hides read posts
|
||||||
let local_user_form = LocalUserUpdateForm {
|
let local_user_form = LocalUserUpdateForm {
|
||||||
|
@ -1520,7 +1668,7 @@ mod tests {
|
||||||
|
|
||||||
// Make sure you don't see the read post in the results
|
// Make sure you don't see the read post in the results
|
||||||
let post_listings_hide_read = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_hide_read = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(vec![POST], names(&post_listings_hide_read));
|
assert_eq!(vec![POST_WITH_TAGS, POST], names(&post_listings_hide_read));
|
||||||
|
|
||||||
// Test with the show_read override as true
|
// Test with the show_read override as true
|
||||||
let post_listings_show_read_true = PostQuery {
|
let post_listings_show_read_true = PostQuery {
|
||||||
|
@ -1530,7 +1678,7 @@ mod tests {
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vec![POST_BY_BOT, POST],
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
names(&post_listings_show_read_true)
|
names(&post_listings_show_read_true)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1541,16 +1689,19 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(vec![POST], names(&post_listings_show_read_false));
|
assert_eq!(
|
||||||
cleanup(data, pool).await
|
vec![POST_WITH_TAGS, POST],
|
||||||
|
names(&post_listings_show_read_false)
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_hide_hidden() -> LemmyResult<()> {
|
async fn post_listings_hide_hidden(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Mark a post as hidden
|
// Mark a post as hidden
|
||||||
PostHide::hide(
|
PostHide::hide(
|
||||||
|
@ -1562,7 +1713,10 @@ mod tests {
|
||||||
|
|
||||||
// Make sure you don't see the hidden post in the results
|
// Make sure you don't see the hidden post in the results
|
||||||
let post_listings_hide_hidden = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_hide_hidden = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(vec![POST], names(&post_listings_hide_hidden));
|
assert_eq!(
|
||||||
|
vec![POST_WITH_TAGS, POST],
|
||||||
|
names(&post_listings_hide_hidden)
|
||||||
|
);
|
||||||
|
|
||||||
// Make sure it does come back with the show_hidden option
|
// Make sure it does come back with the show_hidden option
|
||||||
let post_listings_show_hidden = PostQuery {
|
let post_listings_show_hidden = PostQuery {
|
||||||
|
@ -1573,20 +1727,23 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_show_hidden));
|
assert_eq!(
|
||||||
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
|
names(&post_listings_show_hidden)
|
||||||
|
);
|
||||||
|
|
||||||
// Make sure that hidden field is true.
|
// Make sure that hidden field is true.
|
||||||
assert!(&post_listings_show_hidden.first().is_some_and(|p| p.hidden));
|
assert!(&post_listings_show_hidden.get(1).is_some_and(|p| p.hidden));
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_hide_nsfw() -> LemmyResult<()> {
|
async fn post_listings_hide_nsfw(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Mark a post as nsfw
|
// Mark a post as nsfw
|
||||||
let update_form = PostUpdateForm {
|
let update_form = PostUpdateForm {
|
||||||
|
@ -1594,11 +1751,11 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
Post::update(pool, data.inserted_bot_post.id, &update_form).await?;
|
Post::update(pool, data.inserted_post_with_tags.id, &update_form).await?;
|
||||||
|
|
||||||
// Make sure you don't see the nsfw post in the regular results
|
// Make sure you don't see the nsfw post in the regular results
|
||||||
let post_listings_hide_nsfw = data.default_post_query().list(&data.site, pool).await?;
|
let post_listings_hide_nsfw = data.default_post_query().list(&data.site, pool).await?;
|
||||||
assert_eq!(vec![POST], names(&post_listings_hide_nsfw));
|
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_hide_nsfw));
|
||||||
|
|
||||||
// Make sure it does come back with the show_nsfw option
|
// Make sure it does come back with the show_nsfw option
|
||||||
let post_listings_show_nsfw = PostQuery {
|
let post_listings_show_nsfw = PostQuery {
|
||||||
|
@ -1609,22 +1766,19 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(vec![POST_BY_BOT, POST], names(&post_listings_show_nsfw));
|
assert_eq!(
|
||||||
|
vec![POST_WITH_TAGS, POST_BY_BOT, POST],
|
||||||
|
names(&post_listings_show_nsfw)
|
||||||
|
);
|
||||||
|
|
||||||
// Make sure that nsfw field is true.
|
// Make sure that nsfw field is true.
|
||||||
assert!(&post_listings_show_nsfw.first().is_some_and(|p| p.post.nsfw));
|
assert!(
|
||||||
|
&post_listings_show_nsfw
|
||||||
cleanup(data, pool).await
|
.first()
|
||||||
}
|
.ok_or(LemmyErrorType::NotFound)?
|
||||||
|
.post
|
||||||
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
.nsfw
|
||||||
let num_deleted = Post::delete(pool, data.inserted_post.id).await?;
|
);
|
||||||
Community::delete(pool, data.inserted_community.id).await?;
|
|
||||||
Person::delete(pool, data.local_user_view.person.id).await?;
|
|
||||||
Person::delete(pool, data.inserted_bot.id).await?;
|
|
||||||
Person::delete(pool, data.blocked_local_user_view.person.id).await?;
|
|
||||||
Instance::delete(pool, data.inserted_instance.id).await?;
|
|
||||||
assert_eq!(1, num_deleted);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1746,15 +1900,16 @@ mod tests {
|
||||||
hidden: false,
|
hidden: false,
|
||||||
saved: false,
|
saved: false,
|
||||||
creator_blocked: false,
|
creator_blocked: false,
|
||||||
|
tags: PostTags::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn local_only_instance() -> LemmyResult<()> {
|
async fn local_only_instance(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool_for_tests();
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
Community::update(
|
Community::update(
|
||||||
pool,
|
pool,
|
||||||
|
@ -1779,7 +1934,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(2, authenticated_query.len());
|
assert_eq!(3, authenticated_query.len());
|
||||||
|
|
||||||
let unauthenticated_post = PostView::read(pool, data.inserted_post.id, None, false).await;
|
let unauthenticated_post = PostView::read(pool, data.inserted_post.id, None, false).await;
|
||||||
assert!(unauthenticated_post.is_err());
|
assert!(unauthenticated_post.is_err());
|
||||||
|
@ -1793,16 +1948,15 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
assert!(authenticated_post.is_ok());
|
assert!(authenticated_post.is_ok());
|
||||||
|
|
||||||
cleanup(data, pool).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_local_user_banned_from_community() -> LemmyResult<()> {
|
async fn post_listing_local_user_banned_from_community(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Test that post view shows if local user is blocked from community
|
// Test that post view shows if local user is blocked from community
|
||||||
let banned_from_comm_person = PersonInsertForm::test_form(data.inserted_instance.id, "jill");
|
let banned_from_comm_person = PersonInsertForm::test_form(data.inserted_instance.id, "jill");
|
||||||
|
@ -1837,15 +1991,15 @@ mod tests {
|
||||||
assert!(post_view.banned_from_community);
|
assert!(post_view.banned_from_community);
|
||||||
|
|
||||||
Person::delete(pool, inserted_banned_from_comm_person.id).await?;
|
Person::delete(pool, inserted_banned_from_comm_person.id).await?;
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_local_user_not_banned_from_community() -> LemmyResult<()> {
|
async fn post_listing_local_user_not_banned_from_community(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
let post_view = PostView::read(
|
let post_view = PostView::read(
|
||||||
pool,
|
pool,
|
||||||
|
@ -1857,15 +2011,15 @@ mod tests {
|
||||||
|
|
||||||
assert!(!post_view.banned_from_community);
|
assert!(!post_view.banned_from_community);
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn speed_check() -> LemmyResult<()> {
|
async fn speed_check(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Make sure the post_view query is less than this time
|
// Make sure the post_view query is less than this time
|
||||||
let duration_max = Duration::from_millis(80);
|
let duration_max = Duration::from_millis(80);
|
||||||
|
@ -1913,15 +2067,15 @@ mod tests {
|
||||||
duration_max
|
duration_max
|
||||||
);
|
);
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listings_no_comments_only() -> LemmyResult<()> {
|
async fn post_listings_no_comments_only(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Create a comment for a post
|
// Create a comment for a post
|
||||||
let comment_form = CommentInsertForm::new(
|
let comment_form = CommentInsertForm::new(
|
||||||
|
@ -1941,17 +2095,20 @@ mod tests {
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(vec![POST_BY_BOT], names(&post_listings_no_comments));
|
assert_eq!(
|
||||||
|
vec![POST_WITH_TAGS, POST_BY_BOT],
|
||||||
|
names(&post_listings_no_comments)
|
||||||
|
);
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn post_listing_private_community() -> LemmyResult<()> {
|
async fn post_listing_private_community(data: &mut Data) -> LemmyResult<()> {
|
||||||
let pool = &build_db_pool()?;
|
let pool = &data.pool();
|
||||||
let pool = &mut pool.into();
|
let pool = &mut pool.into();
|
||||||
let mut data = init_data(pool).await?;
|
|
||||||
|
|
||||||
// Mark community as private
|
// Mark community as private
|
||||||
Community::update(
|
Community::update(
|
||||||
|
@ -2003,7 +2160,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(2, read_post_listing.len());
|
assert_eq!(3, read_post_listing.len());
|
||||||
let post_view = PostView::read(
|
let post_view = PostView::read(
|
||||||
pool,
|
pool,
|
||||||
data.inserted_post.id,
|
data.inserted_post.id,
|
||||||
|
@ -2030,7 +2187,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
.list(&data.site, pool)
|
.list(&data.site, pool)
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(2, read_post_listing.len());
|
assert_eq!(3, read_post_listing.len());
|
||||||
let post_view = PostView::read(
|
let post_view = PostView::read(
|
||||||
pool,
|
pool,
|
||||||
data.inserted_post.id,
|
data.inserted_post.id,
|
||||||
|
@ -2040,6 +2197,33 @@ mod tests {
|
||||||
.await;
|
.await;
|
||||||
assert!(post_view.is_ok());
|
assert!(post_view.is_ok());
|
||||||
|
|
||||||
cleanup(data, pool).await
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test_context(Data)]
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn post_tags_present(data: &mut Data) -> LemmyResult<()> {
|
||||||
|
let pool = &data.pool();
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
|
||||||
|
let post_view = PostView::read(
|
||||||
|
pool,
|
||||||
|
data.inserted_post_with_tags.id,
|
||||||
|
Some(&data.local_user_view.local_user),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert_eq!(2, post_view.tags.tags.len());
|
||||||
|
assert_eq!(data.tag_1.name, post_view.tags.tags[0].name);
|
||||||
|
assert_eq!(data.tag_2.name, post_view.tags.tags[1].name);
|
||||||
|
|
||||||
|
let all_posts = data.default_post_query().list(&data.site, pool).await?;
|
||||||
|
assert_eq!(2, all_posts[0].tags.tags.len()); // post with tags
|
||||||
|
assert_eq!(0, all_posts[1].tags.tags.len()); // bot post
|
||||||
|
assert_eq!(0, all_posts[2].tags.tags.len()); // normal post
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use diesel::Queryable;
|
use diesel::Queryable;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use diesel::{deserialize::FromSqlRow, expression::AsExpression, sql_types};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::{CommentAggregates, PersonAggregates, PostAggregates, SiteAggregates},
|
aggregates::structs::{CommentAggregates, PersonAggregates, PostAggregates, SiteAggregates},
|
||||||
source::{
|
source::{
|
||||||
|
@ -20,6 +22,7 @@ use lemmy_db_schema::{
|
||||||
private_message_report::PrivateMessageReport,
|
private_message_report::PrivateMessageReport,
|
||||||
registration_application::RegistrationApplication,
|
registration_application::RegistrationApplication,
|
||||||
site::Site,
|
site::Site,
|
||||||
|
tag::Tag,
|
||||||
},
|
},
|
||||||
SubscribedType,
|
SubscribedType,
|
||||||
};
|
};
|
||||||
|
@ -151,6 +154,7 @@ pub struct PostView {
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub my_vote: Option<i16>,
|
pub my_vote: Option<i16>,
|
||||||
pub unread_comments: i64,
|
pub unread_comments: i64,
|
||||||
|
pub tags: PostTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||||
|
@ -237,3 +241,12 @@ pub struct LocalImageView {
|
||||||
pub local_image: LocalImage,
|
pub local_image: LocalImage,
|
||||||
pub person: Person,
|
pub person: Person,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, PartialEq, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS, FromSqlRow, AsExpression))]
|
||||||
|
#[serde(transparent)]
|
||||||
|
#[cfg_attr(feature = "full", diesel(sql_type = Nullable<sql_types::Json>))]
|
||||||
|
/// we wrap this in a struct so we can implement FromSqlRow<Json> for it
|
||||||
|
pub struct PostTags {
|
||||||
|
pub tags: Vec<Tag>,
|
||||||
|
}
|
||||||
|
|
|
@ -188,7 +188,7 @@ impl CommunityView {
|
||||||
let is_mod =
|
let is_mod =
|
||||||
CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await;
|
CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await;
|
||||||
if is_mod.is_ok()
|
if is_mod.is_ok()
|
||||||
|| PersonView::read(pool, person_id)
|
|| PersonView::read(pool, person_id, false)
|
||||||
.await
|
.await
|
||||||
.is_ok_and(|t| t.is_admin)
|
.is_ok_and(|t| t.is_admin)
|
||||||
{
|
{
|
||||||
|
@ -206,7 +206,7 @@ impl CommunityView {
|
||||||
let is_mod_of_any =
|
let is_mod_of_any =
|
||||||
CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await;
|
CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await;
|
||||||
if is_mod_of_any.is_ok()
|
if is_mod_of_any.is_ok()
|
||||||
|| PersonView::read(pool, person_id)
|
|| PersonView::read(pool, person_id, false)
|
||||||
.await
|
.await
|
||||||
.is_ok_and(|t| t.is_admin)
|
.is_ok_and(|t| t.is_admin)
|
||||||
{
|
{
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue