Merge remote-tracking branch 'origin/main' into persistent-queue

This commit is contained in:
phiresky 2023-08-02 23:01:50 +00:00
commit 2d3ad1b076
427 changed files with 25309 additions and 15179 deletions

View File

@ -3,6 +3,22 @@
variables: variables:
- &muslrust_image "clux/muslrust:1.70.0" - &muslrust_image "clux/muslrust:1.70.0"
- &slow_check_paths
- path:
# rust source code
- "**/*.rs"
- "**/Cargo.toml"
- "Cargo.lock"
# database migrations
- "migrations"
# typescript tests
- "api_tests"
# config files and scripts used by ci
- ".woodpecker.yml"
- ".rustfmt.toml"
- "scripts/update_config_defaults.sh"
- "diesel.toml"
- ".gitmodules"
# Broken for cron jobs currently, see # Broken for cron jobs currently, see
# https://github.com/woodpecker-ci/woodpecker/issues/1716 # https://github.com/woodpecker-ci/woodpecker/issues/1716
@ -48,6 +64,7 @@ pipeline:
- "api_tests/node_modules" - "api_tests/node_modules"
secrets: secrets:
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET] [MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
when: *slow_check_paths
toml_fmt: toml_fmt:
image: tamasfe/taplo:0.8.1 image: tamasfe/taplo:0.8.1
@ -65,8 +82,18 @@ pipeline:
- rustup toolchain install nightly-2023-07-10 - rustup toolchain install nightly-2023-07-10
- rustup component add rustfmt --toolchain nightly-2023-07-10 - rustup component add rustfmt --toolchain nightly-2023-07-10
- cargo +nightly-2023-07-10 fmt -- --check - cargo +nightly-2023-07-10 fmt -- --check
# when:
# platform: linux/amd64 sql_fmt:
image: alpine:3
commands:
- apk add bash wget perl make git
- wget https://github.com/darold/pgFormatter/archive/refs/tags/v5.5.tar.gz
- tar xzf v5.5.tar.gz
- cd pgFormatter-5.5
- perl Makefile.PL
- make && make install
- cd ..
- ./scripts/./sql_format_check.sh
# make sure api builds with default features (used by other crates relying on lemmy api) # make sure api builds with default features (used by other crates relying on lemmy api)
check_api_common_default_features: check_api_common_default_features:
@ -75,8 +102,7 @@ pipeline:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- cargo check --package lemmy_api_common - cargo check --package lemmy_api_common
# when: when: *slow_check_paths
# platform: linux/amd64
lemmy_api_common_doesnt_depend_on_diesel: lemmy_api_common_doesnt_depend_on_diesel:
image: *muslrust_image image: *muslrust_image
@ -84,8 +110,7 @@ pipeline:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
- "! cargo tree -p lemmy_api_common --no-default-features -i diesel" - "! cargo tree -p lemmy_api_common --no-default-features -i diesel"
# when: when: *slow_check_paths
# platform: linux/amd64
lemmy_api_common_works_with_wasm: lemmy_api_common_works_with_wasm:
image: *muslrust_image image: *muslrust_image
@ -94,6 +119,7 @@ pipeline:
commands: commands:
- "rustup target add wasm32-unknown-unknown" - "rustup target add wasm32-unknown-unknown"
- "cargo check --target wasm32-unknown-unknown -p lemmy_api_common" - "cargo check --target wasm32-unknown-unknown -p lemmy_api_common"
when: *slow_check_paths
check_defaults_hjson_updated: check_defaults_hjson_updated:
image: *muslrust_image image: *muslrust_image
@ -103,8 +129,7 @@ pipeline:
- export LEMMY_CONFIG_LOCATION=./config/config.hjson - export LEMMY_CONFIG_LOCATION=./config/config.hjson
- ./scripts/update_config_defaults.sh config/defaults_current.hjson - ./scripts/update_config_defaults.sh config/defaults_current.hjson
- diff config/defaults.hjson config/defaults_current.hjson - diff config/defaults.hjson config/defaults_current.hjson
# when: when: *slow_check_paths
# platform: linux/amd64
check_diesel_schema: check_diesel_schema:
image: willsquire/diesel-cli image: willsquire/diesel-cli
@ -115,6 +140,7 @@ pipeline:
- diesel migration run - diesel migration run
- diesel print-schema --config-file=diesel.toml > tmp.schema - diesel print-schema --config-file=diesel.toml > tmp.schema
- diff tmp.schema crates/db_schema/src/schema.rs - diff tmp.schema crates/db_schema/src/schema.rs
when: *slow_check_paths
check_diesel_migration_revertable: check_diesel_migration_revertable:
image: willsquire/diesel-cli image: willsquire/diesel-cli
@ -124,13 +150,14 @@ pipeline:
commands: commands:
- diesel migration run - diesel migration run
- diesel migration redo - diesel migration redo
when: *slow_check_paths
cargo_clippy: cargo_clippy:
image: *muslrust_image image: *muslrust_image
environment: environment:
CARGO_HOME: .cargo CARGO_HOME: .cargo
commands: commands:
# when adding new clippy lints, make sure to also add them in scripts/fix-clippy.sh # when adding new clippy lints, make sure to also add them in scripts/lint.sh
- rustup component add clippy - rustup component add clippy
- cargo clippy --workspace --tests --all-targets --features console -- - cargo clippy --workspace --tests --all-targets --features console --
-D warnings -D deprecated -D clippy::perf -D clippy::complexity -D warnings -D deprecated -D clippy::perf -D clippy::complexity
@ -147,8 +174,7 @@ pipeline:
-D clippy::needless_collect -D clippy::needless_collect
-D clippy::unwrap_used -D clippy::unwrap_used
-D clippy::indexing_slicing -D clippy::indexing_slicing
# when: when: *slow_check_paths
# platform: linux/amd64
cargo_test: cargo_test:
image: *muslrust_image image: *muslrust_image
@ -159,8 +185,7 @@ pipeline:
commands: commands:
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson - export LEMMY_CONFIG_LOCATION=../../config/config.hjson
- cargo test --workspace --no-fail-fast - cargo test --workspace --no-fail-fast
# when: when: *slow_check_paths
# platform: linux/amd64
cargo_build: cargo_build:
image: *muslrust_image image: *muslrust_image
@ -169,8 +194,7 @@ pipeline:
commands: commands:
- cargo build - cargo build
- mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server - mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server
# when: when: *slow_check_paths
# platform: linux/amd64
run_federation_tests: run_federation_tests:
image: node:alpine image: node:alpine
@ -183,8 +207,7 @@ pipeline:
- cd api_tests/ - cd api_tests/
- yarn - yarn
- yarn api-test - yarn api-test
# when: when: *slow_check_paths
# platform: linux/amd64
rebuild-cache: rebuild-cache:
image: meltwater/drone-cache:v1 image: meltwater/drone-cache:v1
@ -208,6 +231,7 @@ pipeline:
- "api_tests/node_modules" - "api_tests/node_modules"
secrets: secrets:
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET] [MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
when: *slow_check_paths
publish_release_docker: publish_release_docker:
image: woodpeckerci/plugin-docker-buildx image: woodpeckerci/plugin-docker-buildx
@ -257,5 +281,3 @@ services:
environment: environment:
POSTGRES_USER: lemmy POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password POSTGRES_PASSWORD: password
# when:
# platform: linux/amd64

2
Cargo.lock generated
View File

@ -2728,7 +2728,6 @@ dependencies = [
"serde_json", "serde_json",
"serde_with", "serde_with",
"serial_test", "serial_test",
"sha2",
"strum_macros", "strum_macros",
"task-local-extensions", "task-local-extensions",
"tokio", "tokio",
@ -2761,7 +2760,6 @@ dependencies = [
"serde_json", "serde_json",
"serde_with", "serde_with",
"serial_test", "serial_test",
"sha2",
"strum", "strum",
"strum_macros", "strum_macros",
"tokio", "tokio",

View File

@ -108,7 +108,6 @@ diesel_ltree = "0.3.0"
typed-builder = "0.15.0" typed-builder = "0.15.0"
serial_test = "2.0.0" serial_test = "2.0.0"
tokio = { version = "1.29.1", features = ["full"] } tokio = { version = "1.29.1", features = ["full"] }
sha2 = "0.10.7"
regex = "1.9.0" regex = "1.9.0"
once_cell = "1.18.0" once_cell = "1.18.0"
diesel-derive-newtype = "2.1.0" diesel-derive-newtype = "2.1.0"

View File

@ -9,7 +9,7 @@
"scripts": { "scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'", "lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
"fix": "prettier --write src && eslint --fix src", "fix": "prettier --write src && eslint --fix src",
"api-test": "jest -i follow.spec.ts && jest -i src/post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts" "api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",

View File

@ -30,9 +30,6 @@ else
done done
fi fi
echo "killall existing lemmy_server processes"
killall lemmy_server || true
echo "$PWD" echo "$PWD"
echo "start alpha" echo "start alpha"

View File

@ -7,14 +7,14 @@ pushd ..
cargo build cargo build
rm target/lemmy_server || true rm target/lemmy_server || true
cp target/debug/lemmy_server target/lemmy_server cp target/debug/lemmy_server target/lemmy_server
killall -s1 lemmy_server || true
./api_tests/prepare-drone-federation-test.sh ./api_tests/prepare-drone-federation-test.sh
popd popd
yarn yarn
yarn api-test || true yarn api-test || true
killall -s1 lemmy_server killall -s1 lemmy_server || true
for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do
psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE" psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE"
done done

View File

@ -30,10 +30,12 @@ import {
getCommentParentId, getCommentParentId,
resolveCommunity, resolveCommunity,
getPersonDetails, getPersonDetails,
getReplies,
getUnreadCount,
} from "./shared"; } from "./shared";
import { CommentView } from "lemmy-js-client/dist/types/CommentView"; import { CommentView } from "lemmy-js-client/dist/types/CommentView";
let postRes: PostResponse; let postOnAlphaRes: PostResponse;
beforeAll(async () => { beforeAll(async () => {
await setupLogins(); await setupLogins();
@ -42,7 +44,7 @@ beforeAll(async () => {
await followBeta(gamma); await followBeta(gamma);
let betaCommunity = (await resolveBetaCommunity(alpha)).community; let betaCommunity = (await resolveBetaCommunity(alpha)).community;
if (betaCommunity) { if (betaCommunity) {
postRes = await createPost(alpha, betaCommunity.community.id); postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
} }
}); });
@ -65,7 +67,7 @@ function assertCommentFederation(
} }
test("Create a comment", async () => { test("Create a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
expect(commentRes.comment_view.comment.content).toBeDefined(); expect(commentRes.comment_view.comment.content).toBeDefined();
expect(commentRes.comment_view.community.local).toBe(false); expect(commentRes.comment_view.community.local).toBe(false);
expect(commentRes.comment_view.creator.local).toBe(true); expect(commentRes.comment_view.creator.local).toBe(true);
@ -87,7 +89,7 @@ test("Create a comment in a non-existent post", async () => {
}); });
test("Update a comment", async () => { test("Update a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// Federate the comment first // Federate the comment first
let betaComment = ( let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment) await resolveComment(beta, commentRes.comment_view.comment)
@ -113,7 +115,7 @@ test("Update a comment", async () => {
test("Delete a comment", async () => { test("Delete a comment", async () => {
// creating a comment on alpha (remote from home of community) // creating a comment on alpha (remote from home of community)
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// Find the comment on beta (home of community) // Find the comment on beta (home of community)
let betaComment = ( let betaComment = (
@ -167,7 +169,7 @@ test("Delete a comment", async () => {
}); });
test.skip("Remove a comment from admin and community on the same instance", async () => { test.skip("Remove a comment from admin and community on the same instance", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// Get the id for beta // Get the id for beta
let betaCommentId = ( let betaCommentId = (
@ -189,13 +191,14 @@ test.skip("Remove a comment from admin and community on the same instance", asyn
); );
expect(refetchedPostComments.comments[0].comment.removed).toBe(true); expect(refetchedPostComments.comments[0].comment.removed).toBe(true);
// beta will unremove the comment
let unremoveCommentRes = await removeComment(beta, false, betaCommentId); let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
expect(unremoveCommentRes.comment_view.comment.removed).toBe(false); expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
// Make sure that comment is unremoved on beta // Make sure that comment is unremoved on alpha
let refetchedPostComments2 = await getComments( let refetchedPostComments2 = await getComments(
alpha, alpha,
postRes.post_view.post.id, postOnAlphaRes.post_view.post.id,
); );
expect(refetchedPostComments2.comments[0].comment.removed).toBe(false); expect(refetchedPostComments2.comments[0].comment.removed).toBe(false);
assertCommentFederation( assertCommentFederation(
@ -249,7 +252,7 @@ test("Remove a comment from admin and community on different instance", async ()
}); });
test("Unlike a comment", async () => { test("Unlike a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// Lemmy automatically creates 1 like (vote) by author of comment. // Lemmy automatically creates 1 like (vote) by author of comment.
// Make sure that comment is liked (voted up) on gamma, downstream peer // Make sure that comment is liked (voted up) on gamma, downstream peer
@ -286,7 +289,7 @@ test("Unlike a comment", async () => {
}); });
test("Federated comment like", async () => { test("Federated comment like", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// Find the comment on beta // Find the comment on beta
let betaComment = ( let betaComment = (
@ -301,13 +304,14 @@ test("Federated comment like", async () => {
expect(like.comment_view.counts.score).toBe(2); expect(like.comment_view.counts.score).toBe(2);
// Get the post from alpha, check the likes // Get the post from alpha, check the likes
let postComments = await getComments(alpha, postRes.post_view.post.id); let postComments = await getComments(alpha, postOnAlphaRes.post_view.post.id);
expect(postComments.comments[0].counts.score).toBe(2); expect(postComments.comments[0].counts.score).toBe(2);
}); });
test("Reply to a comment", async () => { test("Reply to a comment from another instance, get notification", async () => {
// Create a comment on alpha, find it on beta // Create a root-level trunk-branch comment on alpha
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// find that comment id on beta
let betaComment = ( let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment) await resolveComment(beta, commentRes.comment_view.comment)
).comment; ).comment;
@ -316,9 +320,7 @@ test("Reply to a comment", async () => {
throw "Missing beta comment"; throw "Missing beta comment";
} }
// find that comment id on beta // Reply from beta, extending the branch
// Reply from beta
let replyRes = await createComment( let replyRes = await createComment(
beta, beta,
betaComment.post.id, betaComment.post.id,
@ -332,11 +334,13 @@ test("Reply to a comment", async () => {
); );
expect(replyRes.comment_view.counts.score).toBe(1); expect(replyRes.comment_view.counts.score).toBe(1);
// Make sure that comment is seen on alpha // Make sure that reply comment is seen on alpha
// TODO not sure why, but a searchComment back to alpha, for the ap_id of betas // TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
// comment, isn't working. // comment, isn't working.
// let searchAlpha = await searchComment(alpha, replyRes.comment); // let searchAlpha = await searchComment(alpha, replyRes.comment);
let postComments = await getComments(alpha, postRes.post_view.post.id); let postComments = await getComments(alpha, postOnAlphaRes.post_view.post.id);
// Note: in Lemmy 0.18.3 pre-release this is coming up 7
expect(postComments.comments.length).toBeGreaterThanOrEqual(2);
let alphaComment = postComments.comments[0]; let alphaComment = postComments.comments[0];
expect(alphaComment.comment.content).toBeDefined(); expect(alphaComment.comment.content).toBeDefined();
expect(getCommentParentId(alphaComment.comment)).toBe( expect(getCommentParentId(alphaComment.comment)).toBe(
@ -346,15 +350,33 @@ test("Reply to a comment", async () => {
expect(alphaComment.creator.local).toBe(false); expect(alphaComment.creator.local).toBe(false);
expect(alphaComment.counts.score).toBe(1); expect(alphaComment.counts.score).toBe(1);
assertCommentFederation(alphaComment, replyRes.comment_view); assertCommentFederation(alphaComment, replyRes.comment_view);
// Did alpha get notified of the reply from beta?
let alphaUnreadCountRes = await getUnreadCount(alpha);
expect(alphaUnreadCountRes.replies).toBe(1);
// check inbox of replies on alpha, fetching read/unread both
let alphaRepliesRes = await getReplies(alpha);
expect(alphaRepliesRes.replies.length).toBe(1);
expect(alphaRepliesRes.replies[0].comment.content).toBeDefined();
expect(alphaRepliesRes.replies[0].community.local).toBe(false);
expect(alphaRepliesRes.replies[0].creator.local).toBe(false);
expect(alphaRepliesRes.replies[0].counts.score).toBe(1);
// ToDo: interesting alphaRepliesRes.replies[0].comment_reply.id is 1, meaning? how did that come about?
expect(alphaRepliesRes.replies[0].comment.id).toBe(alphaComment.comment.id);
// this is a new notification, getReplies fetch was for read/unread both, confirm it is unread.
expect(alphaRepliesRes.replies[0].comment_reply.read).toBe(false);
assertCommentFederation(alphaRepliesRes.replies[0], replyRes.comment_view);
}); });
test("Mention beta", async () => { test("Mention beta from alpha", async () => {
// Create a mention on alpha // Create a new branch, trunk-level comment branch, from alpha instance
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// Create a reply comment to previous comment, this has a mention in body
let mentionContent = "A test mention of @lemmy_beta@lemmy-beta:8551"; let mentionContent = "A test mention of @lemmy_beta@lemmy-beta:8551";
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let mentionRes = await createComment( let mentionRes = await createComment(
alpha, alpha,
postRes.post_view.post.id, postOnAlphaRes.post_view.post.id,
commentRes.comment_view.comment.id, commentRes.comment_view.comment.id,
mentionContent, mentionContent,
); );
@ -363,15 +385,44 @@ test("Mention beta", async () => {
expect(mentionRes.comment_view.creator.local).toBe(true); expect(mentionRes.comment_view.creator.local).toBe(true);
expect(mentionRes.comment_view.counts.score).toBe(1); expect(mentionRes.comment_view.counts.score).toBe(1);
// get beta's localized copy of the alpha post
let betaPost = (await resolvePost(beta, postOnAlphaRes.post_view.post)).post;
if (!betaPost) {
throw "unable to locate post on beta";
}
expect(betaPost.post.ap_id).toBe(postOnAlphaRes.post_view.post.ap_id);
expect(betaPost.post.name).toBe(postOnAlphaRes.post_view.post.name);
// Make sure that both new comments are seen on beta and have parent/child relationship
let betaPostComments = await getComments(beta, betaPost.post.id);
expect(betaPostComments.comments.length).toBeGreaterThanOrEqual(2);
// the trunk-branch root comment will be older than the mention reply comment, so index 1
let betaRootComment = betaPostComments.comments[1];
// the trunk-branch root comment should not have a parent
expect(getCommentParentId(betaRootComment.comment)).toBeUndefined();
expect(betaRootComment.comment.content).toBeDefined();
// the mention reply comment should have parent that points to the branch root level comment
expect(getCommentParentId(betaPostComments.comments[0].comment)).toBe(
betaPostComments.comments[1].comment.id,
);
expect(betaRootComment.community.local).toBe(true);
expect(betaRootComment.creator.local).toBe(false);
expect(betaRootComment.counts.score).toBe(1);
assertCommentFederation(betaRootComment, commentRes.comment_view);
let mentionsRes = await getMentions(beta); let mentionsRes = await getMentions(beta);
expect(mentionsRes.mentions[0].comment.content).toBeDefined(); expect(mentionsRes.mentions[0].comment.content).toBeDefined();
expect(mentionsRes.mentions[0].community.local).toBe(true); expect(mentionsRes.mentions[0].community.local).toBe(true);
expect(mentionsRes.mentions[0].creator.local).toBe(false); expect(mentionsRes.mentions[0].creator.local).toBe(false);
expect(mentionsRes.mentions[0].counts.score).toBe(1); expect(mentionsRes.mentions[0].counts.score).toBe(1);
// the reply comment with mention should be the most fresh, newest, index 0
expect(mentionsRes.mentions[0].person_mention.comment_id).toBe(
betaPostComments.comments[0].comment.id,
);
}); });
test("Comment Search", async () => { test("Comment Search", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id); let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
let betaComment = ( let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment) await resolveComment(beta, commentRes.comment_view.comment)
).comment; ).comment;
@ -496,13 +547,13 @@ test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
).toBe(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 postRes = await createPost(beta, 2); let postOnBetaRes = await createPost(beta, 2);
expect(postRes.post_view.post.name).toBeDefined(); expect(postOnBetaRes.post_view.post.name).toBeDefined();
let parentCommentContent = "An invisible top level comment from beta"; let parentCommentContent = "An invisible top level comment from beta";
let parentCommentRes = await createComment( let parentCommentRes = await createComment(
beta, beta,
postRes.post_view.post.id, postOnBetaRes.post_view.post.id,
undefined, undefined,
parentCommentContent, parentCommentContent,
); );
@ -514,7 +565,7 @@ test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
let childCommentContent = "An invisible child comment from beta"; let childCommentContent = "An invisible child comment from beta";
let childCommentRes = await createComment( let childCommentRes = await createComment(
beta, beta,
postRes.post_view.post.id, postOnBetaRes.post_view.post.id,
parentCommentRes.comment_view.comment.id, parentCommentRes.comment_view.comment.id,
childCommentContent, childCommentContent,
); );
@ -537,7 +588,8 @@ test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent); expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent);
// Get the post from alpha // Get the post from alpha
let alphaPostB = (await resolvePost(alpha, postRes.post_view.post)).post; let alphaPostB = (await resolvePost(alpha, postOnBetaRes.post_view.post))
.post;
if (!alphaPostB) { if (!alphaPostB) {
throw "Missing alpha post B"; throw "Missing alpha post B";
} }
@ -564,10 +616,11 @@ test("Report a comment", async () => {
if (!betaCommunity) { if (!betaCommunity) {
throw "Missing beta community"; throw "Missing beta community";
} }
let postRes = (await createPost(beta, betaCommunity.community.id)).post_view let postOnBetaRes = (await createPost(beta, betaCommunity.community.id))
.post; .post_view.post;
expect(postRes).toBeDefined(); expect(postOnBetaRes).toBeDefined();
let commentRes = (await createComment(beta, postRes.id)).comment_view.comment; let commentRes = (await createComment(beta, postOnBetaRes.id)).comment_view
.comment;
expect(commentRes).toBeDefined(); expect(commentRes).toBeDefined();
let alphaComment = (await resolveComment(alpha, commentRes)).comment?.comment; let alphaComment = (await resolveComment(alpha, commentRes)).comment?.comment;

View File

@ -1,4 +1,11 @@
import { LemmyHttp } from "lemmy-js-client"; import {
GetReplies,
GetRepliesResponse,
GetUnreadCount,
GetUnreadCountResponse,
LemmyHttp,
LocalUser,
} 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";
@ -325,6 +332,24 @@ export async function getComments(
return api.client.getComments(form); return api.client.getComments(form);
} }
export async function getUnreadCount(
api: API,
): Promise<GetUnreadCountResponse> {
let form: GetUnreadCount = {
auth: api.auth,
};
return api.client.getUnreadCount(form);
}
export async function getReplies(api: API): Promise<GetRepliesResponse> {
let form: GetReplies = {
sort: "New",
unread_only: false,
auth: api.auth,
};
return api.client.getReplies(form);
}
export async function resolveComment( export async function resolveComment(
api: API, api: API,
comment: Comment, comment: Comment,

View File

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_comment_response, build_response::build_comment_response,
comment::{CommentResponse, CreateCommentLike}, comment::{CommentResponse, CreateCommentLike},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, check_downvotes_enabled, local_user_view_from_jwt}, utils::{check_community_ban, check_downvotes_enabled, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -17,70 +18,80 @@ use lemmy_db_schema::{
}; };
use lemmy_db_views::structs::{CommentView, LocalUserView}; use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use std::ops::Deref;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for CreateCommentLike { pub async fn like_comment(
type Response = CommentResponse; data: Json<CreateCommentLike>,
context: Data<LemmyContext>,
) -> Result<Json<CommentResponse>, LemmyError> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let mut recipient_ids = Vec::<LocalUserId>::new();
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> {
let data: &CreateCommentLike = self;
let local_site = LocalSite::read(&mut context.pool()).await?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let mut recipient_ids = Vec::<LocalUserId>::new(); // Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, &local_site)?;
// Don't do a downvote if site has downvotes disabled let comment_id = data.comment_id;
check_downvotes_enabled(data.score, &local_site)?; let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
let comment_id = data.comment_id; check_community_ban(
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; local_user_view.person.id,
orig_comment.community.id,
&mut context.pool(),
)
.await?;
check_community_ban( // Add parent poster or commenter to recipients
local_user_view.person.id, let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
orig_comment.community.id, if let Ok(reply) = comment_reply {
&mut context.pool(), let recipient_id = reply.recipient_id;
) if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
.await?; {
recipient_ids.push(local_recipient.local_user.id);
// Add parent poster or commenter to recipients
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
if let Ok(reply) = comment_reply {
let recipient_id = reply.recipient_id;
if let Ok(local_recipient) =
LocalUserView::read_person(&mut context.pool(), recipient_id).await
{
recipient_ids.push(local_recipient.local_user.id);
}
} }
}
let like_form = CommentLikeForm { let like_form = CommentLikeForm {
comment_id: data.comment_id, comment_id: data.comment_id,
post_id: orig_comment.post.id, post_id: orig_comment.post.id,
person_id: local_user_view.person.id, person_id: local_user_view.person.id,
score: data.score, score: data.score,
}; };
// Remove any likes first // Remove any likes first
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
CommentLike::remove(&mut context.pool(), person_id, comment_id).await?; CommentLike::remove(&mut context.pool(), person_id, comment_id).await?;
// Only add the like if the score isnt 0 // Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add { if do_add {
CommentLike::like(&mut context.pool(), &like_form) CommentLike::like(&mut context.pool(), &like_form)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntLikeComment)?; .with_lemmy_type(LemmyErrorType::CouldntLikeComment)?;
} }
ActivityChannel::submit_activity(
SendActivityData::LikePostOrComment(
orig_comment.comment.ap_id,
local_user_view.person.clone(),
orig_comment.community,
data.score,
),
&context,
)
.await?;
Ok(Json(
build_comment_response( build_comment_response(
context, context.deref(),
comment_id, comment_id,
Some(local_user_view), Some(local_user_view),
None, None,
recipient_ids, recipient_ids,
) )
.await .await?,
} ))
} }

View File

@ -1,8 +1,10 @@
use crate::{check_report_reason, Perform}; use crate::check_report_reason;
use actix_web::web::Data; use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
comment::{CommentReportResponse, CreateCommentReport}, comment::{CommentReportResponse, CreateCommentReport},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
local_user_view_from_jwt, local_user_view_from_jwt,
@ -21,55 +23,60 @@ use lemmy_db_views::structs::{CommentReportView, CommentView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
/// Creates a comment report and notifies the moderators of the community /// Creates a comment report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for CreateCommentReport { pub async fn create_comment_report(
type Response = CommentReportResponse; data: Json<CreateCommentReport>,
context: Data<LemmyContext>,
) -> Result<Json<CommentReportResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let reason = sanitize_html(data.reason.trim());
async fn perform( check_report_reason(&reason, &local_site)?;
&self,
context: &Data<LemmyContext>,
) -> Result<CommentReportResponse, LemmyError> {
let data: &CreateCommentReport = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = sanitize_html(self.reason.trim()); let person_id = local_user_view.person.id;
check_report_reason(&reason, &local_site)?; let comment_id = data.comment_id;
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
let person_id = local_user_view.person.id; check_community_ban(person_id, comment_view.community.id, &mut context.pool()).await?;
let comment_id = data.comment_id;
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
check_community_ban(person_id, comment_view.community.id, &mut context.pool()).await?; let report_form = CommentReportForm {
creator_id: person_id,
comment_id,
original_comment_text: comment_view.comment.content,
reason,
};
let report_form = CommentReportForm { let report = CommentReport::report(&mut context.pool(), &report_form)
creator_id: person_id, .await
comment_id, .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
original_comment_text: comment_view.comment.content,
reason,
};
let report = CommentReport::report(&mut context.pool(), &report_form) let comment_report_view =
.await CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let comment_report_view = // Email the admins
CommentReportView::read(&mut context.pool(), report.id, person_id).await?; if local_site.reports_email_admins {
send_new_report_email_to_admins(
// Email the admins &comment_report_view.creator.name,
if local_site.reports_email_admins { &comment_report_view.comment_creator.name,
send_new_report_email_to_admins( &mut context.pool(),
&comment_report_view.creator.name, context.settings(),
&comment_report_view.comment_creator.name, )
&mut context.pool(), .await?;
context.settings(),
)
.await?;
}
Ok(CommentReportResponse {
comment_report_view,
})
} }
ActivityChannel::submit_activity(
SendActivityData::CreateReport(
comment_view.comment.ap_id.inner().clone(),
local_user_view.person,
comment_view.community,
data.reason.clone(),
),
&context,
)
.await?;
Ok(Json(CommentReportResponse {
comment_report_view,
}))
} }

View File

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
community::{AddModToCommunity, AddModToCommunityResponse}, community::{AddModToCommunity, AddModToCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_mod_or_admin, local_user_view_from_jwt}, utils::{is_mod_or_admin, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,58 +16,62 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for AddModToCommunity { pub async fn add_mod_to_community(
type Response = AddModToCommunityResponse; data: Json<AddModToCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<AddModToCommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let community_id = data.community_id;
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<AddModToCommunityResponse, LemmyError> {
let data: &AddModToCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community_id = data.community_id; // Verify that only mods or admins can add mod
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?;
// Verify that only mods or admins can add mod let community = Community::read(&mut context.pool(), community_id).await?;
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?; if local_user_view.person.admin && !community.local {
let community = Community::read(&mut context.pool(), community_id).await?; return Err(LemmyErrorType::NotAModerator)?;
if local_user_view.person.admin && !community.local {
return Err(LemmyErrorType::NotAModerator)?;
}
// Update in local database
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
person_id: data.person_id,
};
if data.added {
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
} else {
CommunityModerator::leave(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
}
// Mod tables
let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
removed: Some(!data.added),
};
ModAddCommunity::create(&mut context.pool(), &form).await?;
// Note: in case a remote mod is added, this returns the old moderators list, it will only get
// updated once we receive an activity from the community (like `Announce/Add/Moderator`)
let community_id = data.community_id;
let moderators =
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
Ok(AddModToCommunityResponse { moderators })
} }
// Update in local database
let community_moderator_form = CommunityModeratorForm {
community_id: data.community_id,
person_id: data.person_id,
};
if data.added {
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
} else {
CommunityModerator::leave(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
}
// Mod tables
let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
removed: Some(!data.added),
};
ModAddCommunity::create(&mut context.pool(), &form).await?;
// Note: in case a remote mod is added, this returns the old moderators list, it will only get
// updated once we receive an activity from the community (like `Announce/Add/Moderator`)
let community_id = data.community_id;
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
ActivityChannel::submit_activity(
SendActivityData::AddModToCommunity(
local_user_view.person,
data.community_id,
data.person_id,
data.added,
),
&context,
)
.await?;
Ok(Json(AddModToCommunityResponse { moderators }))
} }

View File

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
community::{BanFromCommunity, BanFromCommunityResponse}, community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
is_mod_or_admin, is_mod_or_admin,
local_user_view_from_jwt, local_user_view_from_jwt,
@ -28,77 +29,85 @@ use lemmy_utils::{
utils::{time::naive_from_unix, validation::is_valid_body_field}, utils::{time::naive_from_unix, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for BanFromCommunity { pub async fn ban_from_community(
type Response = BanFromCommunityResponse; data: Json<BanFromCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let banned_person_id = data.person_id;
async fn perform( let remove_data = data.remove_data.unwrap_or(false);
&self, let expires = data.expires.map(naive_from_unix);
context: &Data<LemmyContext>,
) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community_id = data.community_id; // Verify that only mods or admins can ban
let banned_person_id = data.person_id; is_mod_or_admin(
let remove_data = data.remove_data.unwrap_or(false); &mut context.pool(),
let expires = data.expires.map(naive_from_unix); local_user_view.person.id,
data.community_id,
)
.await?;
is_valid_body_field(&data.reason, false)?;
// Verify that only mods or admins can ban let community_user_ban_form = CommunityPersonBanForm {
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?; community_id: data.community_id,
is_valid_body_field(&data.reason, false)?; person_id: data.person_id,
expires: Some(expires),
};
let community_user_ban_form = CommunityPersonBanForm { if data.ban {
CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
// Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id, community_id: data.community_id,
person_id: data.person_id, person_id: banned_person_id,
expires: Some(expires), pending: false,
}; };
if data.ban { CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form) .await
.await .ok();
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?; } else {
CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form)
// Also unsubscribe them from the community, if they are subscribed .await
let community_follower_form = CommunityFollowerForm { .with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
community_id: data.community_id,
person_id: banned_person_id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await
.ok();
} else {
CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?;
}
// Remove/Restore their data if that's desired
if remove_data {
remove_user_data_in_community(community_id, banned_person_id, &mut context.pool()).await?;
}
// Mod tables
let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
let person_id = data.person_id;
let person_view = PersonView::read(&mut context.pool(), person_id).await?;
Ok(BanFromCommunityResponse {
person_view,
banned: data.ban,
})
} }
// Remove/Restore their data if that's desired
if remove_data {
remove_user_data_in_community(data.community_id, banned_person_id, &mut context.pool()).await?;
}
// Mod tables
let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
community_id: data.community_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::BanFromCommunity(
local_user_view.person,
data.community_id,
person_view.person.clone(),
data.0.clone(),
),
&context,
)
.await?;
Ok(Json(BanFromCommunityResponse {
person_view,
banned: data.ban,
}))
} }

View File

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
community::{BlockCommunity, BlockCommunityResponse}, community::{BlockCommunity, BlockCommunityResponse},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt, utils::local_user_view_from_jwt,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,52 +16,56 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityView; use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for BlockCommunity { pub async fn block_community(
type Response = BlockCommunityResponse; data: Json<BlockCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<BlockCommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let community_id = data.community_id;
async fn perform( let person_id = local_user_view.person.id;
&self, let community_block_form = CommunityBlockForm {
context: &Data<LemmyContext>, person_id,
) -> Result<BlockCommunityResponse, LemmyError> { community_id,
let data: &BlockCommunity = self; };
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community_id = data.community_id; if data.block {
let person_id = local_user_view.person.id; CommunityBlock::block(&mut context.pool(), &community_block_form)
let community_block_form = CommunityBlockForm { .await
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
// Also, unfollow the community, and send a federated unfollow
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
person_id, person_id,
community_id, pending: false,
}; };
if data.block { CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
CommunityBlock::block(&mut context.pool(), &community_block_form) .await
.await .ok();
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?; } else {
CommunityBlock::unblock(&mut context.pool(), &community_block_form)
// Also, unfollow the community, and send a federated unfollow .await
let community_follower_form = CommunityFollowerForm { .with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
community_id: data.community_id,
person_id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await
.ok();
} else {
CommunityBlock::unblock(&mut context.pool(), &community_block_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
}
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), None).await?;
Ok(BlockCommunityResponse {
blocked: data.block,
community_view,
})
} }
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), None).await?;
ActivityChannel::submit_activity(
SendActivityData::FollowCommunity(
community_view.community.clone(),
local_user_view.person.clone(),
false,
),
&context,
)
.await?;
Ok(Json(BlockCommunityResponse {
blocked: data.block,
community_view,
}))
} }

View File

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
community::{CommunityResponse, FollowCommunity}, community::{CommunityResponse, FollowCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt}, utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,54 +16,56 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityView; use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for FollowCommunity { pub async fn follow_community(
type Response = CommunityResponse; data: Json<FollowCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let community = Community::read(&mut context.pool(), data.community_id).await?;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { let mut community_follower_form = CommunityFollowerForm {
let data: &FollowCommunity = self; community_id: community.id,
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; person_id: local_user_view.person.id,
pending: false,
};
let community_id = data.community_id; if data.follow {
let community = Community::read(&mut context.pool(), community_id).await?; if community.local {
let mut community_follower_form = CommunityFollowerForm { check_community_ban(local_user_view.person.id, community.id, &mut context.pool()).await?;
community_id: data.community_id, check_community_deleted_or_removed(community.id, &mut context.pool()).await?;
person_id: local_user_view.person.id,
pending: false,
};
if data.follow { CommunityFollower::follow(&mut context.pool(), &community_follower_form)
if community.local { .await
check_community_ban(local_user_view.person.id, community_id, &mut context.pool()).await?; .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
check_community_deleted_or_removed(community_id, &mut context.pool()).await?; } else {
// Mark as pending, the actual federation activity is sent via `SendActivity` handler
CommunityFollower::follow(&mut context.pool(), &community_follower_form) community_follower_form.pending = true;
.await CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} else {
// Mark as pending, the actual federation activity is sent via `SendActivity` handler
community_follower_form.pending = true;
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
}
}
if !data.follow {
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await .await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} }
let community_id = data.community_id;
let person_id = local_user_view.person.id;
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), None).await?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(Self::Response {
community_view,
discussion_languages,
})
} }
if !data.follow {
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
}
ActivityChannel::submit_activity(
SendActivityData::FollowCommunity(community, local_user_view.person.clone(), data.follow),
&context,
)
.await?;
let community_id = data.community_id;
let person_id = local_user_view.person.id;
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), None).await?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(Json(CommunityResponse {
community_view,
discussion_languages,
}))
} }

View File

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, HideCommunity}, community::{CommunityResponse, HideCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt}, utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,36 +16,38 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for HideCommunity { pub async fn hide_community(
type Response = CommunityResponse; data: Json<HideCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
// Verify its a admin (only admin can hide or unhide it)
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
is_admin(&local_user_view)?;
#[tracing::instrument(skip(context))] let community_form = CommunityUpdateForm::builder()
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { .hidden(Some(data.hidden))
let data: &HideCommunity = self; .build();
// Verify its a admin (only admin can hide or unhide it) let mod_hide_community_form = ModHideCommunityForm {
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; community_id: data.community_id,
is_admin(&local_user_view)?; mod_person_id: local_user_view.person.id,
reason: sanitize_html_opt(&data.reason),
hidden: Some(data.hidden),
};
let community_form = CommunityUpdateForm::builder() let community_id = data.community_id;
.hidden(Some(data.hidden)) let community = Community::update(&mut context.pool(), community_id, &community_form)
.build(); .await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?;
let mod_hide_community_form = ModHideCommunityForm { ModHideCommunity::create(&mut context.pool(), &mod_hide_community_form).await?;
community_id: data.community_id,
mod_person_id: local_user_view.person.id,
reason: sanitize_html_opt(&data.reason),
hidden: Some(data.hidden),
};
let community_id = data.community_id; ActivityChannel::submit_activity(
Community::update(&mut context.pool(), community_id, &community_form) SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
.await &context,
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?; )
.await?;
ModHideCommunity::create(&mut context.pool(), &mod_hide_community_form).await?; build_community_response(&context, local_user_view, community_id).await
build_community_response(context, local_user_view, community_id).await
}
} }

View File

@ -1,6 +1,6 @@
mod add_mod; pub mod add_mod;
mod ban; pub mod ban;
mod block; pub mod block;
mod follow; pub mod follow;
mod hide; pub mod hide;
mod transfer; pub mod transfer;

View File

@ -1,8 +1,9 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{BanPerson, BanPersonResponse}, person::{BanPerson, BanPersonResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt, remove_user_data, sanitize_html_opt}, utils::{is_admin, local_user_view_from_jwt, remove_user_data, sanitize_html_opt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -17,65 +18,68 @@ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType}, error::{LemmyError, LemmyErrorExt, LemmyErrorType},
utils::{time::naive_from_unix, validation::is_valid_body_field}, utils::{time::naive_from_unix, validation::is_valid_body_field},
}; };
#[tracing::instrument(skip(context))]
pub async fn ban_from_site(
data: Json<BanPerson>,
context: Data<LemmyContext>,
) -> Result<Json<BanPersonResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[async_trait::async_trait(?Send)] // Make sure user is an admin
impl Perform for BanPerson { is_admin(&local_user_view)?;
type Response = BanPersonResponse;
#[tracing::instrument(skip(context))] is_valid_body_field(&data.reason, false)?;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<BanPersonResponse, LemmyError> {
let data: &BanPerson = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
// Make sure user is an admin let expires = data.expires.map(naive_from_unix);
is_admin(&local_user_view)?;
is_valid_body_field(&data.reason, false)?; let person = Person::update(
&mut context.pool(),
data.person_id,
&PersonUpdateForm::builder()
.banned(Some(data.ban))
.ban_expires(Some(expires))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;
let ban = data.ban; // Remove their data if that's desired
let banned_person_id = data.person_id; let remove_data = data.remove_data.unwrap_or(false);
let expires = data.expires.map(naive_from_unix); if remove_data {
remove_user_data(
let person = Person::update( person.id,
&mut context.pool(), &mut context.pool(),
banned_person_id, context.settings(),
&PersonUpdateForm::builder() context.client(),
.banned(Some(ban))
.ban_expires(Some(expires))
.build(),
) )
.await .await?;
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;
// Remove their data if that's desired
let remove_data = data.remove_data.unwrap_or(false);
if remove_data {
remove_user_data(
person.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
}
// Mod tables
let form = ModBanForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBan::create(&mut context.pool(), &form).await?;
let person_id = data.person_id;
let person_view = PersonView::read(&mut context.pool(), person_id).await?;
Ok(BanPersonResponse {
person_view,
banned: data.ban,
})
} }
// Mod tables
let form = ModBanForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
reason: sanitize_html_opt(&data.reason),
banned: Some(data.ban),
expires,
};
ModBan::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
ActivityChannel::submit_activity(
SendActivityData::BanFromSite(
local_user_view.person,
person_view.person.clone(),
data.0.clone(),
),
&context,
)
.await?;
Ok(Json(BanPersonResponse {
person_view,
banned: data.ban,
}))
} }

View File

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{FeaturePost, PostResponse}, post::{FeaturePost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -22,67 +23,65 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for FeaturePost { pub async fn feature_post(
type Response = PostResponse; data: Json<FeaturePost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &FeaturePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; check_community_ban(
let orig_post = Post::read(&mut context.pool(), post_id).await?; local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
check_community_ban( if data.feature_type == PostFeatureType::Community {
// Verify that only the mods can feature in community
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id, local_user_view.person.id,
orig_post.community_id, orig_post.community_id,
&mut context.pool(),
) )
.await?; .await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?; } else {
is_admin(&local_user_view)?;
if data.feature_type == PostFeatureType::Community {
// Verify that only the mods can feature in community
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
} else {
is_admin(&local_user_view)?;
}
// Update the post
let post_id = data.post_id;
let new_post: PostUpdateForm = if data.feature_type == PostFeatureType::Community {
PostUpdateForm::builder()
.featured_community(Some(data.featured))
.build()
} else {
PostUpdateForm::builder()
.featured_local(Some(data.featured))
.build()
};
Post::update(&mut context.pool(), post_id, &new_post).await?;
// Mod tables
let form = ModFeaturePostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
featured: data.featured,
is_featured_community: data.feature_type == PostFeatureType::Community,
};
ModFeaturePost::create(&mut context.pool(), &form).await?;
build_post_response(
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }
// Update the post
let post_id = data.post_id;
let new_post: PostUpdateForm = if data.feature_type == PostFeatureType::Community {
PostUpdateForm::builder()
.featured_community(Some(data.featured))
.build()
} else {
PostUpdateForm::builder()
.featured_local(Some(data.featured))
.build()
};
let post = Post::update(&mut context.pool(), post_id, &new_post).await?;
// Mod tables
let form = ModFeaturePostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
featured: data.featured,
is_featured_community: data.feature_type == PostFeatureType::Community,
};
ModFeaturePost::create(&mut context.pool(), &form).await?;
let person_id = local_user_view.person.id;
ActivityChannel::submit_activity(
SendActivityData::FeaturePost(post, local_user_view.person, data.featured),
&context,
)
.await?;
build_post_response(&context, orig_post.community_id, person_id, post_id).await
} }

View File

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{CreatePostLike, PostResponse}, post::{CreatePostLike, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -14,66 +15,76 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
community::Community,
local_site::LocalSite, local_site::LocalSite,
post::{Post, PostLike, PostLikeForm}, post::{Post, PostLike, PostLikeForm},
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use std::ops::Deref;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for CreatePostLike { pub async fn like_post(
type Response = PostResponse; data: Json<CreatePostLike>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] // Don't do a downvote if site has downvotes disabled
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { check_downvotes_enabled(data.score, &local_site)?;
let data: &CreatePostLike = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Don't do a downvote if site has downvotes disabled // Check for a community ban
check_downvotes_enabled(data.score, &local_site)?; let post_id = data.post_id;
let post = Post::read(&mut context.pool(), post_id).await?;
// Check for a community ban check_community_ban(
let post_id = data.post_id; local_user_view.person.id,
let post = Post::read(&mut context.pool(), post_id).await?; post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?;
check_community_ban( let like_form = PostLikeForm {
local_user_view.person.id, post_id: data.post_id,
post.community_id, person_id: local_user_view.person.id,
&mut context.pool(), score: data.score,
) };
.await?;
check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?;
let like_form = PostLikeForm { // Remove any likes first
post_id: data.post_id, let person_id = local_user_view.person.id;
person_id: local_user_view.person.id,
score: data.score,
};
// Remove any likes first PostLike::remove(&mut context.pool(), person_id, post_id).await?;
let person_id = local_user_view.person.id;
PostLike::remove(&mut context.pool(), person_id, post_id).await?; // Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
// Only add the like if the score isnt 0 if do_add {
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); PostLike::like(&mut context.pool(), &like_form)
if do_add { .await
PostLike::like(&mut context.pool(), &like_form) .with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
.await
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
}
// Mark the post as read
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
build_post_response(
context,
post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }
// Mark the post as read
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
ActivityChannel::submit_activity(
SendActivityData::LikePostOrComment(
post.ap_id,
local_user_view.person.clone(),
Community::read(&mut context.pool(), post.community_id).await?,
data.score,
),
&context,
)
.await?;
build_post_response(
context.deref(),
post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }

View File

@ -1,9 +1,10 @@
use crate::Perform; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{LockPost, PostResponse}, post::{LockPost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
check_community_deleted_or_removed, check_community_deleted_or_removed,
@ -20,58 +21,56 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for LockPost { pub async fn lock_post(
type Response = PostResponse; data: Json<LockPost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &LockPost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; check_community_ban(
let orig_post = Post::read(&mut context.pool(), post_id).await?; local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
check_community_ban( // Verify that only the mods can lock
local_user_view.person.id, is_mod_or_admin(
orig_post.community_id, &mut context.pool(),
&mut context.pool(), local_user_view.person.id,
) orig_post.community_id,
.await?; )
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?; .await?;
// Verify that only the mods can lock // Update the post
is_mod_or_admin( let post_id = data.post_id;
&mut context.pool(), let locked = data.locked;
local_user_view.person.id, let post = Post::update(
orig_post.community_id, &mut context.pool(),
) post_id,
.await?; &PostUpdateForm::builder().locked(Some(locked)).build(),
)
.await?;
// Update the post // Mod tables
let post_id = data.post_id; let form = ModLockPostForm {
let locked = data.locked; mod_person_id: local_user_view.person.id,
Post::update( post_id: data.post_id,
&mut context.pool(), locked: Some(locked),
post_id, };
&PostUpdateForm::builder().locked(Some(locked)).build(), ModLockPost::create(&mut context.pool(), &form).await?;
)
.await?;
// Mod tables let person_id = local_user_view.person.id;
let form = ModLockPostForm { ActivityChannel::submit_activity(
mod_person_id: local_user_view.person.id, SendActivityData::LockPost(post, local_user_view.person, data.locked),
post_id: data.post_id, &context,
locked: Some(locked), )
}; .await?;
ModLockPost::create(&mut context.pool(), &form).await?;
build_post_response( build_post_response(&context, orig_post.community_id, person_id, post_id).await
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
}
} }

View File

@ -1,6 +1,6 @@
mod feature; pub mod feature;
mod get_link_metadata; pub mod get_link_metadata;
mod like; pub mod like;
mod lock; pub mod lock;
mod mark_read; pub mod mark_read;
mod save; pub mod save;

View File

@ -1,8 +1,10 @@
use crate::{check_report_reason, Perform}; use crate::check_report_reason;
use actix_web::web::Data; use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{CreatePostReport, PostReportResponse}, post::{CreatePostReport, PostReportResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
local_user_view_from_jwt, local_user_view_from_jwt,
@ -21,51 +23,59 @@ use lemmy_db_views::structs::{PostReportView, PostView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
/// Creates a post report and notifies the moderators of the community /// Creates a post report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl Perform for CreatePostReport { pub async fn create_post_report(
type Response = PostReportResponse; data: Json<CreatePostReport>,
context: Data<LemmyContext>,
) -> Result<Json<PostReportResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let reason = sanitize_html(data.reason.trim());
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostReportResponse, LemmyError> { check_report_reason(&reason, &local_site)?;
let data: &CreatePostReport = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = sanitize_html(self.reason.trim()); let person_id = local_user_view.person.id;
check_report_reason(&reason, &local_site)?; let post_id = data.post_id;
let post_view = PostView::read(&mut context.pool(), post_id, None, None).await?;
let person_id = local_user_view.person.id; check_community_ban(person_id, post_view.community.id, &mut context.pool()).await?;
let post_id = data.post_id;
let post_view = PostView::read(&mut context.pool(), post_id, None, None).await?;
check_community_ban(person_id, post_view.community.id, &mut context.pool()).await?; let report_form = PostReportForm {
creator_id: person_id,
post_id,
original_post_name: post_view.post.name,
original_post_url: post_view.post.url,
original_post_body: post_view.post.body,
reason,
};
let report_form = PostReportForm { let report = PostReport::report(&mut context.pool(), &report_form)
creator_id: person_id, .await
post_id, .with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
original_post_name: post_view.post.name,
original_post_url: post_view.post.url,
original_post_body: post_view.post.body,
reason,
};
let report = PostReport::report(&mut context.pool(), &report_form) let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?;
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?; // Email the admins
if local_site.reports_email_admins {
// Email the admins send_new_report_email_to_admins(
if local_site.reports_email_admins { &post_report_view.creator.name,
send_new_report_email_to_admins( &post_report_view.post_creator.name,
&post_report_view.creator.name, &mut context.pool(),
&post_report_view.post_creator.name, context.settings(),
&mut context.pool(), )
context.settings(), .await?;
)
.await?;
}
Ok(PostReportResponse { post_report_view })
} }
ActivityChannel::submit_activity(
SendActivityData::CreateReport(
post_view.post.ap_id.inner().clone(),
local_user_view.person,
post_view.community,
data.reason.clone(),
),
&context,
)
.await?;
Ok(Json(PostReportResponse { post_report_view }))
} }

View File

@ -1,3 +1,3 @@
mod create; pub mod create;
mod list; pub mod list;
mod resolve; pub mod resolve;

View File

@ -5,7 +5,7 @@ use crate::{
post::PostResponse, post::PostResponse,
utils::{check_person_block, get_interface_language, is_mod_or_admin, send_email_to_user}, utils::{check_person_block, get_interface_language, is_mod_or_admin, send_email_to_user},
}; };
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
source::{ source::{
@ -39,10 +39,10 @@ pub async fn build_comment_response(
} }
pub async fn build_community_response( pub async fn build_community_response(
context: &Data<LemmyContext>, context: &LemmyContext,
local_user_view: LocalUserView, local_user_view: LocalUserView,
community_id: CommunityId, community_id: CommunityId,
) -> Result<CommunityResponse, LemmyError> { ) -> Result<Json<CommunityResponse>, LemmyError> {
let is_mod_or_admin = let is_mod_or_admin =
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id) is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id)
.await .await
@ -57,10 +57,10 @@ pub async fn build_community_response(
.await?; .await?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?; let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(CommunityResponse { Ok(Json(CommunityResponse {
community_view, community_view,
discussion_languages, discussion_languages,
}) }))
} }
pub async fn build_post_response( pub async fn build_post_response(
@ -68,7 +68,7 @@ pub async fn build_post_response(
community_id: CommunityId, community_id: CommunityId,
person_id: PersonId, person_id: PersonId,
post_id: PostId, post_id: PostId,
) -> Result<PostResponse, LemmyError> { ) -> Result<Json<PostResponse>, LemmyError> {
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person_id, community_id) let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person_id, community_id)
.await .await
.is_ok(); .is_ok();
@ -79,7 +79,7 @@ pub async fn build_post_response(
Some(is_mod_or_admin), Some(is_mod_or_admin),
) )
.await?; .await?;
Ok(PostResponse { post_view }) Ok(Json(PostResponse { post_view }))
} }
// TODO: this function is a mess and should be split up to handle email seperately // TODO: this function is a mess and should be split up to handle email seperately

View File

@ -1,7 +1,22 @@
use crate::context::LemmyContext; use crate::{
community::BanFromCommunity,
context::LemmyContext,
person::BanPerson,
post::{DeletePost, RemovePost},
};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use lemmy_db_schema::source::{comment::Comment, post::Post}; use lemmy_db_schema::{
newtypes::{CommunityId, DbUrl, PersonId},
source::{
comment::Comment,
community::Community,
person::Person,
post::Post,
private_message::PrivateMessage,
},
};
use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::{error::LemmyResult, SYNCHRONOUS_FEDERATION}; use lemmy_utils::{error::LemmyResult, SYNCHRONOUS_FEDERATION};
use once_cell::sync::{Lazy, OnceCell}; use once_cell::sync::{Lazy, OnceCell};
use tokio::{ use tokio::{
@ -12,6 +27,7 @@ use tokio::{
}, },
task::JoinHandle, task::JoinHandle,
}; };
use url::Url;
type MatchOutgoingActivitiesBoxed = type MatchOutgoingActivitiesBoxed =
Box<for<'a> fn(SendActivityData, &'a Data<LemmyContext>) -> BoxFuture<'a, LemmyResult<()>>>; Box<for<'a> fn(SendActivityData, &'a Data<LemmyContext>) -> BoxFuture<'a, LemmyResult<()>>>;
@ -22,7 +38,28 @@ pub static MATCH_OUTGOING_ACTIVITIES: OnceCell<MatchOutgoingActivitiesBoxed> = O
#[derive(Debug)] #[derive(Debug)]
pub enum SendActivityData { pub enum SendActivityData {
CreatePost(Post), CreatePost(Post),
UpdatePost(Post),
DeletePost(Post, Person, DeletePost),
RemovePost(Post, Person, RemovePost),
LockPost(Post, Person, bool),
FeaturePost(Post, Person, bool),
CreateComment(Comment), CreateComment(Comment),
UpdateComment(Comment),
DeleteComment(Comment, Person, Community),
RemoveComment(Comment, Person, Community, Option<String>),
LikePostOrComment(DbUrl, Person, Community, i16),
FollowCommunity(Community, Person, bool),
UpdateCommunity(Person, Community),
DeleteCommunity(Person, Community, bool),
RemoveCommunity(Person, Community, Option<String>, bool),
AddModToCommunity(Person, CommunityId, PersonId, bool),
BanFromCommunity(Person, CommunityId, Person, BanFromCommunity),
BanFromSite(Person, Person, BanPerson),
CreatePrivateMessage(PrivateMessageView),
UpdatePrivateMessage(PrivateMessageView),
DeletePrivateMessage(Person, PrivateMessage, bool),
DeleteUser(Person),
CreateReport(Url, Person, Community, String),
} }
// TODO: instead of static, move this into LemmyContext. make sure that stopping the process with // TODO: instead of static, move this into LemmyContext. make sure that stopping the process with

View File

@ -342,9 +342,8 @@ pub async fn send_password_reset_email(
let token = uuid::Uuid::new_v4().to_string(); let token = uuid::Uuid::new_v4().to_string();
// Insert the row // Insert the row
let token2 = token.clone();
let local_user_id = user.local_user.id; let local_user_id = user.local_user.id;
PasswordResetRequest::create_token(pool, local_user_id, &token2).await?; PasswordResetRequest::create_token(pool, local_user_id, token.clone()).await?;
let email = &user.local_user.email.clone().expect("email"); let email = &user.local_user.email.clone().expect("email");
let lang = get_interface_language(user); let lang = get_interface_language(user);

View File

@ -36,7 +36,6 @@ use lemmy_utils::{
validation::is_valid_body_field, validation::is_valid_body_field,
}, },
}; };
use std::ops::Deref;
const MAX_COMMENT_DEPTH_LIMIT: usize = 100; const MAX_COMMENT_DEPTH_LIMIT: usize = 100;
@ -196,7 +195,7 @@ pub async fn create_comment(
Ok(Json( Ok(Json(
build_comment_response( build_comment_response(
context.deref(), &context,
inserted_comment.id, inserted_comment.id,
Some(local_user_view), Some(local_user_view),
data.form_id.clone(), data.form_id.clone(),

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::{build_comment_response, send_local_notifs}, build_response::{build_comment_response, send_local_notifs},
comment::{CommentResponse, DeleteComment}, comment::{CommentResponse, DeleteComment},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, local_user_view_from_jwt}, utils::{check_community_ban, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,66 +16,75 @@ use lemmy_db_schema::{
}; };
use lemmy_db_views::structs::CommentView; use lemmy_db_views::structs::CommentView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use std::ops::Deref;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeleteComment { pub async fn delete_comment(
type Response = CommentResponse; data: Json<DeleteComment>,
context: Data<LemmyContext>,
) -> Result<Json<CommentResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let comment_id = data.comment_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> { let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
let data: &DeleteComment = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let comment_id = data.comment_id; // Dont delete it if its already been deleted.
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; if orig_comment.comment.deleted == data.deleted {
return Err(LemmyErrorType::CouldntUpdateComment)?;
}
// Dont delete it if its already been deleted. check_community_ban(
if orig_comment.comment.deleted == data.deleted { local_user_view.person.id,
return Err(LemmyErrorType::CouldntUpdateComment)?; orig_comment.community.id,
} &mut context.pool(),
)
.await?;
check_community_ban( // Verify that only the creator can delete
local_user_view.person.id, if local_user_view.person.id != orig_comment.creator.id {
orig_comment.community.id, return Err(LemmyErrorType::NoCommentEditAllowed)?;
&mut context.pool(), }
)
.await?;
// Verify that only the creator can delete // Do the delete
if local_user_view.person.id != orig_comment.creator.id { let deleted = data.deleted;
return Err(LemmyErrorType::NoCommentEditAllowed)?; let updated_comment = Comment::update(
} &mut context.pool(),
comment_id,
&CommentUpdateForm::builder().deleted(Some(deleted)).build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
// Do the delete let post_id = updated_comment.post_id;
let deleted = data.deleted; let post = Post::read(&mut context.pool(), post_id).await?;
let updated_comment = Comment::update( let recipient_ids = send_local_notifs(
&mut context.pool(), vec![],
comment_id, &updated_comment,
&CommentUpdateForm::builder().deleted(Some(deleted)).build(), &local_user_view.person,
) &post,
.await false,
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; &context,
)
.await?;
let updated_comment_id = updated_comment.id;
let post_id = updated_comment.post_id; ActivityChannel::submit_activity(
let post = Post::read(&mut context.pool(), post_id).await?; SendActivityData::DeleteComment(
let recipient_ids = send_local_notifs( updated_comment,
vec![], local_user_view.person.clone(),
&updated_comment, orig_comment.community,
&local_user_view.person, ),
&post, &context,
false, )
context, .await?;
)
.await?;
Ok(Json(
build_comment_response( build_comment_response(
context.deref(), &context,
updated_comment.id, updated_comment_id,
Some(local_user_view), Some(local_user_view),
None, None,
recipient_ids, recipient_ids,
) )
.await .await?,
} ))
} }

View File

@ -7,7 +7,6 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::source::local_site::LocalSite; use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use std::ops::Deref;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn get_comment( pub async fn get_comment(
@ -20,6 +19,6 @@ pub async fn get_comment(
check_private_instance(&local_user_view, &local_site)?; check_private_instance(&local_user_view, &local_site)?;
Ok(Json( Ok(Json(
build_comment_response(context.deref(), data.id, local_user_view, None, vec![]).await?, build_comment_response(&context, data.id, local_user_view, None, vec![]).await?,
)) ))
} }

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::{build_comment_response, send_local_notifs}, build_response::{build_comment_response, send_local_notifs},
comment::{CommentResponse, RemoveComment}, comment::{CommentResponse, RemoveComment},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt}, utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -16,73 +17,83 @@ use lemmy_db_schema::{
}; };
use lemmy_db_views::structs::CommentView; use lemmy_db_views::structs::CommentView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use std::ops::Deref;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for RemoveComment { pub async fn remove_comment(
type Response = CommentResponse; data: Json<RemoveComment>,
context: Data<LemmyContext>,
) -> Result<Json<CommentResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let comment_id = data.comment_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> { let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
let data: &RemoveComment = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let comment_id = data.comment_id; check_community_ban(
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; local_user_view.person.id,
orig_comment.community.id,
&mut context.pool(),
)
.await?;
check_community_ban( // Verify that only a mod or admin can remove
local_user_view.person.id, is_mod_or_admin(
orig_comment.community.id, &mut context.pool(),
&mut context.pool(), local_user_view.person.id,
) orig_comment.community.id,
.await?; )
.await?;
// Verify that only a mod or admin can remove // Do the remove
is_mod_or_admin( let removed = data.removed;
&mut context.pool(), let updated_comment = Comment::update(
local_user_view.person.id, &mut context.pool(),
orig_comment.community.id, comment_id,
) &CommentUpdateForm::builder().removed(Some(removed)).build(),
.await?; )
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
// Do the remove // Mod tables
let removed = data.removed; let form = ModRemoveCommentForm {
let updated_comment = Comment::update( mod_person_id: local_user_view.person.id,
&mut context.pool(), comment_id: data.comment_id,
comment_id, removed: Some(removed),
&CommentUpdateForm::builder().removed(Some(removed)).build(), reason: data.reason.clone(),
) };
.await ModRemoveComment::create(&mut context.pool(), &form).await?;
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
// Mod tables let post_id = updated_comment.post_id;
let form = ModRemoveCommentForm { let post = Post::read(&mut context.pool(), post_id).await?;
mod_person_id: local_user_view.person.id, let recipient_ids = send_local_notifs(
comment_id: data.comment_id, vec![],
removed: Some(removed), &updated_comment,
reason: data.reason.clone(), &local_user_view.person.clone(),
}; &post,
ModRemoveComment::create(&mut context.pool(), &form).await?; false,
&context,
)
.await?;
let updated_comment_id = updated_comment.id;
let post_id = updated_comment.post_id; ActivityChannel::submit_activity(
let post = Post::read(&mut context.pool(), post_id).await?; SendActivityData::RemoveComment(
let recipient_ids = send_local_notifs( updated_comment,
vec![], local_user_view.person.clone(),
&updated_comment, orig_comment.community,
&local_user_view.person.clone(), data.reason.clone(),
&post, ),
false, &context,
context, )
) .await?;
.await?;
Ok(Json(
build_comment_response( build_comment_response(
context.deref(), &context,
updated_comment.id, updated_comment_id,
Some(local_user_view), Some(local_user_view),
None, None,
recipient_ids, recipient_ids,
) )
.await .await?,
} ))
} }

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::{build_comment_response, send_local_notifs}, build_response::{build_comment_response, send_local_notifs},
comment::{CommentResponse, EditComment}, comment::{CommentResponse, EditComment},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
local_site_to_slur_regex, local_site_to_slur_regex,
@ -29,79 +30,83 @@ use lemmy_utils::{
validation::is_valid_body_field, validation::is_valid_body_field,
}, },
}; };
use std::ops::Deref;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditComment { pub async fn update_comment(
type Response = CommentResponse; data: Json<EditComment>,
context: Data<LemmyContext>,
) -> Result<Json<CommentResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let comment_id = data.comment_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommentResponse, LemmyError> { let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
let data: &EditComment = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let comment_id = data.comment_id; check_community_ban(
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?; local_user_view.person.id,
orig_comment.community.id,
&mut context.pool(),
)
.await?;
check_community_ban( // Verify that only the creator can edit
local_user_view.person.id, if local_user_view.person.id != orig_comment.creator.id {
orig_comment.community.id, return Err(LemmyErrorType::NoCommentEditAllowed)?;
&mut context.pool(), }
)
.await?;
// Verify that only the creator can edit let language_id = data.language_id;
if local_user_view.person.id != orig_comment.creator.id { CommunityLanguage::is_allowed_community_language(
return Err(LemmyErrorType::NoCommentEditAllowed)?; &mut context.pool(),
} language_id,
orig_comment.community.id,
)
.await?;
let language_id = self.language_id; // Update the Content
CommunityLanguage::is_allowed_community_language( let content = data
&mut context.pool(), .content
language_id, .as_ref()
orig_comment.community.id, .map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site)));
) is_valid_body_field(&content, false)?;
.await?; let content = sanitize_html_opt(&content);
// Update the Content let comment_id = data.comment_id;
let content = data let form = CommentUpdateForm::builder()
.content .content(content)
.as_ref() .language_id(data.language_id)
.map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site))); .updated(Some(Some(naive_now())))
is_valid_body_field(&content, false)?; .build();
let content = sanitize_html_opt(&content); let updated_comment = Comment::update(&mut context.pool(), comment_id, &form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
let comment_id = data.comment_id; // Do the mentions / recipients
let form = CommentUpdateForm::builder() let updated_comment_content = updated_comment.content.clone();
.content(content) let mentions = scrape_text_for_mentions(&updated_comment_content);
.language_id(data.language_id) let recipient_ids = send_local_notifs(
.updated(Some(Some(naive_now()))) mentions,
.build(); &updated_comment,
let updated_comment = Comment::update(&mut context.pool(), comment_id, &form) &local_user_view.person,
.await &orig_comment.post,
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; false,
&context,
)
.await?;
// Do the mentions / recipients ActivityChannel::submit_activity(
let updated_comment_content = updated_comment.content.clone(); SendActivityData::UpdateComment(updated_comment.clone()),
let mentions = scrape_text_for_mentions(&updated_comment_content); &context,
let recipient_ids = send_local_notifs( )
mentions, .await?;
&updated_comment,
&local_user_view.person,
&orig_comment.post,
false,
context,
)
.await?;
Ok(Json(
build_comment_response( build_comment_response(
context.deref(), &context,
updated_comment.id, updated_comment.id,
Some(local_user_view), Some(local_user_view),
self.form_id.clone(), data.form_id.clone(),
recipient_ids, recipient_ids,
) )
.await .await?,
} ))
} }

View File

@ -1,6 +1,5 @@
use crate::PerformCrud; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use activitypub_federation::http_signatures::generate_actor_keypair; use actix_web::web::Json;
use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, CreateCommunity}, community::{CommunityResponse, CreateCommunity},
@ -42,107 +41,104 @@ use lemmy_utils::{
}, },
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for CreateCommunity { pub async fn create_community(
type Response = CommunityResponse; data: Json<CreateCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
#[tracing::instrument(skip(context))] if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { return Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?;
let data: &CreateCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
return Err(LemmyErrorType::OnlyAdminsCanCreateCommunities)?;
}
// Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite_to_url_create(&data.icon)?;
let banner = diesel_option_overwrite_to_url_create(&data.banner)?;
let name = sanitize_html(&data.name);
let title = sanitize_html(&data.title);
let description = sanitize_html_opt(&data.description);
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&name, &slur_regex)?;
check_slurs(&title, &slur_regex)?;
check_slurs_opt(&description, &slur_regex)?;
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
is_valid_body_field(&data.description, false)?;
// Double check for duplicate community actor_ids
let community_actor_id = generate_local_apub_endpoint(
EndpointType::Community,
&data.name,
&context.settings().get_protocol_and_hostname(),
)?;
let community_dupe =
Community::read_from_apub_id(&mut context.pool(), &community_actor_id).await?;
if community_dupe.is_some() {
return Err(LemmyErrorType::CommunityAlreadyExists)?;
}
// When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?;
let community_form = CommunityInsertForm::builder()
.name(name)
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.actor_id(Some(community_actor_id.clone()))
.private_key(Some(keypair.private_key))
.public_key(keypair.public_key)
.followers_url(Some(generate_followers_url(&community_actor_id)?))
.inbox_url(Some(generate_inbox_url(&community_actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&community_actor_id)?))
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.instance_id(site_view.site.instance_id)
.build();
let inserted_community = Community::create(&mut context.pool(), &community_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityAlreadyExists)?;
// The community creator becomes a moderator
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
};
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
// Follow your own community
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
pending: false,
};
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
// Update the discussion_languages if that's provided
let community_id = inserted_community.id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
build_community_response(context, local_user_view, community_id).await
} }
// Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite_to_url_create(&data.icon)?;
let banner = diesel_option_overwrite_to_url_create(&data.banner)?;
let name = sanitize_html(&data.name);
let title = sanitize_html(&data.title);
let description = sanitize_html_opt(&data.description);
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&name, &slur_regex)?;
check_slurs(&title, &slur_regex)?;
check_slurs_opt(&description, &slur_regex)?;
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
is_valid_body_field(&data.description, false)?;
// Double check for duplicate community actor_ids
let community_actor_id = generate_local_apub_endpoint(
EndpointType::Community,
&data.name,
&context.settings().get_protocol_and_hostname(),
)?;
let community_dupe =
Community::read_from_apub_id(&mut context.pool(), &community_actor_id).await?;
if community_dupe.is_some() {
return Err(LemmyErrorType::CommunityAlreadyExists)?;
}
// When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?;
let community_form = CommunityInsertForm::builder()
.name(name)
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.actor_id(Some(community_actor_id.clone()))
.private_key(Some(keypair.private_key))
.public_key(keypair.public_key)
.followers_url(Some(generate_followers_url(&community_actor_id)?))
.inbox_url(Some(generate_inbox_url(&community_actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&community_actor_id)?))
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.instance_id(site_view.site.instance_id)
.build();
let inserted_community = Community::create(&mut context.pool(), &community_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityAlreadyExists)?;
// The community creator becomes a moderator
let community_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
};
CommunityModerator::join(&mut context.pool(), &community_moderator_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityModeratorAlreadyExists)?;
// Follow your own community
let community_follower_form = CommunityFollowerForm {
community_id: inserted_community.id,
person_id: local_user_view.person.id,
pending: false,
};
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
// Update the discussion_languages if that's provided
let community_id = inserted_community.id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
build_community_response(&context, local_user_view, community_id).await
} }

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, DeleteCommunity}, community::{CommunityResponse, DeleteCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_top_mod, local_user_view_from_jwt}, utils::{is_top_mod, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -13,36 +14,39 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeleteCommunity { pub async fn delete_community(
type Response = CommunityResponse; data: Json<DeleteCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] // Fetch the community mods
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { let community_id = data.community_id;
let data: &DeleteCommunity = self; let community_mods =
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
// Fetch the community mods // Make sure deleter is the top mod
let community_id = data.community_id; is_top_mod(&local_user_view, &community_mods)?;
let community_mods =
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
// Make sure deleter is the top mod // Do the delete
is_top_mod(&local_user_view, &community_mods)?; let community_id = data.community_id;
let deleted = data.deleted;
let community = Community::update(
&mut context.pool(),
community_id,
&CommunityUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
// Do the delete ActivityChannel::submit_activity(
let community_id = data.community_id; SendActivityData::DeleteCommunity(local_user_view.person.clone(), community, data.deleted),
let deleted = data.deleted; &context,
Community::update( )
&mut context.pool(), .await?;
community_id,
&CommunityUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
build_community_response(context, local_user_view, community_id).await build_community_response(&context, local_user_view, community_id).await
}
} }

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, RemoveCommunity}, community::{CommunityResponse, RemoveCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt}, utils::{is_admin, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -18,42 +19,50 @@ use lemmy_utils::{
utils::time::naive_from_unix, utils::time::naive_from_unix,
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for RemoveCommunity { pub async fn remove_community(
type Response = CommunityResponse; data: Json<RemoveCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] // Verify its an admin (only an admin can remove a community)
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { is_admin(&local_user_view)?;
let data: &RemoveCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
// Verify its an admin (only an admin can remove a community) // Do the remove
is_admin(&local_user_view)?; let community_id = data.community_id;
let removed = data.removed;
let community = Community::update(
&mut context.pool(),
community_id,
&CommunityUpdateForm::builder()
.removed(Some(removed))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
// Do the remove // Mod tables
let community_id = data.community_id; let expires = data.expires.map(naive_from_unix);
let removed = data.removed; let form = ModRemoveCommunityForm {
Community::update( mod_person_id: local_user_view.person.id,
&mut context.pool(), community_id: data.community_id,
community_id, removed: Some(removed),
&CommunityUpdateForm::builder() reason: data.reason.clone(),
.removed(Some(removed)) expires,
.build(), };
) ModRemoveCommunity::create(&mut context.pool(), &form).await?;
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
// Mod tables ActivityChannel::submit_activity(
let expires = data.expires.map(naive_from_unix); SendActivityData::RemoveCommunity(
let form = ModRemoveCommunityForm { local_user_view.person.clone(),
mod_person_id: local_user_view.person.id, community,
community_id: data.community_id, data.reason.clone(),
removed: Some(removed), data.removed,
reason: data.reason.clone(), ),
expires, &context,
}; )
ModRemoveCommunity::create(&mut context.pool(), &form).await?; .await?;
build_community_response(context, local_user_view, community_id).await build_community_response(&context, local_user_view, community_id).await
}
} }

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_community_response, build_response::build_community_response,
community::{CommunityResponse, EditCommunity}, community::{CommunityResponse, EditCommunity},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html_opt}, utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html_opt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -22,65 +23,68 @@ use lemmy_utils::{
utils::{slurs::check_slurs_opt, validation::is_valid_body_field}, utils::{slurs::check_slurs_opt, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditCommunity { pub async fn update_community(
type Response = CommunityResponse; data: Json<EditCommunity>,
context: Data<LemmyContext>,
) -> Result<Json<CommunityResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let slur_regex = local_site_to_slur_regex(&local_site);
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> { check_slurs_opt(&data.title, &slur_regex)?;
let data: &EditCommunity = self; check_slurs_opt(&data.description, &slur_regex)?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; is_valid_body_field(&data.description, false)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let slur_regex = local_site_to_slur_regex(&local_site); let title = sanitize_html_opt(&data.title);
check_slurs_opt(&data.title, &slur_regex)?; let description = sanitize_html_opt(&data.description);
check_slurs_opt(&data.description, &slur_regex)?;
is_valid_body_field(&data.description, false)?;
let title = sanitize_html_opt(&data.title); let icon = diesel_option_overwrite_to_url(&data.icon)?;
let description = sanitize_html_opt(&data.description); let banner = diesel_option_overwrite_to_url(&data.banner)?;
let description = diesel_option_overwrite(description);
let icon = diesel_option_overwrite_to_url(&data.icon)?; // Verify its a mod (only mods can edit it)
let banner = diesel_option_overwrite_to_url(&data.banner)?; let community_id = data.community_id;
let description = diesel_option_overwrite(description); let mods: Vec<PersonId> =
CommunityModeratorView::for_community(&mut context.pool(), community_id)
// Verify its a mod (only mods can edit it)
let community_id = data.community_id;
let mods: Vec<PersonId> =
CommunityModeratorView::for_community(&mut context.pool(), community_id)
.await
.map(|v| v.into_iter().map(|m| m.moderator.id).collect())?;
if !mods.contains(&local_user_view.person.id) {
return Err(LemmyErrorType::NotAModerator)?;
}
let community_id = data.community_id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
let community_form = CommunityUpdateForm::builder()
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.updated(Some(Some(naive_now())))
.build();
let community_id = data.community_id;
Community::update(&mut context.pool(), community_id, &community_form)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?; .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?;
if !mods.contains(&local_user_view.person.id) {
build_community_response(context, local_user_view, community_id).await return Err(LemmyErrorType::NotAModerator)?;
} }
let community_id = data.community_id;
if let Some(languages) = data.discussion_languages.clone() {
let site_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
// check that community languages are a subset of site languages
// https://stackoverflow.com/a/64227550
let is_subset = languages.iter().all(|item| site_languages.contains(item));
if !is_subset {
return Err(LemmyErrorType::LanguageNotAllowed)?;
}
CommunityLanguage::update(&mut context.pool(), languages, community_id).await?;
}
let community_form = CommunityUpdateForm::builder()
.title(title)
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.updated(Some(Some(naive_now())))
.build();
let community_id = data.community_id;
let community = Community::update(&mut context.pool(), community_id, &community_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
ActivityChannel::submit_activity(
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
&context,
)
.await?;
build_community_response(&context, local_user_view, community_id).await
} }

View File

@ -1,5 +1,5 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
@ -13,41 +13,38 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView; use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for CreateCustomEmoji { pub async fn create_custom_emoji(
type Response = CustomEmojiResponse; data: Json<CreateCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<CustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] let local_site = LocalSite::read(&mut context.pool()).await?;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> { // Make sure user is an admin
let data: &CreateCustomEmoji = self; is_admin(&local_user_view)?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?; let shortcode = sanitize_html(data.shortcode.to_lowercase().trim());
// Make sure user is an admin let alt_text = sanitize_html(&data.alt_text);
is_admin(&local_user_view)?; let category = sanitize_html(&data.category);
let shortcode = sanitize_html(data.shortcode.to_lowercase().trim()); let emoji_form = CustomEmojiInsertForm::builder()
let alt_text = sanitize_html(&data.alt_text); .local_site_id(local_site.id)
let category = sanitize_html(&data.category); .shortcode(shortcode)
.alt_text(alt_text)
let emoji_form = CustomEmojiInsertForm::builder() .category(category)
.local_site_id(local_site.id) .image_url(data.clone().image_url.into())
.shortcode(shortcode) .build();
.alt_text(alt_text) let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?;
.category(category) let mut keywords = vec![];
.image_url(data.clone().image_url.into()) for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build(); .build();
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).await?; keywords.push(keyword_form);
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(CustomEmojiResponse { custom_emoji: view })
} }
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(Json(CustomEmojiResponse { custom_emoji: view }))
} }

View File

@ -1,5 +1,5 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse}, custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse},
@ -8,24 +8,18 @@ use lemmy_api_common::{
use lemmy_db_schema::source::custom_emoji::CustomEmoji; use lemmy_db_schema::source::custom_emoji::CustomEmoji;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeleteCustomEmoji { pub async fn delete_custom_emoji(
type Response = DeleteCustomEmojiResponse; data: Json<DeleteCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<DeleteCustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] // Make sure user is an admin
async fn perform( is_admin(&local_user_view)?;
&self, CustomEmoji::delete(&mut context.pool(), data.id).await?;
context: &Data<LemmyContext>, Ok(Json(DeleteCustomEmojiResponse {
) -> Result<DeleteCustomEmojiResponse, LemmyError> { id: data.id,
let data: &DeleteCustomEmoji = self; success: true,
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?; }))
// Make sure user is an admin
is_admin(&local_user_view)?;
CustomEmoji::delete(&mut context.pool(), data.id).await?;
Ok(DeleteCustomEmojiResponse {
id: data.id,
success: true,
})
}
} }

View File

@ -1,3 +1,3 @@
mod create; pub mod create;
mod delete; pub mod delete;
mod update; pub mod update;

View File

@ -1,5 +1,5 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
@ -13,40 +13,37 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView; use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditCustomEmoji { pub async fn update_custom_emoji(
type Response = CustomEmojiResponse; data: Json<EditCustomEmoji>,
context: Data<LemmyContext>,
) -> Result<Json<CustomEmojiResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] let local_site = LocalSite::read(&mut context.pool()).await?;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> { // Make sure user is an admin
let data: &EditCustomEmoji = self; is_admin(&local_user_view)?;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?; let alt_text = sanitize_html(&data.alt_text);
// Make sure user is an admin let category = sanitize_html(&data.category);
is_admin(&local_user_view)?;
let alt_text = sanitize_html(&data.alt_text); let emoji_form = CustomEmojiUpdateForm::builder()
let category = sanitize_html(&data.category); .local_site_id(local_site.id)
.alt_text(alt_text)
let emoji_form = CustomEmojiUpdateForm::builder() .category(category)
.local_site_id(local_site.id) .image_url(data.clone().image_url.into())
.alt_text(alt_text) .build();
.category(category) let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
.image_url(data.clone().image_url.into()) CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build(); .build();
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?; keywords.push(keyword_form);
CustomEmojiKeyword::delete(&mut context.pool(), data.id).await?;
let mut keywords = vec![];
for keyword in &data.keywords {
let keyword_form = CustomEmojiKeywordInsertForm::builder()
.custom_emoji_id(emoji.id)
.keyword(keyword.to_lowercase().trim().to_string())
.build();
keywords.push(keyword_form);
}
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(CustomEmojiResponse { custom_emoji: view })
} }
CustomEmojiKeyword::create(&mut context.pool(), keywords).await?;
let view = CustomEmojiView::get(&mut context.pool(), emoji.id).await?;
Ok(Json(CustomEmojiResponse { custom_emoji: view }))
} }

View File

@ -1,7 +1,3 @@
use actix_web::web::Data;
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::LemmyError;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod custom_emoji; pub mod custom_emoji;
@ -9,10 +5,3 @@ pub mod post;
pub mod private_message; pub mod private_message;
pub mod site; pub mod site;
pub mod user; pub mod user;
#[async_trait::async_trait(?Send)]
pub trait PerformCrud {
type Response: serde::ser::Serialize + Send + Clone + Sync;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError>;
}

View File

@ -194,7 +194,5 @@ pub async fn create_post(
} }
}; };
Ok(Json( build_post_response(&context, community_id, person_id, post_id).await
build_post_response(&context, community_id, person_id, post_id).await?,
))
} }

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{DeletePost, PostResponse}, post::{DeletePost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt}, utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -12,52 +13,50 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeletePost { pub async fn delete_post(
type Response = PostResponse; data: Json<DeletePost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &DeletePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; // Dont delete it if its already been deleted.
let orig_post = Post::read(&mut context.pool(), post_id).await?; if orig_post.deleted == data.deleted {
return Err(LemmyErrorType::CouldntUpdatePost)?;
// Dont delete it if its already been deleted.
if orig_post.deleted == data.deleted {
return Err(LemmyErrorType::CouldntUpdatePost)?;
}
check_community_ban(
local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
// Verify that only the creator can delete
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
return Err(LemmyErrorType::NoPostEditAllowed)?;
}
// Update the post
let post_id = data.post_id;
let deleted = data.deleted;
Post::update(
&mut context.pool(),
post_id,
&PostUpdateForm::builder().deleted(Some(deleted)).build(),
)
.await?;
build_post_response(
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }
check_community_ban(
local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
// Verify that only the creator can delete
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
return Err(LemmyErrorType::NoPostEditAllowed)?;
}
// Update the post
let post = Post::update(
&mut context.pool(),
data.post_id,
&PostUpdateForm::builder()
.deleted(Some(data.deleted))
.build(),
)
.await?;
let person_id = local_user_view.person.id;
ActivityChannel::submit_activity(
SendActivityData::DeletePost(post, local_user_view.person, data.0.clone()),
&context,
)
.await?;
build_post_response(&context, orig_post.community_id, person_id, data.post_id).await
} }

View File

@ -1,9 +1,10 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{PostResponse, RemovePost}, post::{PostResponse, RemovePost},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt}, utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -15,58 +16,56 @@ use lemmy_db_schema::{
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for RemovePost { pub async fn remove_post(
type Response = PostResponse; data: Json<RemovePost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(context))] let post_id = data.post_id;
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> { let orig_post = Post::read(&mut context.pool(), post_id).await?;
let data: &RemovePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id; check_community_ban(
let orig_post = Post::read(&mut context.pool(), post_id).await?; local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_ban( // Verify that only the mods can remove
local_user_view.person.id, is_mod_or_admin(
orig_post.community_id, &mut context.pool(),
&mut context.pool(), local_user_view.person.id,
) orig_post.community_id,
.await?; )
.await?;
// Verify that only the mods can remove // Update the post
is_mod_or_admin( let post_id = data.post_id;
&mut context.pool(), let removed = data.removed;
local_user_view.person.id, let post = Post::update(
orig_post.community_id, &mut context.pool(),
) post_id,
.await?; &PostUpdateForm::builder().removed(Some(removed)).build(),
)
.await?;
// Update the post // Mod tables
let post_id = data.post_id; let form = ModRemovePostForm {
let removed = data.removed; mod_person_id: local_user_view.person.id,
Post::update( post_id: data.post_id,
&mut context.pool(), removed: Some(removed),
post_id, reason: data.reason.clone(),
&PostUpdateForm::builder().removed(Some(removed)).build(), };
) ModRemovePost::create(&mut context.pool(), &form).await?;
.await?;
// Mod tables let person_id = local_user_view.person.id;
let form = ModRemovePostForm { ActivityChannel::submit_activity(
mod_person_id: local_user_view.person.id, SendActivityData::RemovePost(post, local_user_view.person, data.0),
post_id: data.post_id, &context,
removed: Some(removed), )
reason: data.reason.clone(), .await?;
};
ModRemovePost::create(&mut context.pool(), &form).await?;
build_post_response( build_post_response(&context, orig_post.community_id, person_id, post_id).await
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
}
} }

View File

@ -1,10 +1,11 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
post::{EditPost, PostResponse}, post::{EditPost, PostResponse},
request::fetch_site_data, request::fetch_site_data,
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_community_ban, check_community_ban,
local_site_to_slur_regex, local_site_to_slur_regex,
@ -28,95 +29,95 @@ use lemmy_utils::{
validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title}, validation::{check_url_scheme, clean_url_params, is_valid_body_field, is_valid_post_title},
}, },
}; };
use std::ops::Deref;
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditPost { pub async fn update_post(
type Response = PostResponse; data: Json<EditPost>,
context: Data<LemmyContext>,
) -> Result<Json<PostResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(context))] let data_url = data.url.as_ref();
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> {
let data: &EditPost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let data_url = data.url.as_ref(); // TODO No good way to handle a clear.
// Issue link: https://github.com/LemmyNet/lemmy/issues/2287
let url = Some(data_url.map(clean_url_params).map(Into::into));
// TODO No good way to handle a clear. let slur_regex = local_site_to_slur_regex(&local_site);
// Issue link: https://github.com/LemmyNet/lemmy/issues/2287 check_slurs_opt(&data.name, &slur_regex)?;
let url = Some(data_url.map(clean_url_params).map(Into::into)); check_slurs_opt(&data.body, &slur_regex)?;
let slur_regex = local_site_to_slur_regex(&local_site); if let Some(name) = &data.name {
check_slurs_opt(&data.name, &slur_regex)?; is_valid_post_title(name)?;
check_slurs_opt(&data.body, &slur_regex)?;
if let Some(name) = &data.name {
is_valid_post_title(name)?;
}
is_valid_body_field(&data.body, true)?;
check_url_scheme(&data.url)?;
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
check_community_ban(
local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
// Verify that only the creator can edit
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
return Err(LemmyErrorType::NoPostEditAllowed)?;
}
// Fetch post links and Pictrs cached image
let data_url = data.url.as_ref();
let (metadata_res, thumbnail_url) =
fetch_site_data(context.client(), context.settings(), data_url, true).await;
let (embed_title, embed_description, embed_video_url) = metadata_res
.map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
.unwrap_or_default();
let name = sanitize_html_opt(&data.name);
let body = sanitize_html_opt(&data.body);
let body = diesel_option_overwrite(body);
let embed_title = embed_title.map(|e| sanitize_html_opt(&e));
let embed_description = embed_description.map(|e| sanitize_html_opt(&e));
let language_id = self.language_id;
CommunityLanguage::is_allowed_community_language(
&mut context.pool(),
language_id,
orig_post.community_id,
)
.await?;
let post_form = PostUpdateForm::builder()
.name(name)
.url(url)
.body(body)
.nsfw(data.nsfw)
.embed_title(embed_title)
.embed_description(embed_description)
.embed_video_url(embed_video_url)
.language_id(data.language_id)
.thumbnail_url(Some(thumbnail_url))
.updated(Some(Some(naive_now())))
.build();
let post_id = data.post_id;
Post::update(&mut context.pool(), post_id, &post_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
build_post_response(
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }
is_valid_body_field(&data.body, true)?;
check_url_scheme(&data.url)?;
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
check_community_ban(
local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
// Verify that only the creator can edit
if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) {
return Err(LemmyErrorType::NoPostEditAllowed)?;
}
// Fetch post links and Pictrs cached image
let data_url = data.url.as_ref();
let (metadata_res, thumbnail_url) =
fetch_site_data(context.client(), context.settings(), data_url, true).await;
let (embed_title, embed_description, embed_video_url) = metadata_res
.map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
.unwrap_or_default();
let name = sanitize_html_opt(&data.name);
let body = sanitize_html_opt(&data.body);
let body = diesel_option_overwrite(body);
let embed_title = embed_title.map(|e| sanitize_html_opt(&e));
let embed_description = embed_description.map(|e| sanitize_html_opt(&e));
let language_id = data.language_id;
CommunityLanguage::is_allowed_community_language(
&mut context.pool(),
language_id,
orig_post.community_id,
)
.await?;
let post_form = PostUpdateForm::builder()
.name(name)
.url(url)
.body(body)
.nsfw(data.nsfw)
.embed_title(embed_title)
.embed_description(embed_description)
.embed_video_url(embed_video_url)
.language_id(data.language_id)
.thumbnail_url(Some(thumbnail_url))
.updated(Some(Some(naive_now())))
.build();
let post_id = data.post_id;
let updated_post = Post::update(&mut context.pool(), post_id, &post_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
ActivityChannel::submit_activity(SendActivityData::UpdatePost(updated_post), &context).await?;
build_post_response(
context.deref(),
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
} }

View File

@ -1,8 +1,9 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
private_message::{CreatePrivateMessage, PrivateMessageResponse}, private_message::{CreatePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{ utils::{
check_person_block, check_person_block,
generate_local_apub_endpoint, generate_local_apub_endpoint,
@ -27,78 +28,77 @@ use lemmy_utils::{
utils::{slurs::remove_slurs, validation::is_valid_body_field}, utils::{slurs::remove_slurs, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for CreatePrivateMessage { pub async fn create_private_message(
type Response = PrivateMessageResponse; data: Json<CreatePrivateMessage>,
context: Data<LemmyContext>,
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(self, context))] let content = sanitize_html(&data.content);
async fn perform( let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
&self, is_valid_body_field(&Some(content.clone()), false)?;
context: &Data<LemmyContext>,
) -> Result<PrivateMessageResponse, LemmyError> {
let data: &CreatePrivateMessage = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let content = sanitize_html(&data.content); check_person_block(
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site)); local_user_view.person.id,
is_valid_body_field(&Some(content.clone()), false)?; data.recipient_id,
&mut context.pool(),
)
.await?;
check_person_block( let private_message_form = PrivateMessageInsertForm::builder()
local_user_view.person.id, .content(content.clone())
data.recipient_id, .creator_id(local_user_view.person.id)
&mut context.pool(), .recipient_id(data.recipient_id)
) .build();
.await?;
let private_message_form = PrivateMessageInsertForm::builder() let inserted_private_message = PrivateMessage::create(&mut context.pool(), &private_message_form)
.content(content.clone())
.creator_id(local_user_view.person.id)
.recipient_id(data.recipient_id)
.build();
let inserted_private_message =
PrivateMessage::create(&mut context.pool(), &private_message_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
let inserted_private_message_id = inserted_private_message.id;
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let apub_id = generate_local_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
&protocol_and_hostname,
)?;
PrivateMessage::update(
&mut context.pool(),
inserted_private_message.id,
&PrivateMessageUpdateForm::builder()
.ap_id(Some(apub_id))
.build(),
)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?; .with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?; let inserted_private_message_id = inserted_private_message.id;
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let apub_id = generate_local_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
&protocol_and_hostname,
)?;
PrivateMessage::update(
&mut context.pool(),
inserted_private_message.id,
&PrivateMessageUpdateForm::builder()
.ap_id(Some(apub_id))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)?;
// Send email to the local recipient, if one exists let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?;
if view.recipient.local {
let recipient_id = data.recipient_id;
let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id).await?;
let lang = get_interface_language(&local_recipient);
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
let sender_name = &local_user_view.person.name;
send_email_to_user(
&local_recipient,
&lang.notification_private_message_subject(sender_name),
&lang.notification_private_message_body(inbox_link, &content, sender_name),
context.settings(),
)
.await;
}
Ok(PrivateMessageResponse { // Send email to the local recipient, if one exists
private_message_view: view, if view.recipient.local {
}) let recipient_id = data.recipient_id;
let local_recipient = LocalUserView::read_person(&mut context.pool(), recipient_id).await?;
let lang = get_interface_language(&local_recipient);
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
let sender_name = &local_user_view.person.name;
send_email_to_user(
&local_recipient,
&lang.notification_private_message_subject(sender_name),
&lang.notification_private_message_body(inbox_link, &content, sender_name),
context.settings(),
)
.await;
} }
ActivityChannel::submit_activity(
SendActivityData::CreatePrivateMessage(view.clone()),
&context,
)
.await?;
Ok(Json(PrivateMessageResponse {
private_message_view: view,
}))
} }

View File

@ -1,8 +1,9 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
private_message::{DeletePrivateMessage, PrivateMessageResponse}, private_message::{DeletePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt, utils::local_user_view_from_jwt,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -12,42 +13,41 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::PrivateMessageView; use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeletePrivateMessage { pub async fn delete_private_message(
type Response = PrivateMessageResponse; data: Json<DeletePrivateMessage>,
context: Data<LemmyContext>,
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
#[tracing::instrument(skip(self, context))] // Checking permissions
async fn perform( let private_message_id = data.private_message_id;
&self, let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
context: &Data<LemmyContext>, if local_user_view.person.id != orig_private_message.creator_id {
) -> Result<PrivateMessageResponse, LemmyError> { return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
let data: &DeletePrivateMessage = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
// Checking permissions
let private_message_id = data.private_message_id;
let orig_private_message =
PrivateMessage::read(&mut context.pool(), private_message_id).await?;
if local_user_view.person.id != orig_private_message.creator_id {
return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
}
// Doing the update
let private_message_id = data.private_message_id;
let deleted = data.deleted;
PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
Ok(PrivateMessageResponse {
private_message_view: view,
})
} }
// Doing the update
let private_message_id = data.private_message_id;
let deleted = data.deleted;
let private_message = PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
ActivityChannel::submit_activity(
SendActivityData::DeletePrivateMessage(local_user_view.person, private_message, data.deleted),
&context,
)
.await?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
Ok(Json(PrivateMessageResponse {
private_message_view: view,
}))
} }

View File

@ -1,8 +1,9 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
private_message::{EditPrivateMessage, PrivateMessageResponse}, private_message::{EditPrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html}, utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
@ -19,48 +20,47 @@ use lemmy_utils::{
utils::{slurs::remove_slurs, validation::is_valid_body_field}, utils::{slurs::remove_slurs, validation::is_valid_body_field},
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for EditPrivateMessage { pub async fn update_private_message(
type Response = PrivateMessageResponse; data: Json<EditPrivateMessage>,
context: Data<LemmyContext>,
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
#[tracing::instrument(skip(self, context))] // Checking permissions
async fn perform( let private_message_id = data.private_message_id;
&self, let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
context: &Data<LemmyContext>, if local_user_view.person.id != orig_private_message.creator_id {
) -> Result<PrivateMessageResponse, LemmyError> { return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
let data: &EditPrivateMessage = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Checking permissions
let private_message_id = data.private_message_id;
let orig_private_message =
PrivateMessage::read(&mut context.pool(), private_message_id).await?;
if local_user_view.person.id != orig_private_message.creator_id {
return Err(LemmyErrorType::EditPrivateMessageNotAllowed)?;
}
// Doing the update
let content = sanitize_html(&data.content);
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
is_valid_body_field(&Some(content.clone()), false)?;
let private_message_id = data.private_message_id;
PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.content(Some(content))
.updated(Some(Some(naive_now())))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
Ok(PrivateMessageResponse {
private_message_view: view,
})
} }
// Doing the update
let content = sanitize_html(&data.content);
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
is_valid_body_field(&Some(content.clone()), false)?;
let private_message_id = data.private_message_id;
PrivateMessage::update(
&mut context.pool(),
private_message_id,
&PrivateMessageUpdateForm::builder()
.content(Some(content))
.updated(Some(Some(naive_now())))
.build(),
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
ActivityChannel::submit_activity(
SendActivityData::UpdatePrivateMessage(view.clone()),
&context,
)
.await?;
Ok(Json(PrivateMessageResponse {
private_message_view: view,
}))
} }

View File

@ -1,6 +1,5 @@
use crate::PerformCrud; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use activitypub_federation::http_signatures::generate_actor_keypair; use actix_web::web::Json;
use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{LoginResponse, Register}, person::{LoginResponse, Register},
@ -38,177 +37,173 @@ use lemmy_utils::{
}, },
}; };
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for Register { pub async fn register(
type Response = LoginResponse; data: Json<Register>,
context: Data<LemmyContext>,
) -> Result<Json<LoginResponse>, LemmyError> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_site = site_view.local_site;
let require_registration_application =
local_site.registration_mode == RegistrationMode::RequireApplication;
#[tracing::instrument(skip(self, context))] if local_site.registration_mode == RegistrationMode::Closed {
async fn perform(&self, context: &Data<LemmyContext>) -> Result<LoginResponse, LemmyError> { return Err(LemmyErrorType::RegistrationClosed)?;
let data: &Register = self; }
let site_view = SiteView::read_local(&mut context.pool()).await?; password_length_check(&data.password)?;
let local_site = site_view.local_site; honeypot_check(&data.honeypot)?;
let require_registration_application =
local_site.registration_mode == RegistrationMode::RequireApplication;
if local_site.registration_mode == RegistrationMode::Closed { if local_site.require_email_verification && data.email.is_none() {
return Err(LemmyErrorType::RegistrationClosed)?; return Err(LemmyErrorType::EmailRequired)?;
} }
password_length_check(&data.password)?; if local_site.site_setup && require_registration_application && data.answer.is_none() {
honeypot_check(&data.honeypot)?; return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?;
}
if local_site.require_email_verification && data.email.is_none() { // Make sure passwords match
return Err(LemmyErrorType::EmailRequired)?; if data.password != data.password_verify {
} return Err(LemmyErrorType::PasswordsDoNotMatch)?;
}
if local_site.site_setup && require_registration_application && data.answer.is_none() { if local_site.site_setup && local_site.captcha_enabled {
return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?; if let Some(captcha_uuid) = &data.captcha_uuid {
} let uuid = uuid::Uuid::parse_str(captcha_uuid)?;
let check = CaptchaAnswer::check_captcha(
// Make sure passwords match &mut context.pool(),
if data.password != data.password_verify { CheckCaptchaAnswer {
return Err(LemmyErrorType::PasswordsDoNotMatch)?; uuid,
} answer: data.captcha_answer.clone().unwrap_or_default(),
},
if local_site.site_setup && local_site.captcha_enabled { )
if let Some(captcha_uuid) = &data.captcha_uuid { .await?;
let uuid = uuid::Uuid::parse_str(captcha_uuid)?; if !check {
let check = CaptchaAnswer::check_captcha(
&mut context.pool(),
CheckCaptchaAnswer {
uuid,
answer: data.captcha_answer.clone().unwrap_or_default(),
},
)
.await?;
if !check {
return Err(LemmyErrorType::CaptchaIncorrect)?;
}
} else {
return Err(LemmyErrorType::CaptchaIncorrect)?; return Err(LemmyErrorType::CaptchaIncorrect)?;
} }
} else {
return Err(LemmyErrorType::CaptchaIncorrect)?;
} }
}
let slur_regex = local_site_to_slur_regex(&local_site); let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.username, &slur_regex)?; check_slurs(&data.username, &slur_regex)?;
check_slurs_opt(&data.answer, &slur_regex)?; check_slurs_opt(&data.answer, &slur_regex)?;
let username = sanitize_html(&data.username); let username = sanitize_html(&data.username);
let actor_keypair = generate_actor_keypair()?; let actor_keypair = generate_actor_keypair()?;
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?; is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
let actor_id = generate_local_apub_endpoint( let actor_id = generate_local_apub_endpoint(
EndpointType::Person, EndpointType::Person,
&data.username, &data.username,
&context.settings().get_protocol_and_hostname(), &context.settings().get_protocol_and_hostname(),
)?; )?;
if let Some(email) = &data.email { if let Some(email) = &data.email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? { if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?; return Err(LemmyErrorType::EmailAlreadyExists)?;
}
} }
}
// We have to create both a person, and local_user // We have to create both a person, and local_user
// Register the new person // Register the new person
let person_form = PersonInsertForm::builder() let person_form = PersonInsertForm::builder()
.name(username) .name(username)
.actor_id(Some(actor_id.clone())) .actor_id(Some(actor_id.clone()))
.private_key(Some(actor_keypair.private_key)) .private_key(Some(actor_keypair.private_key))
.public_key(actor_keypair.public_key) .public_key(actor_keypair.public_key)
.inbox_url(Some(generate_inbox_url(&actor_id)?)) .inbox_url(Some(generate_inbox_url(&actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?)) .shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?))
// If its the initial site setup, they are an admin // If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup)) .admin(Some(!local_site.site_setup))
.instance_id(site_view.site.instance_id) .instance_id(site_view.site.instance_id)
.build(); .build();
// insert the person // insert the person
let inserted_person = Person::create(&mut context.pool(), &person_form) let inserted_person = Person::create(&mut context.pool(), &person_form)
.await .await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?; .with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
// Automatically set their application as accepted, if they created this with open registration. // Automatically set their application as accepted, if they created this with open registration.
// Also fixes a bug which allows users to log in when registrations are changed to closed. // Also fixes a bug which allows users to log in when registrations are changed to closed.
let accepted_application = Some(!require_registration_application); let accepted_application = Some(!require_registration_application);
// Create the local user // Create the local user
let local_user_form = LocalUserInsertForm::builder() let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id) .person_id(inserted_person.id)
.email(data.email.as_deref().map(str::to_lowercase)) .email(data.email.as_deref().map(str::to_lowercase))
.password_encrypted(data.password.to_string()) .password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw)) .show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application) .accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type)) .default_listing_type(Some(local_site.default_post_listing_type))
.build(); .build();
let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?; let inserted_local_user = LocalUser::create(&mut context.pool(), &local_user_form).await?;
if local_site.site_setup && require_registration_application { if local_site.site_setup && require_registration_application {
// Create the registration application // Create the registration application
let form = RegistrationApplicationInsertForm { let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id, local_user_id: inserted_local_user.id,
// We already made sure answer was not null above // We already made sure answer was not null above
answer: data.answer.clone().expect("must have an answer"), answer: data.answer.clone().expect("must have an answer"),
};
RegistrationApplication::create(&mut context.pool(), &form).await?;
}
// Email the admins
if local_site.application_email_admins {
send_new_applicant_email_to_admins(&data.username, &mut context.pool(), context.settings())
.await?;
}
let mut login_response = LoginResponse {
jwt: None,
registration_created: false,
verify_email_sent: false,
}; };
// Log the user in directly if the site is not setup, or email verification and application aren't required RegistrationApplication::create(&mut context.pool(), &form).await?;
if !local_site.site_setup }
|| (!require_registration_application && !local_site.require_email_verification)
{
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
);
} else {
if local_site.require_email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
counts: PersonAggregates::default(),
};
// we check at the beginning of this method that email is set
let email = local_user_view
.local_user
.email
.clone()
.expect("email was provided");
send_verification_email( // Email the admins
&local_user_view, if local_site.application_email_admins {
&email, send_new_applicant_email_to_admins(&data.username, &mut context.pool(), context.settings())
&mut context.pool(), .await?;
context.settings(), }
)
.await?;
login_response.verify_email_sent = true;
}
if require_registration_application { let mut login_response = LoginResponse {
login_response.registration_created = true; jwt: None,
} registration_created: false,
verify_email_sent: false,
};
// Log the user in directly if the site is not setup, or email verification and application aren't required
if !local_site.site_setup
|| (!require_registration_application && !local_site.require_email_verification)
{
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
&context.secret().jwt_secret,
&context.settings().hostname,
)?
.into(),
);
} else {
if local_site.require_email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
counts: PersonAggregates::default(),
};
// we check at the beginning of this method that email is set
let email = local_user_view
.local_user
.email
.clone()
.expect("email was provided");
send_verification_email(
&local_user_view,
&email,
&mut context.pool(),
context.settings(),
)
.await?;
login_response.verify_email_sent = true;
} }
Ok(login_response) if require_registration_application {
login_response.registration_created = true;
}
} }
Ok(Json(login_response))
} }

View File

@ -1,32 +1,36 @@
use crate::PerformCrud; use activitypub_federation::config::Data;
use actix_web::web::Data; use actix_web::web::Json;
use bcrypt::verify; use bcrypt::verify;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{DeleteAccount, DeleteAccountResponse}, person::{DeleteAccount, DeleteAccountResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt, utils::local_user_view_from_jwt,
}; };
use lemmy_utils::error::{LemmyError, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorType};
#[async_trait::async_trait(?Send)] #[tracing::instrument(skip(context))]
impl PerformCrud for DeleteAccount { pub async fn delete_account(
type Response = DeleteAccountResponse; data: Json<DeleteAccount>,
context: Data<LemmyContext>,
) -> Result<Json<DeleteAccountResponse>, LemmyError> {
let local_user_view = local_user_view_from_jwt(data.auth.as_ref(), &context).await?;
#[tracing::instrument(skip(self, context))] // Verify the password
async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError> { let valid: bool = verify(
let data = self; &data.password,
let local_user_view = local_user_view_from_jwt(data.auth.as_ref(), context).await?; &local_user_view.local_user.password_encrypted,
)
// Verify the password .unwrap_or(false);
let valid: bool = verify( if !valid {
&data.password, return Err(LemmyErrorType::IncorrectLogin)?;
&local_user_view.local_user.password_encrypted,
)
.unwrap_or(false);
if !valid {
return Err(LemmyErrorType::IncorrectLogin)?;
}
Ok(DeleteAccountResponse {})
} }
ActivityChannel::submit_activity(
SendActivityData::DeleteUser(local_user_view.person),
&context,
)
.await?;
Ok(Json(DeleteAccountResponse {}))
} }

View File

@ -1,2 +1,2 @@
mod create; pub mod create;
mod delete; pub mod delete;

View File

@ -33,7 +33,6 @@ http = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
uuid = { workspace = true } uuid = { workspace = true }
sha2 = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }

View File

@ -1,10 +1,9 @@
use crate::{ use crate::{
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson}, objects::{community::ApubCommunity, instance::ApubSite},
protocol::{ protocol::{
activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser}, activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
objects::{group::Group, instance::Instance}, objects::{group::Group, instance::Instance},
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -12,19 +11,18 @@ use activitypub_federation::{
traits::{Actor, Object}, traits::{Actor, Object},
}; };
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use lemmy_api_common::{ use lemmy_api_common::{community::BanFromCommunity, context::LemmyContext, person::BanPerson};
community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext,
person::{BanPerson, BanPersonResponse},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::CommunityId,
source::{community::Community, person::Person, site::Site}, source::{community::Community, person::Person, site::Site},
traits::Crud, traits::Crud,
utils::DbPool, utils::DbPool,
}; };
use lemmy_db_views::structs::SiteView; use lemmy_db_views::structs::SiteView;
use lemmy_utils::{error::LemmyError, utils::time::naive_from_unix}; use lemmy_utils::{
error::{LemmyError, LemmyResult},
utils::time::naive_from_unix,
};
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
@ -132,87 +130,74 @@ async fn generate_cc(
}) })
} }
#[async_trait::async_trait] pub(crate) async fn send_ban_from_site(
impl SendActivity for BanPerson { mod_: Person,
type Response = BanPersonResponse; banned_user: Person,
data: BanPerson,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into());
let expires = data.expires.map(naive_from_unix);
async fn send_activity( // if the action affects a local user, federate to other instances
request: &Self, if banned_user.local {
_response: &Self::Response, if data.ban {
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let person = Person::read(&mut context.pool(), request.person_id).await?;
let site = SiteOrCommunity::Site(SiteView::read_local(&mut context.pool()).await?.site.into());
let expires = request.expires.map(naive_from_unix);
// if the action affects a local user, federate to other instances
if person.local {
if request.ban {
BlockUser::send(
&site,
&person.into(),
&local_user_view.person.into(),
request.remove_data.unwrap_or(false),
request.reason.clone(),
expires,
context,
)
.await
} else {
UndoBlockUser::send(
&site,
&person.into(),
&local_user_view.person.into(),
request.reason.clone(),
context,
)
.await
}
} else {
Ok(())
}
}
}
#[async_trait::async_trait]
impl SendActivity for BanFromCommunity {
type Response = BanFromCommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community: ApubCommunity = Community::read(&mut context.pool(), request.community_id)
.await?
.into();
let banned_person: ApubPerson = Person::read(&mut context.pool(), request.person_id)
.await?
.into();
let expires = request.expires.map(naive_from_unix);
if request.ban {
BlockUser::send( BlockUser::send(
&SiteOrCommunity::Community(community), &site,
&banned_person, &banned_user.into(),
&local_user_view.person.clone().into(), &mod_.into(),
request.remove_data.unwrap_or(false), data.remove_data.unwrap_or(false),
request.reason.clone(), data.reason.clone(),
expires, expires,
context, &context,
) )
.await .await
} else { } else {
UndoBlockUser::send( UndoBlockUser::send(
&SiteOrCommunity::Community(community), &site,
&banned_person, &banned_user.into(),
&local_user_view.person.clone().into(), &mod_.into(),
request.reason.clone(), data.reason.clone(),
context, &context,
) )
.await .await
} }
} else {
Ok(())
}
}
pub(crate) async fn send_ban_from_community(
mod_: Person,
community_id: CommunityId,
banned_person: Person,
data: BanFromCommunity,
context: Data<LemmyContext>,
) -> LemmyResult<()> {
let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
.await?
.into();
let expires = data.expires.map(naive_from_unix);
if data.ban {
BlockUser::send(
&SiteOrCommunity::Community(community),
&banned_person.into(),
&mod_.into(),
data.remove_data.unwrap_or(false),
data.reason.clone(),
expires,
&context,
)
.await
} else {
UndoBlockUser::send(
&SiteOrCommunity::Community(community),
&banned_person.into(),
&mod_.into(),
data.reason.clone(),
&context,
)
.await
} }
} }

View File

@ -13,7 +13,6 @@ use crate::{
activities::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove}, activities::community::{collection_add::CollectionAdd, collection_remove::CollectionRemove},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -22,13 +21,12 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::{
community::{AddModToCommunity, AddModToCommunityResponse},
context::LemmyContext, context::LemmyContext,
post::{FeaturePost, PostResponse}, utils::{generate_featured_url, generate_moderators_url},
utils::{generate_featured_url, generate_moderators_url, local_user_view_from_jwt},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
impls::community::CollectionType, impls::community::CollectionType,
newtypes::{CommunityId, PersonId},
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
community::{Community, CommunityModerator, CommunityModeratorForm}, community::{Community, CommunityModerator, CommunityModeratorForm},
@ -174,61 +172,41 @@ impl ActivityHandler for CollectionAdd {
} }
} }
#[async_trait::async_trait] pub(crate) async fn send_add_mod_to_community(
impl SendActivity for AddModToCommunity { actor: Person,
type Response = AddModToCommunityResponse; community_id: CommunityId,
updated_mod_id: PersonId,
async fn send_activity( added: bool,
request: &Self, context: Data<LemmyContext>,
_response: &Self::Response, ) -> Result<(), LemmyError> {
context: &Data<LemmyContext>, let actor: ApubPerson = actor.into();
) -> Result<(), LemmyError> { let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; .await?
let community: ApubCommunity = Community::read(&mut context.pool(), request.community_id) .into();
.await? let updated_mod: ApubPerson = Person::read(&mut context.pool(), updated_mod_id)
.into(); .await?
let updated_mod: ApubPerson = Person::read(&mut context.pool(), request.person_id) .into();
.await? if added {
.into(); CollectionAdd::send_add_mod(&community, &updated_mod, &actor, &context).await
if request.added { } else {
CollectionAdd::send_add_mod( CollectionRemove::send_remove_mod(&community, &updated_mod, &actor, &context).await
&community,
&updated_mod,
&local_user_view.person.into(),
context,
)
.await
} else {
CollectionRemove::send_remove_mod(
&community,
&updated_mod,
&local_user_view.person.into(),
context,
)
.await
}
} }
} }
#[async_trait::async_trait] pub(crate) async fn send_feature_post(
impl SendActivity for FeaturePost { post: Post,
type Response = PostResponse; actor: Person,
featured: bool,
async fn send_activity( context: Data<LemmyContext>,
request: &Self, ) -> Result<(), LemmyError> {
response: &Self::Response, let actor: ApubPerson = actor.into();
context: &Data<LemmyContext>, let post: ApubPost = post.into();
) -> Result<(), LemmyError> { let community = Community::read(&mut context.pool(), post.community_id)
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; .await?
let community = Community::read(&mut context.pool(), response.post_view.community.id) .into();
.await? if featured {
.into(); CollectionAdd::send_add_featured_post(&community, &post, &actor, &context).await
let post = response.post_view.post.clone().into(); } else {
let person = local_user_view.person.into(); CollectionRemove::send_remove_featured_post(&community, &post, &actor, &context).await
if request.featured {
CollectionAdd::send_add_featured_post(&community, &post, &person, context).await
} else {
CollectionRemove::send_remove_featured_post(&community, &post, &person, context).await
}
} }
} }

View File

@ -9,26 +9,24 @@ use crate::{
}, },
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
insert_received_activity, insert_received_activity,
objects::community::ApubCommunity,
protocol::{ protocol::{
activities::community::lock_page::{LockPage, LockType, UndoLockPage}, activities::community::lock_page::{LockPage, LockType, UndoLockPage},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
fetch::object_id::ObjectId,
kinds::{activity::UndoType, public}, kinds::{activity::UndoType, public},
traits::ActivityHandler, traits::ActivityHandler,
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
context::LemmyContext,
post::{LockPost, PostResponse},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
community::Community, community::Community,
person::Person,
post::{Post, PostUpdateForm}, post::{Post, PostUpdateForm},
}, },
traits::Crud, traits::Crud,
@ -103,59 +101,55 @@ impl ActivityHandler for UndoLockPage {
} }
} }
#[async_trait::async_trait] pub(crate) async fn send_lock_post(
impl SendActivity for LockPost { post: Post,
type Response = PostResponse; actor: Person,
locked: bool,
async fn send_activity( context: Data<LemmyContext>,
request: &Self, ) -> Result<(), LemmyError> {
response: &Self::Response, let community: ApubCommunity = Community::read(&mut context.pool(), post.community_id)
context: &Data<LemmyContext>, .await?
) -> Result<(), LemmyError> { .into();
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; let id = generate_activity_id(
LockType::Lock,
&context.settings().get_protocol_and_hostname(),
)?;
let community_id = community.actor_id.inner().clone();
let lock = LockPage {
actor: actor.actor_id.clone().into(),
to: vec![public()],
object: ObjectId::from(post.ap_id),
cc: vec![community_id.clone()],
kind: LockType::Lock,
id,
audience: Some(community_id.into()),
};
let activity = if locked {
AnnouncableActivities::LockPost(lock)
} else {
let id = generate_activity_id( let id = generate_activity_id(
LockType::Lock, UndoType::Undo,
&context.settings().get_protocol_and_hostname(), &context.settings().get_protocol_and_hostname(),
)?; )?;
let community_id = response.post_view.community.actor_id.clone(); let undo = UndoLockPage {
let actor = local_user_view.person.actor_id.clone().into(); actor: lock.actor.clone(),
let lock = LockPage {
actor,
to: vec![public()], to: vec![public()],
object: response.post_view.post.ap_id.clone().into(), cc: lock.cc.clone(),
cc: vec![community_id.clone().into()], kind: UndoType::Undo,
kind: LockType::Lock,
id, id,
audience: Some(community_id.into()), audience: lock.audience.clone(),
object: lock,
}; };
let activity = if request.locked { AnnouncableActivities::UndoLockPost(undo)
AnnouncableActivities::LockPost(lock) };
} else { send_activity_in_community(
let id = generate_activity_id( activity,
UndoType::Undo, &actor.into(),
&context.settings().get_protocol_and_hostname(), &community,
)?; ActivitySendTargets::empty(),
let undo = UndoLockPage { true,
actor: lock.actor.clone(), &context,
to: vec![public()], )
cc: lock.cc.clone(), .await?;
kind: UndoType::Undo, Ok(())
id,
audience: lock.audience.clone(),
object: lock,
};
AnnouncableActivities::UndoLockPost(undo)
};
let community = Community::read(&mut context.pool(), response.post_view.community.id).await?;
send_activity_in_community(
activity,
&local_user_view.person.into(),
&community.into(),
ActivitySendTargets::empty(),
true,
context,
)
.await?;
Ok(())
}
} }

View File

@ -4,7 +4,6 @@ use crate::{
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::report::Report, InCommunity}, protocol::{activities::community::report::Report, InCommunity},
PostOrComment, PostOrComment,
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -12,16 +11,13 @@ use activitypub_federation::{
kinds::activity::FlagType, kinds::activity::FlagType,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::{context::LemmyContext, utils::sanitize_html};
comment::{CommentReportResponse, CreateCommentReport},
context::LemmyContext,
post::{CreatePostReport, PostReportResponse},
utils::{local_user_view_from_jwt, sanitize_html},
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
comment_report::{CommentReport, CommentReportForm}, comment_report::{CommentReport, CommentReportForm},
community::Community,
person::Person,
post_report::{PostReport, PostReportForm}, post_report::{PostReport, PostReportForm},
}, },
traits::Reportable, traits::Reportable,
@ -29,58 +25,17 @@ use lemmy_db_schema::{
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait]
impl SendActivity for CreatePostReport {
type Response = PostReportResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
Report::send(
ObjectId::from(response.post_report_view.post.ap_id.clone()),
&local_user_view.person.into(),
ObjectId::from(response.post_report_view.community.actor_id.clone()),
request.reason.to_string(),
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for CreateCommentReport {
type Response = CommentReportResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
Report::send(
ObjectId::from(response.comment_report_view.comment.ap_id.clone()),
&local_user_view.person.into(),
ObjectId::from(response.comment_report_view.community.actor_id.clone()),
request.reason.to_string(),
context,
)
.await
}
}
impl Report { impl Report {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn send( pub(crate) async fn send(
object_id: ObjectId<PostOrComment>, object_id: ObjectId<PostOrComment>,
actor: &ApubPerson, actor: Person,
community_id: ObjectId<ApubCommunity>, community: Community,
reason: String, reason: String,
context: &Data<LemmyContext>, context: Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community = community_id.dereference_local(context).await?; let actor: ApubPerson = actor.into();
let community: ApubCommunity = community.into();
let kind = FlagType::Flag; let kind = FlagType::Flag;
let id = generate_activity_id( let id = generate_activity_id(
kind.clone(), kind.clone(),
@ -97,7 +52,7 @@ impl Report {
}; };
// todo: this should probably filter and only send if the community is remote? // todo: this should probably filter and only send if the community is remote?
let inbox = ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox()); let inbox = ActivitySendTargets::to_inbox(community.shared_inbox_or_inbox());
send_lemmy_activity(context, report, actor, inbox, false).await send_lemmy_activity(&context, report, &actor, inbox, false).await
} }
} }

View File

@ -10,72 +10,51 @@ use crate::{
insert_received_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::update::UpdateCommunity, InCommunity}, protocol::{activities::community::update::UpdateCommunity, InCommunity},
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
kinds::{activity::UpdateType, public}, kinds::{activity::UpdateType, public},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
community::{CommunityResponse, EditCommunity, HideCommunity},
context::LemmyContext,
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{activity::ActivitySendTargets, community::Community}, source::{activity::ActivitySendTargets, community::Community, person::Person},
traits::Crud, traits::Crud,
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait] pub(crate) async fn send_update_community(
impl SendActivity for EditCommunity { community: Community,
type Response = CommunityResponse; actor: Person,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let community: ApubCommunity = community.into();
let actor: ApubPerson = actor.into();
let id = generate_activity_id(
UpdateType::Update,
&context.settings().get_protocol_and_hostname(),
)?;
let update = UpdateCommunity {
actor: actor.id().into(),
to: vec![public()],
object: Box::new(community.clone().into_json(&context).await?),
cc: vec![community.id()],
kind: UpdateType::Update,
id: id.clone(),
audience: Some(community.id().into()),
};
async fn send_activity( let activity = AnnouncableActivities::UpdateCommunity(update);
request: &Self, send_activity_in_community(
_response: &Self::Response, activity,
context: &Data<LemmyContext>, &actor,
) -> Result<(), LemmyError> { &community,
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; ActivitySendTargets::empty(),
let community = Community::read(&mut context.pool(), request.community_id).await?; true,
UpdateCommunity::send(community.into(), &local_user_view.person.into(), context).await &context,
} )
} .await
impl UpdateCommunity {
#[tracing::instrument(skip_all)]
pub async fn send(
community: ApubCommunity,
actor: &ApubPerson,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let id = generate_activity_id(
UpdateType::Update,
&context.settings().get_protocol_and_hostname(),
)?;
let update = UpdateCommunity {
actor: actor.id().into(),
to: vec![public()],
object: Box::new(community.clone().into_json(context).await?),
cc: vec![community.id()],
kind: UpdateType::Update,
id: id.clone(),
audience: Some(community.id().into()),
};
let activity = AnnouncableActivities::UpdateCommunity(update);
send_activity_in_community(
activity,
actor,
&community,
ActivitySendTargets::empty(),
true,
context,
)
.await
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -112,18 +91,3 @@ impl ActivityHandler for UpdateCommunity {
Ok(()) Ok(())
} }
} }
#[async_trait::async_trait]
impl SendActivity for HideCommunity {
type Response = CommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
UpdateCommunity::send(community.into(), &local_user_view.person.into(), context).await
}
}

View File

@ -14,7 +14,6 @@ use crate::{
activities::{create_or_update::note::CreateOrUpdateNote, CreateOrUpdateType}, activities::{create_or_update::note::CreateOrUpdateNote, CreateOrUpdateType},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -25,7 +24,6 @@ use activitypub_federation::{
}; };
use lemmy_api_common::{ use lemmy_api_common::{
build_response::send_local_notifs, build_response::send_local_notifs,
comment::{CommentResponse, EditComment},
context::LemmyContext, context::LemmyContext,
utils::{check_post_deleted_or_removed, is_mod_or_admin}, utils::{check_post_deleted_or_removed, is_mod_or_admin},
}; };
@ -44,25 +42,6 @@ use lemmy_db_schema::{
use lemmy_utils::{error::LemmyError, utils::mention::scrape_text_for_mentions}; use lemmy_utils::{error::LemmyError, utils::mention::scrape_text_for_mentions};
use url::Url; use url::Url;
#[async_trait::async_trait]
impl SendActivity for EditComment {
type Response = CommentResponse;
async fn send_activity(
_request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
CreateOrUpdateNote::send(
response.comment_view.comment.clone(),
response.comment_view.creator.id,
CreateOrUpdateType::Update,
context.reset_request_count(),
)
.await
}
}
impl CreateOrUpdateNote { impl CreateOrUpdateNote {
#[tracing::instrument(skip(comment, person_id, kind, context))] #[tracing::instrument(skip(comment, person_id, kind, context))]
pub(crate) async fn send( pub(crate) async fn send(

View File

@ -14,7 +14,6 @@ use crate::{
activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType}, activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -22,10 +21,7 @@ use activitypub_federation::{
protocol::verification::{verify_domains_match, verify_urls_match}, protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
context::LemmyContext,
post::{EditPost, PostResponse},
};
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::PostAggregates, aggregates::structs::PostAggregates,
newtypes::PersonId, newtypes::PersonId,
@ -40,25 +36,6 @@ use lemmy_db_schema::{
use lemmy_utils::error::{LemmyError, LemmyErrorType}; use lemmy_utils::error::{LemmyError, LemmyErrorType};
use url::Url; use url::Url;
#[async_trait::async_trait]
impl SendActivity for EditPost {
type Response = PostResponse;
async fn send_activity(
_request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
CreateOrUpdatePage::send(
response.post_view.post.clone(),
response.post_view.creator.id,
CreateOrUpdateType::Update,
context.reset_request_count(),
)
.await
}
}
impl CreateOrUpdatePage { impl CreateOrUpdatePage {
pub(crate) async fn new( pub(crate) async fn new(
post: ApubPost, post: ApubPost,

View File

@ -6,7 +6,6 @@ use crate::{
create_or_update::chat_message::CreateOrUpdateChatMessage, create_or_update::chat_message::CreateOrUpdateChatMessage,
CreateOrUpdateType, CreateOrUpdateType,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -22,77 +21,33 @@ use lemmy_db_schema::{
source::{activity::ActivitySendTargets, person::Person, private_message::PrivateMessage}, source::{activity::ActivitySendTargets, person::Person, private_message::PrivateMessage},
traits::Crud, traits::Crud,
}; };
use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait] pub(crate) async fn send_create_or_update_pm(
impl SendActivity for CreatePrivateMessage { pm_view: PrivateMessageView,
type Response = PrivateMessageResponse; kind: CreateOrUpdateType,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let actor: ApubPerson = pm_view.creator.into();
let recipient: ApubPerson = pm_view.recipient.into();
async fn send_activity( let id = generate_activity_id(
_request: &Self, kind.clone(),
response: &Self::Response, &context.settings().get_protocol_and_hostname(),
context: &Data<LemmyContext>, )?;
) -> Result<(), LemmyError> { let create_or_update = CreateOrUpdateChatMessage {
CreateOrUpdateChatMessage::send( id: id.clone(),
&response.private_message_view.private_message, actor: actor.id().into(),
response.private_message_view.creator.id, to: [recipient.id().into()],
CreateOrUpdateType::Create, object: ApubPrivateMessage(pm_view.private_message.clone())
context, .into_json(&context)
) .await?,
.await kind,
} };
} let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox());
#[async_trait::async_trait] send_lemmy_activity(&context, create_or_update, &actor, inbox, true).await
impl SendActivity for EditPrivateMessage {
type Response = PrivateMessageResponse;
async fn send_activity(
_request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
CreateOrUpdateChatMessage::send(
&response.private_message_view.private_message,
response.private_message_view.creator.id,
CreateOrUpdateType::Update,
context,
)
.await
}
}
impl CreateOrUpdateChatMessage {
#[tracing::instrument(skip_all)]
async fn send(
private_message: &PrivateMessage,
sender_id: PersonId,
kind: CreateOrUpdateType,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let recipient_id = private_message.recipient_id;
let sender: ApubPerson = Person::read(&mut context.pool(), sender_id).await?.into();
let recipient: ApubPerson = Person::read(&mut context.pool(), recipient_id)
.await?
.into();
let id = generate_activity_id(
kind.clone(),
&context.settings().get_protocol_and_hostname(),
)?;
let create_or_update = CreateOrUpdateChatMessage {
id: id.clone(),
actor: sender.id().into(),
to: [recipient.id().into()],
object: ApubPrivateMessage(private_message.clone())
.into_json(context)
.await?,
kind,
};
let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox());
send_lemmy_activity(context, create_or_update, &sender, inbox, true).await
}
} }
#[async_trait::async_trait] #[async_trait::async_trait]

View File

@ -3,7 +3,6 @@ use crate::{
insert_received_activity, insert_received_activity,
objects::person::ApubPerson, objects::person::ApubPerson,
protocol::activities::deletion::delete_user::DeleteUser, protocol::activities::deletion::delete_user::DeleteUser,
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -17,45 +16,38 @@ use lemmy_api_common::{
utils::{delete_user_account, local_user_view_from_jwt}, utils::{delete_user_account, local_user_view_from_jwt},
}; };
use lemmy_db_schema::source::activity::ActivitySendTargets; use lemmy_db_schema::source::activity::ActivitySendTargets;
use lemmy_api_common::{context::LemmyContext, utils::delete_user_account};
use lemmy_db_schema::source::person::Person;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
#[async_trait::async_trait] pub async fn delete_user(person: Person, context: Data<LemmyContext>) -> Result<(), LemmyError> {
impl SendActivity for DeleteAccount { let actor: ApubPerson = person.into();
type Response = DeleteAccountResponse; delete_user_account(
actor.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
async fn send_activity( let id = generate_activity_id(
request: &Self, DeleteType::Delete,
_response: &Self::Response, &context.settings().get_protocol_and_hostname(),
context: &Data<LemmyContext>, )?;
) -> Result<(), LemmyError> { let delete = DeleteUser {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; actor: actor.id().into(),
let actor: ApubPerson = local_user_view.person.into(); to: vec![public()],
delete_user_account( object: actor.id().into(),
actor.id, kind: DeleteType::Delete,
&mut context.pool(), id: id.clone(),
context.settings(), cc: vec![],
context.client(), };
)
.await?;
let id = generate_activity_id(
DeleteType::Delete,
&context.settings().get_protocol_and_hostname(),
)?;
let delete = DeleteUser {
actor: actor.id().into(),
to: vec![public()],
object: actor.id().into(),
kind: DeleteType::Delete,
id: id.clone(),
cc: vec![],
};
let mut inboxes = ActivitySendTargets::empty(); let mut inboxes = ActivitySendTargets::empty();
inboxes.set_all_instances(true); inboxes.set_all_instances(true);
send_lemmy_activity(context, delete, &actor, inboxes, true).await?; send_lemmy_activity(&context, delete, &actor, inboxes, true).await?;
Ok(()) Ok(())
} }
} }

View File

@ -19,7 +19,6 @@ use crate::{
activities::deletion::{delete::Delete, undo_delete::UndoDelete}, activities::deletion::{delete::Delete, undo_delete::UndoDelete},
InCommunity, InCommunity,
}, },
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -28,15 +27,9 @@ use activitypub_federation::{
protocol::verification::verify_domains_match, protocol::verification::verify_domains_match,
traits::{Actor, Object}, traits::{Actor, Object},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
comment::{CommentResponse, DeleteComment, RemoveComment},
community::{CommunityResponse, DeleteCommunity, RemoveCommunity},
context::LemmyContext,
post::{DeletePost, PostResponse, RemovePost},
private_message::{DeletePrivateMessage, PrivateMessageResponse},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::CommunityId,
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
comment::{Comment, CommentUpdateForm}, comment::{Comment, CommentUpdateForm},
@ -55,170 +48,10 @@ pub mod delete;
pub mod delete_user; pub mod delete_user;
pub mod undo_delete; pub mod undo_delete;
#[async_trait::async_trait]
impl SendActivity for DeletePost {
type Response = PostResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), response.post_view.community.id).await?;
let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
None,
request.deleted,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for RemovePost {
type Response = PostResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), response.post_view.community.id).await?;
let deletable = DeletableObjects::Post(response.post_view.post.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
request.reason.clone().or_else(|| Some(String::new())),
request.removed,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for DeleteComment {
type Response = CommentResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let community_id = response.comment_view.community.id;
let community = Community::read(&mut context.pool(), community_id).await?;
let person = Person::read(&mut context.pool(), response.comment_view.creator.id).await?;
let deletable = DeletableObjects::Comment(response.comment_view.comment.clone().into());
send_apub_delete_in_community(person, community, deletable, None, request.deleted, context)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for RemoveComment {
type Response = CommentResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let comment = Comment::read(&mut context.pool(), request.comment_id).await?;
let community =
Community::read(&mut context.pool(), response.comment_view.community.id).await?;
let deletable = DeletableObjects::Comment(comment.into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
request.reason.clone().or_else(|| Some(String::new())),
request.removed,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for DeletePrivateMessage {
type Response = PrivateMessageResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
send_apub_delete_private_message(
&local_user_view.person.into(),
response.private_message_view.private_message.clone(),
request.deleted,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for DeleteCommunity {
type Response = CommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
None,
request.deleted,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for RemoveCommunity {
type Response = CommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(
local_user_view.person,
community,
deletable,
request.reason.clone().or_else(|| Some(String::new())),
request.removed,
context,
)
.await
}
}
/// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this /// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
/// action was done by a normal user. /// action was done by a normal user.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn send_apub_delete_in_community( pub(crate) async fn send_apub_delete_in_community(
actor: Person, actor: Person,
community: Community, community: Community,
object: DeletableObjects, object: DeletableObjects,
@ -246,12 +79,44 @@ async fn send_apub_delete_in_community(
.await .await
} }
/// Parameter `reason` being set indicates that this is a removal by a mod. If its unset, this
/// action was done by a normal user.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn send_apub_delete_private_message( pub(crate) async fn send_apub_delete_in_community_new(
actor: Person,
community_id: CommunityId,
object: DeletableObjects,
reason: Option<String>,
deleted: bool,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let community = Community::read(&mut context.pool(), community_id).await?;
let actor = ApubPerson::from(actor);
let is_mod_action = reason.is_some();
let activity = if deleted {
let delete = Delete::new(&actor, object, public(), Some(&community), reason, &context)?;
AnnouncableActivities::Delete(delete)
} else {
let undo = UndoDelete::new(&actor, object, public(), Some(&community), reason, &context)?;
AnnouncableActivities::UndoDelete(undo)
};
send_activity_in_community(
activity,
&actor,
&community.into(),
vec![],
is_mod_action,
&context,
)
.await
}
#[tracing::instrument(skip_all)]
pub(crate) async fn send_apub_delete_private_message(
actor: &ApubPerson, actor: &ApubPerson,
pm: PrivateMessage, pm: PrivateMessage,
deleted: bool, deleted: bool,
context: &Data<LemmyContext>, context: Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let recipient_id = pm.recipient_id; let recipient_id = pm.recipient_id;
let recipient: ApubPerson = Person::read(&mut context.pool(), recipient_id) let recipient: ApubPerson = Person::read(&mut context.pool(), recipient_id)
@ -261,11 +126,11 @@ async fn send_apub_delete_private_message(
let deletable = DeletableObjects::PrivateMessage(pm.into()); let deletable = DeletableObjects::PrivateMessage(pm.into());
let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox()); let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox());
if deleted { if deleted {
let delete: Delete = Delete::new(actor, deletable, recipient.id(), None, None, context)?; let delete: Delete = Delete::new(actor, deletable, recipient.id(), None, None, &context)?;
send_lemmy_activity(context, delete, actor, inbox, true).await?; send_lemmy_activity(&context, delete, actor, inbox, true).await?;
} else { } else {
let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, context)?; let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, &context)?;
send_lemmy_activity(context, undo, actor, inbox, true).await?; send_lemmy_activity(&context, undo, actor, inbox, true).await?;
}; };
Ok(()) Ok(())
} }

View File

@ -8,12 +8,7 @@ use crate::{
fetcher::user_or_community::UserOrCommunity, fetcher::user_or_community::UserOrCommunity,
insert_received_activity, insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{ protocol::activities::following::{accept::AcceptFollow, follow::Follow},
accept::AcceptFollow,
follow::Follow,
undo_follow::UndoFollow,
},
SendActivity,
}; };
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -21,18 +16,14 @@ use activitypub_federation::{
protocol::verification::verify_urls_match, protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor}, traits::{ActivityHandler, Actor},
}; };
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
community::{BlockCommunity, BlockCommunityResponse},
context::LemmyContext,
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
community::{Community, CommunityFollower, CommunityFollowerForm}, community::{Community, CommunityFollower, CommunityFollowerForm},
person::{PersonFollower, PersonFollowerForm}, person::{PersonFollower, PersonFollowerForm},
}, },
traits::{Crud, Followable}, traits::Followable,
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
use url::Url; use url::Url;
@ -130,18 +121,3 @@ impl ActivityHandler for Follow {
AcceptFollow::send(self, context).await AcceptFollow::send(self, context).await
} }
} }
#[async_trait::async_trait]
impl SendActivity for BlockCommunity {
type Response = BlockCommunityResponse;
async fn send_activity(
request: &Self,
_response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?;
let community = Community::read(&mut context.pool(), request.community_id).await?;
UndoFollow::send(&local_user_view.person.into(), &community.into(), context).await
}
}

View File

@ -1,41 +1,27 @@
use crate::{ use crate::{
objects::community::ApubCommunity, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{follow::Follow, undo_follow::UndoFollow}, protocol::activities::following::{follow::Follow, undo_follow::UndoFollow},
SendActivity,
}; };
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
community::{CommunityResponse, FollowCommunity}, use lemmy_db_schema::source::{community::Community, person::Person};
context::LemmyContext,
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{source::community::Community, traits::Crud};
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
pub mod accept; pub mod accept;
pub mod follow; pub mod follow;
pub mod undo_follow; pub mod undo_follow;
#[async_trait::async_trait] pub async fn send_follow_community(
impl SendActivity for FollowCommunity { community: Community,
type Response = CommunityResponse; person: Person,
follow: bool,
async fn send_activity( context: &Data<LemmyContext>,
request: &Self, ) -> Result<(), LemmyError> {
_response: &Self::Response, let community: ApubCommunity = community.into();
context: &Data<LemmyContext>, let actor: ApubPerson = person.into();
) -> Result<(), LemmyError> { if follow {
let local_user_view = local_user_view_from_jwt(&request.auth, context).await?; Follow::send(&actor, &community, context).await
let person = local_user_view.person.clone().into(); } else {
let community: ApubCommunity = Community::read(&mut context.pool(), request.community_id) UndoFollow::send(&actor, &community, context).await
.await?
.into();
if community.local {
Ok(())
} else if request.follow {
Follow::send(&person, &community, context).await
} else {
UndoFollow::send(&person, &community, context).await
}
} }
} }

View File

@ -1,6 +1,25 @@
use self::following::send_follow_community;
use crate::{ use crate::{
activities::{
block::{send_ban_from_community, send_ban_from_site},
community::{
collection_add::{send_add_mod_to_community, send_feature_post},
lock_page::send_lock_post,
update::send_update_community,
},
create_or_update::private_message::send_create_or_update_pm,
deletion::{
delete_user::delete_user,
send_apub_delete_in_community,
send_apub_delete_in_community_new,
send_apub_delete_private_message,
DeletableObjects,
},
voting::send_like_activity,
},
objects::{community::ApubCommunity, person::ApubPerson}, objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::{ protocol::activities::{
community::report::Report,
create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage}, create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage},
CreateOrUpdateType, CreateOrUpdateType,
}, },
@ -208,15 +227,102 @@ pub async fn match_outgoing_activities(
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let context = context.reset_request_count(); let context = context.reset_request_count();
let fed_task = async { let fed_task = async {
use SendActivityData::*;
match data { match data {
SendActivityData::CreatePost(post) => { CreatePost(post) => {
let creator_id = post.creator_id; let creator_id = post.creator_id;
CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Create, context).await CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Create, context).await
} }
SendActivityData::CreateComment(comment) => { UpdatePost(post) => {
let creator_id = post.creator_id;
CreateOrUpdatePage::send(post, creator_id, CreateOrUpdateType::Update, context).await
}
DeletePost(post, person, data) => {
send_apub_delete_in_community_new(
person,
post.community_id,
DeletableObjects::Post(post.into()),
None,
data.deleted,
context,
)
.await
}
RemovePost(post, person, data) => {
send_apub_delete_in_community_new(
person,
post.community_id,
DeletableObjects::Post(post.into()),
data.reason.or_else(|| Some(String::new())),
data.removed,
context,
)
.await
}
LockPost(post, actor, locked) => send_lock_post(post, actor, locked, context).await,
FeaturePost(post, actor, featured) => send_feature_post(post, actor, featured, context).await,
CreateComment(comment) => {
let creator_id = comment.creator_id; let creator_id = comment.creator_id;
CreateOrUpdateNote::send(comment, creator_id, CreateOrUpdateType::Create, context).await CreateOrUpdateNote::send(comment, creator_id, CreateOrUpdateType::Create, context).await
} }
UpdateComment(comment) => {
let creator_id = comment.creator_id;
CreateOrUpdateNote::send(comment, creator_id, CreateOrUpdateType::Update, context).await
}
DeleteComment(comment, actor, community) => {
let is_deleted = comment.deleted;
let deletable = DeletableObjects::Comment(comment.into());
send_apub_delete_in_community(actor, community, deletable, None, is_deleted, &context).await
}
RemoveComment(comment, actor, community, reason) => {
let is_removed = comment.removed;
let deletable = DeletableObjects::Comment(comment.into());
send_apub_delete_in_community(actor, community, deletable, reason, is_removed, &context)
.await
}
LikePostOrComment(object_id, person, community, score) => {
send_like_activity(object_id, person, community, score, context).await
}
FollowCommunity(community, person, follow) => {
send_follow_community(community, person, follow, &context).await
}
UpdateCommunity(actor, community) => send_update_community(community, actor, context).await,
DeleteCommunity(actor, community, removed) => {
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(actor, community, deletable, None, removed, &context).await
}
RemoveCommunity(actor, community, reason, removed) => {
let deletable = DeletableObjects::Community(community.clone().into());
send_apub_delete_in_community(
actor,
community,
deletable,
reason.clone().or_else(|| Some(String::new())),
removed,
&context,
)
.await
}
AddModToCommunity(actor, community_id, updated_mod_id, added) => {
send_add_mod_to_community(actor, community_id, updated_mod_id, added, context).await
}
BanFromCommunity(mod_, community_id, target, data) => {
send_ban_from_community(mod_, community_id, target, data, context).await
}
BanFromSite(mod_, target, data) => send_ban_from_site(mod_, target, data, context).await,
CreatePrivateMessage(pm) => {
send_create_or_update_pm(pm, CreateOrUpdateType::Create, context).await
}
UpdatePrivateMessage(pm) => {
send_create_or_update_pm(pm, CreateOrUpdateType::Update, context).await
}
DeletePrivateMessage(person, pm, deleted) => {
send_apub_delete_private_message(&person.into(), pm, deleted, context).await
}
DeleteUser(person) => delete_user(person, context).await,
CreateReport(url, actor, community, reason) => {
Report::send(ObjectId::from(url), actor, community, reason, context).await
}
} }
}; };
if *SYNCHRONOUS_FEDERATION { if *SYNCHRONOUS_FEDERATION {

View File

@ -2,23 +2,16 @@ use crate::{
activities::community::send_activity_in_community, activities::community::send_activity_in_community,
activity_lists::AnnouncableActivities, activity_lists::AnnouncableActivities,
fetcher::post_or_comment::PostOrComment, fetcher::post_or_comment::PostOrComment,
objects::{comment::ApubComment, person::ApubPerson, post::ApubPost}, objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::voting::{ protocol::activities::voting::{
undo_vote::UndoVote, undo_vote::UndoVote,
vote::{Vote, VoteType}, vote::{Vote, VoteType},
}, },
SendActivity,
}; };
use activitypub_federation::{config::Data, fetch::object_id::ObjectId}; use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
use lemmy_api_common::{ use lemmy_api_common::context::LemmyContext;
comment::{CommentResponse, CreateCommentLike},
context::LemmyContext,
post::{CreatePostLike, PostResponse},
sensitive::Sensitive,
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::CommunityId, newtypes::DbUrl,
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
comment::{CommentLike, CommentLikeForm}, comment::{CommentLike, CommentLikeForm},
@ -26,84 +19,36 @@ use lemmy_db_schema::{
person::Person, person::Person,
post::{PostLike, PostLikeForm}, post::{PostLike, PostLikeForm},
}, },
traits::{Crud, Likeable}, traits::Likeable,
}; };
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
pub mod undo_vote; pub mod undo_vote;
pub mod vote; pub mod vote;
#[async_trait::async_trait] pub(crate) async fn send_like_activity(
impl SendActivity for CreatePostLike { object_id: DbUrl,
type Response = PostResponse; actor: Person,
community: Community,
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let object_id = ObjectId::from(response.post_view.post.ap_id.clone());
let community_id = response.post_view.community.id;
send_activity(
object_id,
community_id,
request.score,
&request.auth,
context,
)
.await
}
}
#[async_trait::async_trait]
impl SendActivity for CreateCommentLike {
type Response = CommentResponse;
async fn send_activity(
request: &Self,
response: &Self::Response,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> {
let object_id = ObjectId::from(response.comment_view.comment.ap_id.clone());
let community_id = response.comment_view.community.id;
send_activity(
object_id,
community_id,
request.score,
&request.auth,
context,
)
.await
}
}
async fn send_activity(
object_id: ObjectId<PostOrComment>,
community_id: CommunityId,
score: i16, score: i16,
jwt: &Sensitive<String>, context: Data<LemmyContext>,
context: &Data<LemmyContext>,
) -> Result<(), LemmyError> { ) -> Result<(), LemmyError> {
let community = Community::read(&mut context.pool(), community_id) let object_id: ObjectId<PostOrComment> = object_id.try_into()?;
.await? let actor: ApubPerson = actor.into();
.into(); let community: ApubCommunity = community.into();
let local_user_view = local_user_view_from_jwt(jwt, context).await?;
let actor = Person::read(&mut context.pool(), local_user_view.person.id)
.await?
.into();
let empty = ActivitySendTargets::empty(); let empty = ActivitySendTargets::empty();
// score of 1 means upvote, -1 downvote, 0 undo a previous vote // score of 1 means upvote, -1 downvote, 0 undo a previous vote
if score != 0 { if score != 0 {
let vote = Vote::new(object_id, &actor, &community, score.try_into()?, context)?; let vote = Vote::new(object_id, &actor, &community, score.try_into()?, &context)?;
let activity = AnnouncableActivities::Vote(vote); let activity = AnnouncableActivities::Vote(vote);
send_activity_in_community(activity, &actor, &community, empty, false, context).await send_activity_in_community(activity, &actor, &community, empty, false, &context).await
} else { } else {
// Lemmy API doesnt distinguish between Undo/Like and Undo/Dislike, so we hardcode it here. // Lemmy API doesnt distinguish between Undo/Like and Undo/Dislike, so we hardcode it here.
let vote = Vote::new(object_id, &actor, &community, VoteType::Like, context)?; let vote = Vote::new(object_id, &actor, &community, VoteType::Like, &context)?;
let undo_vote = UndoVote::new(vote, &actor, &community, context)?; let undo_vote = UndoVote::new(vote, &actor, &community, &context)?;
let activity = AnnouncableActivities::UndoVote(undo_vote); let activity = AnnouncableActivities::UndoVote(undo_vote);
send_activity_in_community(activity, &actor, &community, empty, false, context).await send_activity_in_community(activity, &actor, &community, empty, false, &context).await
} }
} }

View File

@ -22,7 +22,6 @@ full = [
"bcrypt", "bcrypt",
"lemmy_utils", "lemmy_utils",
"activitypub_federation", "activitypub_federation",
"sha2",
"regex", "regex",
"once_cell", "once_cell",
"serde_json", "serde_json",
@ -60,7 +59,6 @@ diesel-async = { workspace = true, features = [
"postgres", "postgres",
"deadpool", "deadpool",
], optional = true } ], optional = true }
sha2 = { workspace = true, optional = true }
regex = { workspace = true, optional = true } regex = { workspace = true, optional = true }
once_cell = { workspace = true, optional = true } once_cell = { workspace = true, optional = true }
diesel_ltree = { workspace = true, optional = true } diesel_ltree = { workspace = true, optional = true }

View File

@ -156,15 +156,6 @@ impl Crud for Comment {
type InsertForm = CommentInsertForm; type InsertForm = CommentInsertForm;
type UpdateForm = CommentUpdateForm; type UpdateForm = CommentUpdateForm;
type IdType = CommentId; type IdType = CommentId;
async fn read(pool: &mut DbPool<'_>, comment_id: CommentId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
comment.find(comment_id).first::<Self>(conn).await
}
async fn delete(pool: &mut DbPool<'_>, comment_id: CommentId) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(comment.find(comment_id)).execute(conn).await
}
/// This is unimplemented, use [[Comment::create]] /// This is unimplemented, use [[Comment::create]]
async fn create(_pool: &mut DbPool<'_>, _comment_form: &Self::InsertForm) -> Result<Self, Error> { async fn create(_pool: &mut DbPool<'_>, _comment_form: &Self::InsertForm) -> Result<Self, Error> {

View File

@ -13,13 +13,6 @@ impl Crud for CommentReply {
type InsertForm = CommentReplyInsertForm; type InsertForm = CommentReplyInsertForm;
type UpdateForm = CommentReplyUpdateForm; type UpdateForm = CommentReplyUpdateForm;
type IdType = CommentReplyId; type IdType = CommentReplyId;
async fn read(pool: &mut DbPool<'_>, comment_reply_id: CommentReplyId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
comment_reply
.find(comment_reply_id)
.first::<Self>(conn)
.await
}
async fn create( async fn create(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,

View File

@ -27,20 +27,6 @@ impl Crud for Community {
type InsertForm = CommunityInsertForm; type InsertForm = CommunityInsertForm;
type UpdateForm = CommunityUpdateForm; type UpdateForm = CommunityUpdateForm;
type IdType = CommunityId; type IdType = CommunityId;
async fn read(pool: &mut DbPool<'_>, community_id: CommunityId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
community::table
.find(community_id)
.first::<Self>(conn)
.await
}
async fn delete(pool: &mut DbPool<'_>, community_id: CommunityId) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(community::table.find(community_id))
.execute(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let is_new_community = match &form.actor_id { let is_new_community = match &form.actor_id {

View File

@ -69,16 +69,7 @@ impl Crud for LocalUser {
type InsertForm = LocalUserInsertForm; type InsertForm = LocalUserInsertForm;
type UpdateForm = LocalUserUpdateForm; type UpdateForm = LocalUserUpdateForm;
type IdType = LocalUserId; type IdType = LocalUserId;
async fn read(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
local_user.find(local_user_id).first::<Self>(conn).await
}
async fn delete(pool: &mut DbPool<'_>, local_user_id: LocalUserId) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(local_user.find(local_user_id))
.execute(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let mut form_with_encrypted_password = form.clone(); let mut form_with_encrypted_password = form.clone();

View File

@ -42,11 +42,6 @@ impl Crud for ModRemovePost {
type InsertForm = ModRemovePostForm; type InsertForm = ModRemovePostForm;
type UpdateForm = ModRemovePostForm; type UpdateForm = ModRemovePostForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_remove_post::dsl::mod_remove_post;
let conn = &mut get_conn(pool).await?;
mod_remove_post.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModRemovePostForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModRemovePostForm) -> Result<Self, Error> {
use crate::schema::mod_remove_post::dsl::mod_remove_post; use crate::schema::mod_remove_post::dsl::mod_remove_post;
@ -76,11 +71,6 @@ impl Crud for ModLockPost {
type InsertForm = ModLockPostForm; type InsertForm = ModLockPostForm;
type UpdateForm = ModLockPostForm; type UpdateForm = ModLockPostForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_lock_post::dsl::mod_lock_post;
let conn = &mut get_conn(pool).await?;
mod_lock_post.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModLockPostForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModLockPostForm) -> Result<Self, Error> {
use crate::schema::mod_lock_post::dsl::mod_lock_post; use crate::schema::mod_lock_post::dsl::mod_lock_post;
@ -110,11 +100,6 @@ impl Crud for ModFeaturePost {
type InsertForm = ModFeaturePostForm; type InsertForm = ModFeaturePostForm;
type UpdateForm = ModFeaturePostForm; type UpdateForm = ModFeaturePostForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_feature_post::dsl::mod_feature_post;
let conn = &mut get_conn(pool).await?;
mod_feature_post.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModFeaturePostForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModFeaturePostForm) -> Result<Self, Error> {
use crate::schema::mod_feature_post::dsl::mod_feature_post; use crate::schema::mod_feature_post::dsl::mod_feature_post;
@ -144,11 +129,6 @@ impl Crud for ModRemoveComment {
type InsertForm = ModRemoveCommentForm; type InsertForm = ModRemoveCommentForm;
type UpdateForm = ModRemoveCommentForm; type UpdateForm = ModRemoveCommentForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_remove_comment::dsl::mod_remove_comment;
let conn = &mut get_conn(pool).await?;
mod_remove_comment.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModRemoveCommentForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModRemoveCommentForm) -> Result<Self, Error> {
use crate::schema::mod_remove_comment::dsl::mod_remove_comment; use crate::schema::mod_remove_comment::dsl::mod_remove_comment;
@ -178,11 +158,6 @@ impl Crud for ModRemoveCommunity {
type InsertForm = ModRemoveCommunityForm; type InsertForm = ModRemoveCommunityForm;
type UpdateForm = ModRemoveCommunityForm; type UpdateForm = ModRemoveCommunityForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_remove_community::dsl::mod_remove_community;
let conn = &mut get_conn(pool).await?;
mod_remove_community.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModRemoveCommunityForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModRemoveCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_remove_community::dsl::mod_remove_community; use crate::schema::mod_remove_community::dsl::mod_remove_community;
@ -212,14 +187,6 @@ impl Crud for ModBanFromCommunity {
type InsertForm = ModBanFromCommunityForm; type InsertForm = ModBanFromCommunityForm;
type UpdateForm = ModBanFromCommunityForm; type UpdateForm = ModBanFromCommunityForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_ban_from_community::dsl::mod_ban_from_community;
let conn = &mut get_conn(pool).await?;
mod_ban_from_community
.find(from_id)
.first::<Self>(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &ModBanFromCommunityForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModBanFromCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_ban_from_community::dsl::mod_ban_from_community; use crate::schema::mod_ban_from_community::dsl::mod_ban_from_community;
@ -249,11 +216,6 @@ impl Crud for ModBan {
type InsertForm = ModBanForm; type InsertForm = ModBanForm;
type UpdateForm = ModBanForm; type UpdateForm = ModBanForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_ban::dsl::mod_ban;
let conn = &mut get_conn(pool).await?;
mod_ban.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModBanForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModBanForm) -> Result<Self, Error> {
use crate::schema::mod_ban::dsl::mod_ban; use crate::schema::mod_ban::dsl::mod_ban;
@ -280,12 +242,6 @@ impl Crud for ModHideCommunity {
type UpdateForm = ModHideCommunityForm; type UpdateForm = ModHideCommunityForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_hide_community::dsl::mod_hide_community;
let conn = &mut get_conn(pool).await?;
mod_hide_community.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModHideCommunityForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModHideCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_hide_community::dsl::mod_hide_community; use crate::schema::mod_hide_community::dsl::mod_hide_community;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
@ -314,11 +270,6 @@ impl Crud for ModAddCommunity {
type InsertForm = ModAddCommunityForm; type InsertForm = ModAddCommunityForm;
type UpdateForm = ModAddCommunityForm; type UpdateForm = ModAddCommunityForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_add_community::dsl::mod_add_community;
let conn = &mut get_conn(pool).await?;
mod_add_community.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModAddCommunityForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModAddCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_add_community::dsl::mod_add_community; use crate::schema::mod_add_community::dsl::mod_add_community;
@ -348,14 +299,6 @@ impl Crud for ModTransferCommunity {
type InsertForm = ModTransferCommunityForm; type InsertForm = ModTransferCommunityForm;
type UpdateForm = ModTransferCommunityForm; type UpdateForm = ModTransferCommunityForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_transfer_community::dsl::mod_transfer_community;
let conn = &mut get_conn(pool).await?;
mod_transfer_community
.find(from_id)
.first::<Self>(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &ModTransferCommunityForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModTransferCommunityForm) -> Result<Self, Error> {
use crate::schema::mod_transfer_community::dsl::mod_transfer_community; use crate::schema::mod_transfer_community::dsl::mod_transfer_community;
@ -385,11 +328,6 @@ impl Crud for ModAdd {
type InsertForm = ModAddForm; type InsertForm = ModAddForm;
type UpdateForm = ModAddForm; type UpdateForm = ModAddForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::mod_add::dsl::mod_add;
let conn = &mut get_conn(pool).await?;
mod_add.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &ModAddForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &ModAddForm) -> Result<Self, Error> {
use crate::schema::mod_add::dsl::mod_add; use crate::schema::mod_add::dsl::mod_add;
@ -415,11 +353,6 @@ impl Crud for AdminPurgePerson {
type InsertForm = AdminPurgePersonForm; type InsertForm = AdminPurgePersonForm;
type UpdateForm = AdminPurgePersonForm; type UpdateForm = AdminPurgePersonForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::admin_purge_person::dsl::admin_purge_person;
let conn = &mut get_conn(pool).await?;
admin_purge_person.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
use crate::schema::admin_purge_person::dsl::admin_purge_person; use crate::schema::admin_purge_person::dsl::admin_purge_person;
@ -449,14 +382,6 @@ impl Crud for AdminPurgeCommunity {
type InsertForm = AdminPurgeCommunityForm; type InsertForm = AdminPurgeCommunityForm;
type UpdateForm = AdminPurgeCommunityForm; type UpdateForm = AdminPurgeCommunityForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::admin_purge_community::dsl::admin_purge_community;
let conn = &mut get_conn(pool).await?;
admin_purge_community
.find(from_id)
.first::<Self>(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
use crate::schema::admin_purge_community::dsl::admin_purge_community; use crate::schema::admin_purge_community::dsl::admin_purge_community;
@ -486,11 +411,6 @@ impl Crud for AdminPurgePost {
type InsertForm = AdminPurgePostForm; type InsertForm = AdminPurgePostForm;
type UpdateForm = AdminPurgePostForm; type UpdateForm = AdminPurgePostForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::admin_purge_post::dsl::admin_purge_post;
let conn = &mut get_conn(pool).await?;
admin_purge_post.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
use crate::schema::admin_purge_post::dsl::admin_purge_post; use crate::schema::admin_purge_post::dsl::admin_purge_post;
@ -520,11 +440,6 @@ impl Crud for AdminPurgeComment {
type InsertForm = AdminPurgeCommentForm; type InsertForm = AdminPurgeCommentForm;
type UpdateForm = AdminPurgeCommentForm; type UpdateForm = AdminPurgeCommentForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, from_id: i32) -> Result<Self, Error> {
use crate::schema::admin_purge_comment::dsl::admin_purge_comment;
let conn = &mut get_conn(pool).await?;
admin_purge_comment.find(from_id).first::<Self>(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
use crate::schema::admin_purge_comment::dsl::admin_purge_comment; use crate::schema::admin_purge_comment::dsl::admin_purge_comment;

View File

@ -1,11 +1,6 @@
use crate::{ use crate::{
newtypes::LocalUserId, newtypes::LocalUserId,
schema::password_reset_request::dsl::{ schema::password_reset_request::dsl::{local_user_id, password_reset_request, published, token},
local_user_id,
password_reset_request,
published,
token_encrypted,
},
source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm}, source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm},
traits::Crud, traits::Crud,
utils::{get_conn, DbPool}, utils::{get_conn, DbPool},
@ -17,20 +12,13 @@ use diesel::{
QueryDsl, QueryDsl,
}; };
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use sha2::{Digest, Sha256};
#[async_trait] #[async_trait]
impl Crud for PasswordResetRequest { impl Crud for PasswordResetRequest {
type InsertForm = PasswordResetRequestForm; type InsertForm = PasswordResetRequestForm;
type UpdateForm = PasswordResetRequestForm; type UpdateForm = PasswordResetRequestForm;
type IdType = i32; type IdType = i32;
async fn read(pool: &mut DbPool<'_>, password_reset_request_id: i32) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
password_reset_request
.find(password_reset_request_id)
.first::<Self>(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &PasswordResetRequestForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &PasswordResetRequestForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(password_reset_request) insert_into(password_reset_request)
@ -55,29 +43,22 @@ impl PasswordResetRequest {
pub async fn create_token( pub async fn create_token(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
from_local_user_id: LocalUserId, from_local_user_id: LocalUserId,
token: &str, token_: String,
) -> Result<PasswordResetRequest, Error> { ) -> Result<PasswordResetRequest, Error> {
let mut hasher = Sha256::new();
hasher.update(token);
let token_hash: String = bytes_to_hex(hasher.finalize().to_vec());
let form = PasswordResetRequestForm { let form = PasswordResetRequestForm {
local_user_id: from_local_user_id, local_user_id: from_local_user_id,
token_encrypted: token_hash, token: token_,
}; };
Self::create(pool, &form).await Self::create(pool, &form).await
} }
pub async fn read_from_token( pub async fn read_from_token(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
token: &str, token_: &str,
) -> Result<PasswordResetRequest, Error> { ) -> Result<PasswordResetRequest, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let mut hasher = Sha256::new();
hasher.update(token);
let token_hash: String = bytes_to_hex(hasher.finalize().to_vec());
password_reset_request password_reset_request
.filter(token_encrypted.eq(token_hash)) .filter(token.eq(token_))
.filter(published.gt(now - 1.days())) .filter(published.gt(now - 1.days()))
.first::<Self>(conn) .first::<Self>(conn)
.await .await
@ -97,14 +78,6 @@ impl PasswordResetRequest {
} }
} }
fn bytes_to_hex(bytes: Vec<u8>) -> String {
let mut str = String::new();
for byte in bytes {
str = format!("{str}{byte:02x}");
}
str
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#![allow(clippy::unwrap_used)] #![allow(clippy::unwrap_used)]
@ -148,17 +121,16 @@ mod tests {
let inserted_local_user = LocalUser::create(pool, &new_local_user).await.unwrap(); let inserted_local_user = LocalUser::create(pool, &new_local_user).await.unwrap();
let token = "nope"; let token = "nope";
let token_encrypted_ = "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce";
let inserted_password_reset_request = let inserted_password_reset_request =
PasswordResetRequest::create_token(pool, inserted_local_user.id, token) PasswordResetRequest::create_token(pool, inserted_local_user.id, token.to_string())
.await .await
.unwrap(); .unwrap();
let expected_password_reset_request = PasswordResetRequest { let expected_password_reset_request = PasswordResetRequest {
id: inserted_password_reset_request.id, id: inserted_password_reset_request.id,
local_user_id: inserted_local_user.id, local_user_id: inserted_local_user.id,
token_encrypted: token_encrypted_.to_string(), token: token.to_string(),
published: inserted_password_reset_request.published, published: inserted_password_reset_request.published,
}; };

View File

@ -27,12 +27,7 @@ impl Crud for Person {
.first::<Self>(conn) .first::<Self>(conn)
.await .await
} }
async fn delete(pool: &mut DbPool<'_>, person_id: PersonId) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(person::table.find(person_id))
.execute(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &PersonInsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(person::table) insert_into(person::table)

View File

@ -13,13 +13,6 @@ impl Crud for PersonMention {
type InsertForm = PersonMentionInsertForm; type InsertForm = PersonMentionInsertForm;
type UpdateForm = PersonMentionUpdateForm; type UpdateForm = PersonMentionUpdateForm;
type IdType = PersonMentionId; type IdType = PersonMentionId;
async fn read(pool: &mut DbPool<'_>, person_mention_id: PersonMentionId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
person_mention
.find(person_mention_id)
.first::<Self>(conn)
.await
}
async fn create( async fn create(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,

View File

@ -38,15 +38,6 @@ impl Crud for Post {
type InsertForm = PostInsertForm; type InsertForm = PostInsertForm;
type UpdateForm = PostUpdateForm; type UpdateForm = PostUpdateForm;
type IdType = PostId; type IdType = PostId;
async fn read(pool: &mut DbPool<'_>, post_id: PostId) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
post.find(post_id).first::<Self>(conn).await
}
async fn delete(pool: &mut DbPool<'_>, post_id: PostId) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(post.find(post_id)).execute(conn).await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;

View File

@ -15,16 +15,6 @@ impl Crud for PrivateMessage {
type InsertForm = PrivateMessageInsertForm; type InsertForm = PrivateMessageInsertForm;
type UpdateForm = PrivateMessageUpdateForm; type UpdateForm = PrivateMessageUpdateForm;
type IdType = PrivateMessageId; type IdType = PrivateMessageId;
async fn read(
pool: &mut DbPool<'_>,
private_message_id: PrivateMessageId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
private_message
.find(private_message_id)
.first::<Self>(conn)
.await
}
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
@ -48,12 +38,6 @@ impl Crud for PrivateMessage {
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn delete(pool: &mut DbPool<'_>, pm_id: Self::IdType) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(private_message.find(pm_id))
.execute(conn)
.await
}
} }
impl PrivateMessage { impl PrivateMessage {

View File

@ -26,11 +26,6 @@ impl Crud for RegistrationApplication {
.await .await
} }
async fn read(pool: &mut DbPool<'_>, id_: Self::IdType) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
registration_application.find(id_).first::<Self>(conn).await
}
async fn update( async fn update(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
id_: Self::IdType, id_: Self::IdType,
@ -42,13 +37,6 @@ impl Crud for RegistrationApplication {
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn delete(pool: &mut DbPool<'_>, id_: Self::IdType) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(registration_application.find(id_))
.execute(conn)
.await
}
} }
impl RegistrationApplication { impl RegistrationApplication {

View File

@ -58,11 +58,6 @@ impl Crud for Site {
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
async fn delete(pool: &mut DbPool<'_>, site_id: SiteId) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(site.find(site_id)).execute(conn).await
}
} }
impl Site { impl Site {

View File

@ -548,7 +548,7 @@ diesel::table! {
diesel::table! { diesel::table! {
password_reset_request (id) { password_reset_request (id) {
id -> Int4, id -> Int4,
token_encrypted -> Text, token -> Text,
published -> Timestamp, published -> Timestamp,
local_user_id -> Int4, local_user_id -> Int4,
} }

View File

@ -7,7 +7,7 @@ use crate::schema::password_reset_request;
#[cfg_attr(feature = "full", diesel(table_name = password_reset_request))] #[cfg_attr(feature = "full", diesel(table_name = password_reset_request))]
pub struct PasswordResetRequest { pub struct PasswordResetRequest {
pub id: i32, pub id: i32,
pub token_encrypted: String, pub token: String,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub local_user_id: LocalUserId, pub local_user_id: LocalUserId,
} }
@ -16,5 +16,5 @@ pub struct PasswordResetRequest {
#[cfg_attr(feature = "full", diesel(table_name = password_reset_request))] #[cfg_attr(feature = "full", diesel(table_name = password_reset_request))]
pub struct PasswordResetRequestForm { pub struct PasswordResetRequestForm {
pub local_user_id: LocalUserId, pub local_user_id: LocalUserId,
pub token_encrypted: String, pub token: String,
} }

View File

@ -1,34 +1,64 @@
use crate::{ use crate::{
newtypes::{CommunityId, DbUrl, PersonId}, newtypes::{CommunityId, DbUrl, PersonId},
utils::DbPool, utils::{get_conn, DbPool},
};
use diesel::{
associations::HasTable,
dsl,
query_builder::{DeleteStatement, IntoUpdateTarget},
query_dsl::methods::{FindDsl, LimitDsl},
result::Error,
Table,
};
use diesel_async::{
methods::{ExecuteDsl, LoadQuery},
AsyncPgConnection,
RunQueryDsl,
}; };
use diesel::result::Error;
/// Returned by `diesel::delete`
pub type Delete<T> = DeleteStatement<<T as HasTable>::Table, <T as IntoUpdateTarget>::WhereClause>;
/// Returned by `Self::table().find(id)`
pub type Find<T> = dsl::Find<<T as HasTable>::Table, <T as Crud>::IdType>;
pub type PrimaryKey<T> = <<T as HasTable>::Table as Table>::PrimaryKey;
// Trying to create default implementations for `create` and `update` results in a lifetime mess and weird compile errors.
// https://github.com/rust-lang/rust/issues/102211
#[async_trait] #[async_trait]
pub trait Crud { pub trait Crud: HasTable + Sized
where
Self::Table: FindDsl<Self::IdType>,
Find<Self>: LimitDsl + IntoUpdateTarget + Send,
Delete<Find<Self>>: ExecuteDsl<AsyncPgConnection> + Send + 'static,
// Used by `RunQueryDsl::first`
dsl::Limit<Find<Self>>: LoadQuery<'static, AsyncPgConnection, Self> + Send + 'static,
{
type InsertForm; type InsertForm;
type UpdateForm; type UpdateForm;
type IdType; type IdType: Send;
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error>
where async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error>;
Self: Sized;
async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result<Self, Error> async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result<Self, Error> {
where let query: Find<Self> = Self::table().find(id);
Self: Sized; let conn = &mut *get_conn(pool).await?;
query.first::<Self>(conn).await
}
/// when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column. /// when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column.
async fn update( async fn update(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
id: Self::IdType, id: Self::IdType,
form: &Self::UpdateForm, form: &Self::UpdateForm,
) -> Result<Self, Error> ) -> Result<Self, Error>;
where
Self: Sized; async fn delete(pool: &mut DbPool<'_>, id: Self::IdType) -> Result<usize, Error> {
async fn delete(_pool: &mut DbPool<'_>, _id: Self::IdType) -> Result<usize, Error> let query: Delete<Find<Self>> = diesel::delete(Self::table().find(id));
where let conn = &mut *get_conn(pool).await?;
Self: Sized, query.execute(conn).await
Self::IdType: Send,
{
async { Err(Error::NotFound) }.await
} }
} }

View File

@ -308,7 +308,10 @@ fn queries<'a>() -> Queries<
.map(|l| l.local_user.show_read_posts) .map(|l| l.local_user.show_read_posts)
.unwrap_or(true) .unwrap_or(true)
{ {
query = query.filter(post_read::post_id.is_null()); // Do not hide read posts when it is a user profile view
if !is_profile_view {
query = query.filter(post_read::post_id.is_null());
}
} }
if options.local_user.is_some() { if options.local_user.is_some() {

View File

@ -48,7 +48,9 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
} else { } else {
None None
}; };
let open_registrations = Some(site_view.local_site.registration_mode == RegistrationMode::Open); // Since there are 3 registration options,
// we need to set open_registrations as true if RegistrationMode is not Closed.
let open_registrations = Some(site_view.local_site.registration_mode != RegistrationMode::Closed);
let json = NodeInfo { let json = NodeInfo {
version: Some("2.0".to_string()), version: Some("2.0".to_string()),
software: Some(NodeInfoSoftware { software: Some(NodeInfoSoftware {

View File

@ -1,6 +1,7 @@
-- This file was automatically created by Diesel to setup helper functions -- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future -- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations. -- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at (_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at ();
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

View File

@ -1,10 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions -- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future -- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations. -- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called -- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included -- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns) -- in the modified columns)
@ -16,21 +12,25 @@
-- --
-- SELECT diesel_manage_updated_at('users'); -- SELECT diesel_manage_updated_at('users');
-- ``` -- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ CREATE OR REPLACE FUNCTION diesel_manage_updated_at (_tbl regclass)
RETURNS VOID
AS $$
BEGIN BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END; END;
$$ LANGUAGE plpgsql; $$
LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ CREATE OR REPLACE FUNCTION diesel_set_updated_at ()
RETURNS TRIGGER
AS $$
BEGIN BEGIN
IF ( IF (NEW IS DISTINCT FROM OLD AND NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at) THEN
NEW IS DISTINCT FROM OLD AND NEW.updated_at := CURRENT_TIMESTAMP;
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF; END IF;
RETURN NEW; RETURN NEW;
END; END;
$$ LANGUAGE plpgsql; $$
LANGUAGE plpgsql;

View File

@ -1,2 +1,4 @@
drop table user_ban; DROP TABLE user_ban;
drop table user_;
DROP TABLE user_;

View File

@ -1,23 +1,25 @@
create table user_ ( CREATE TABLE user_ (
id serial primary key, id serial PRIMARY KEY,
name varchar(20) not null, name varchar(20) NOT NULL,
fedi_name varchar(40) not null, fedi_name varchar(40) NOT NULL,
preferred_username varchar(20), preferred_username varchar(20),
password_encrypted text not null, password_encrypted text NOT NULL,
email text unique, email text UNIQUE,
icon bytea, icon bytea,
admin boolean default false not null, admin boolean DEFAULT FALSE NOT NULL,
banned boolean default false not null, banned boolean DEFAULT FALSE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
updated timestamp, updated timestamp,
unique(name, fedi_name) UNIQUE (name, fedi_name)
); );
create table user_ban ( CREATE TABLE user_ban (
id serial primary key, id serial PRIMARY KEY,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique (user_id) UNIQUE (user_id)
); );
insert into user_ (name, fedi_name, password_encrypted) values ('admin', 'TBD', 'TBD'); INSERT INTO user_ (name, fedi_name, password_encrypted)
VALUES ('admin', 'TBD', 'TBD');

View File

@ -1,6 +1,14 @@
drop table site; DROP TABLE site;
drop table community_user_ban;;
drop table community_moderator; DROP TABLE community_user_ban;
drop table community_follower;
drop table community; ;
drop table category;
DROP TABLE community_moderator;
DROP TABLE community_follower;
DROP TABLE community;
DROP TABLE category;

View File

@ -1,79 +1,81 @@
create table category ( CREATE TABLE category (
id serial primary key, id serial PRIMARY KEY,
name varchar(100) not null unique name varchar(100) NOT NULL UNIQUE
); );
insert into category (name) values INSERT INTO category (name)
('Discussion'), VALUES ('Discussion'),
('Humor/Memes'), ('Humor/Memes'),
('Gaming'), ('Gaming'),
('Movies'), ('Movies'),
('TV'), ('TV'),
('Music'), ('Music'),
('Literature'), ('Literature'),
('Comics'), ('Comics'),
('Photography'), ('Photography'),
('Art'), ('Art'),
('Learning'), ('Learning'),
('DIY'), ('DIY'),
('Lifestyle'), ('Lifestyle'),
('News'), ('News'),
('Politics'), ('Politics'),
('Society'), ('Society'),
('Gender/Identity/Sexuality'), ('Gender/Identity/Sexuality'),
('Race/Colonisation'), ('Race/Colonisation'),
('Religion'), ('Religion'),
('Science/Technology'), ('Science/Technology'),
('Programming/Software'), ('Programming/Software'),
('Health/Sports/Fitness'), ('Health/Sports/Fitness'),
('Porn'), ('Porn'),
('Places'), ('Places'),
('Meta'), ('Meta'),
('Other'); ('Other');
create table community ( CREATE TABLE community (
id serial primary key, id serial PRIMARY KEY,
name varchar(20) not null unique, name varchar(20) NOT NULL UNIQUE,
title varchar(100) not null, title varchar(100) NOT NULL,
description text, description text,
category_id int references category on update cascade on delete cascade not null, category_id int REFERENCES category ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
creator_id int references user_ on update cascade on delete cascade not null, creator_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
removed boolean default false not null, removed boolean DEFAULT FALSE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
updated timestamp updated timestamp
); );
create table community_moderator ( CREATE TABLE community_moderator (
id serial primary key, id serial PRIMARY KEY,
community_id int references community on update cascade on delete cascade not null, community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique (community_id, user_id) UNIQUE (community_id, user_id)
); );
create table community_follower ( CREATE TABLE community_follower (
id serial primary key, id serial PRIMARY KEY,
community_id int references community on update cascade on delete cascade not null, community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique (community_id, user_id) UNIQUE (community_id, user_id)
); );
create table community_user_ban ( CREATE TABLE community_user_ban (
id serial primary key, id serial PRIMARY KEY,
community_id int references community on update cascade on delete cascade not null, community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique (community_id, user_id) UNIQUE (community_id, user_id)
); );
insert into community (name, title, category_id, creator_id) values ('main', 'The Default Community', 1, 1); INSERT INTO community (name, title, category_id, creator_id)
VALUES ('main', 'The Default Community', 1, 1);
create table site ( CREATE TABLE site (
id serial primary key, id serial PRIMARY KEY,
name varchar(20) not null unique, name varchar(20) NOT NULL UNIQUE,
description text, description text,
creator_id int references user_ on update cascade on delete cascade not null, creator_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
updated timestamp updated timestamp
); );

View File

@ -1,4 +1,8 @@
drop table post_read; DROP TABLE post_read;
drop table post_saved;
drop table post_like; DROP TABLE post_saved;
drop table post;
DROP TABLE post_like;
DROP TABLE post;

View File

@ -1,37 +1,38 @@
create table post ( CREATE TABLE post (
id serial primary key, id serial PRIMARY KEY,
name varchar(100) not null, name varchar(100) NOT NULL,
url text, -- These are both optional, a post can just have a title url text, -- These are both optional, a post can just have a title
body text, body text,
creator_id int references user_ on update cascade on delete cascade not null, creator_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
community_id int references community on update cascade on delete cascade not null, community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
removed boolean default false not null, removed boolean DEFAULT FALSE NOT NULL,
locked boolean default false not null, locked boolean DEFAULT FALSE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
updated timestamp updated timestamp
); );
create table post_like ( CREATE TABLE post_like (
id serial primary key, id serial PRIMARY KEY,
post_id int references post on update cascade on delete cascade not null, post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion score smallint NOT NULL, -- -1, or 1 for dislike, like, no row for no opinion
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique(post_id, user_id) UNIQUE (post_id, user_id)
); );
create table post_saved ( CREATE TABLE post_saved (
id serial primary key, id serial PRIMARY KEY,
post_id int references post on update cascade on delete cascade not null, post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique(post_id, user_id) UNIQUE (post_id, user_id)
); );
create table post_read ( CREATE TABLE post_read (
id serial primary key, id serial PRIMARY KEY,
post_id int references post on update cascade on delete cascade not null, post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique(post_id, user_id) UNIQUE (post_id, user_id)
); );

View File

@ -1,3 +1,6 @@
drop table comment_saved; DROP TABLE comment_saved;
drop table comment_like;
drop table comment; DROP TABLE comment_like;
DROP TABLE comment;

View File

@ -1,29 +1,30 @@
create table comment ( CREATE TABLE comment (
id serial primary key, id serial PRIMARY KEY,
creator_id int references user_ on update cascade on delete cascade not null, creator_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
post_id int references post on update cascade on delete cascade not null, post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
parent_id int references comment on update cascade on delete cascade, parent_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
content text not null, content text NOT NULL,
removed boolean default false not null, removed boolean DEFAULT FALSE NOT NULL,
read boolean default false not null, read boolean DEFAULT FALSE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
updated timestamp updated timestamp
); );
create table comment_like ( CREATE TABLE comment_like (
id serial primary key, id serial PRIMARY KEY,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
comment_id int references comment on update cascade on delete cascade not null, comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
post_id int references post on update cascade on delete cascade not null, post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion score smallint NOT NULL, -- -1, or 1 for dislike, like, no row for no opinion
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique(comment_id, user_id) UNIQUE (comment_id, user_id)
); );
create table comment_saved ( CREATE TABLE comment_saved (
id serial primary key, id serial PRIMARY KEY,
comment_id int references comment on update cascade on delete cascade not null, comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
user_id int references user_ on update cascade on delete cascade not null, user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp not null default now(), published timestamp NOT NULL DEFAULT now(),
unique(comment_id, user_id) UNIQUE (comment_id, user_id)
); );

View File

@ -1,2 +1,4 @@
drop view post_view; DROP VIEW post_view;
drop function hot_rank;
DROP FUNCTION hot_rank;

View File

@ -1,51 +1,107 @@
-- Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity -- Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
create or replace function hot_rank( CREATE OR REPLACE FUNCTION hot_rank (score numeric, published timestamp without time zone)
score numeric, RETURNS integer
published timestamp without time zone) AS $$
returns integer as $$ BEGIN
begin -- hours_diff:=EXTRACT(EPOCH FROM (timezone('utc',now()) - published))/3600
-- hours_diff:=EXTRACT(EPOCH FROM (timezone('utc',now()) - published))/3600 RETURN floor(10000 * log(greatest (1, score + 3)) / power(((EXTRACT(EPOCH FROM (timezone('utc', now()) - published)) / 3600) + 2), 1.8))::integer;
return floor(10000*log(greatest(1,score+3)) / power(((EXTRACT(EPOCH FROM (timezone('utc',now()) - published))/3600) + 2), 1.8))::integer; END;
end; $$ $$
LANGUAGE plpgsql; LANGUAGE plpgsql;
create view post_view as CREATE VIEW post_view AS
with all_post as with all_post AS (
( SELECT
select p.*,
p.*, (
(select name from user_ where p.creator_id = user_.id) as creator_name, SELECT
(select name from community where p.community_id = community.id) as community_name, name
(select removed from community c where p.community_id = c.id) as community_removed, FROM
(select count(*) from comment where comment.post_id = p.id) as number_of_comments, user_
coalesce(sum(pl.score), 0) as score, WHERE
count (case when pl.score = 1 then 1 else null end) as upvotes, p.creator_id = user_.id) AS creator_name,
count (case when pl.score = -1 then 1 else null end) as downvotes, (
hot_rank(coalesce(sum(pl.score) , 0), p.published) as hot_rank SELECT
from post p name
left join post_like pl on p.id = pl.post_id FROM
group by p.id community
WHERE
p.community_id = community.id) AS community_name,
(
SELECT
removed
FROM
community c
WHERE
p.community_id = c.id) AS community_removed,
(
SELECT
count(*)
FROM
comment
WHERE
comment.post_id = p.id) AS number_of_comments,
coalesce(sum(pl.score), 0) AS score,
count(
CASE WHEN pl.score = 1 THEN
1
ELSE
NULL
END) AS upvotes,
count(
CASE WHEN pl.score = - 1 THEN
1
ELSE
NULL
END) AS downvotes,
hot_rank (coalesce(sum(pl.score), 0), p.published) AS hot_rank
FROM
post p
LEFT JOIN post_like pl ON p.id = pl.post_id
GROUP BY
p.id
) )
SELECT
ap.*,
u.id AS user_id,
coalesce(pl.score, 0) AS my_vote,
(
SELECT
cf.id::bool
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND cf.community_id = ap.community_id) AS subscribed,
(
SELECT
pr.id::bool
FROM
post_read pr
WHERE
u.id = pr.user_id
AND pr.post_id = ap.id) AS read,
(
SELECT
ps.id::bool
FROM
post_saved ps
WHERE
u.id = ps.user_id
AND ps.post_id = ap.id) AS saved
FROM
user_ u
CROSS JOIN all_post ap
LEFT JOIN post_like pl ON u.id = pl.user_id
AND ap.id = pl.post_id
UNION ALL
SELECT
ap.*,
NULL AS user_id,
NULL AS my_vote,
NULL AS subscribed,
NULL AS read,
NULL AS saved
FROM
all_post ap;
select
ap.*,
u.id as user_id,
coalesce(pl.score, 0) as my_vote,
(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
from user_ u
cross join all_post ap
left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
union all
select
ap.*,
null as user_id,
null as my_vote,
null as subscribed,
null as read,
null as saved
from all_post ap
;

View File

@ -1,5 +1,10 @@
drop view community_view; DROP VIEW community_view;
drop view community_moderator_view;
drop view community_follower_view; DROP VIEW community_moderator_view;
drop view community_user_ban_view;
drop view site_view; DROP VIEW community_follower_view;
DROP VIEW community_user_ban_view;
DROP VIEW site_view;

View File

@ -1,53 +1,154 @@
create view community_view as CREATE VIEW community_view AS
with all_community as with all_community AS (
( SELECT
select *, *,
(select name from user_ u where c.creator_id = u.id) as creator_name, (
(select name from category ct where c.category_id = ct.id) as category_name, SELECT
(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers, name
(select count(*) from post p where p.community_id = c.id) as number_of_posts, FROM
(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments user_ u
from community c WHERE
c.creator_id = u.id) AS creator_name,
(
SELECT
name
FROM
category ct
WHERE
c.category_id = ct.id) AS category_name,
(
SELECT
count(*)
FROM
community_follower cf
WHERE
cf.community_id = c.id) AS number_of_subscribers,
(
SELECT
count(*)
FROM
post p
WHERE
p.community_id = c.id) AS number_of_posts,
(
SELECT
count(*)
FROM
comment co,
post p
WHERE
c.id = p.community_id
AND p.id = co.post_id) AS number_of_comments
FROM
community c
) )
SELECT
ac.*,
u.id AS user_id,
(
SELECT
cf.id::boolean
FROM
community_follower cf
WHERE
u.id = cf.user_id
AND ac.id = cf.community_id) AS subscribed
FROM
user_ u
CROSS JOIN all_community ac
UNION ALL
SELECT
ac.*,
NULL AS user_id,
NULL AS subscribed
FROM
all_community ac;
select CREATE VIEW community_moderator_view AS
ac.*, SELECT
u.id as user_id, *,
(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed (
from user_ u SELECT
cross join all_community ac name
FROM
user_ u
WHERE
cm.user_id = u.id) AS user_name,
(
SELECT
name
FROM
community c
WHERE
cm.community_id = c.id) AS community_name
FROM
community_moderator cm;
union all CREATE VIEW community_follower_view AS
SELECT
*,
(
SELECT
name
FROM
user_ u
WHERE
cf.user_id = u.id) AS user_name,
(
SELECT
name
FROM
community c
WHERE
cf.community_id = c.id) AS community_name
FROM
community_follower cf;
select CREATE VIEW community_user_ban_view AS
ac.*, SELECT
null as user_id, *,
null as subscribed (
from all_community ac SELECT
; name
FROM
user_ u
WHERE
cm.user_id = u.id) AS user_name,
(
SELECT
name
FROM
community c
WHERE
cm.community_id = c.id) AS community_name
FROM
community_user_ban cm;
create view community_moderator_view as CREATE VIEW site_view AS
select *, SELECT
(select name from user_ u where cm.user_id = u.id) as user_name, *,
(select name from community c where cm.community_id = c.id) as community_name (
from community_moderator cm; SELECT
name
FROM
user_ u
WHERE
s.creator_id = u.id) AS creator_name,
(
SELECT
count(*)
FROM
user_) AS number_of_users,
(
SELECT
count(*)
FROM
post) AS number_of_posts,
(
SELECT
count(*)
FROM
comment) AS number_of_comments
FROM
site s;
create view community_follower_view as
select *,
(select name from user_ u where cf.user_id = u.id) as user_name,
(select name from community c where cf.community_id = c.id) as community_name
from community_follower cf;
create view community_user_ban_view as
select *,
(select name from user_ u where cm.user_id = u.id) as user_name,
(select name from community c where cm.community_id = c.id) as community_name
from community_user_ban cm;
create view site_view as
select *,
(select name from user_ u where s.creator_id = u.id) as creator_name,
(select count(*) from user_) as number_of_users,
(select count(*) from post) as number_of_posts,
(select count(*) from comment) as number_of_comments
from site s;

View File

@ -1,2 +1,4 @@
drop view reply_view; DROP VIEW reply_view;
drop view comment_view;
DROP VIEW comment_view;

View File

@ -1,60 +1,114 @@
create view comment_view as CREATE VIEW comment_view AS
with all_comment as with all_comment AS (
( SELECT
select c.*,
c.*, (
(select community_id from post p where p.id = c.post_id), SELECT
(select u.banned from user_ u where c.creator_id = u.id) as banned, community_id
(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community, FROM
(select name from user_ where c.creator_id = user_.id) as creator_name, post p
coalesce(sum(cl.score), 0) as score, WHERE
count (case when cl.score = 1 then 1 else null end) as upvotes, p.id = c.post_id),
count (case when cl.score = -1 then 1 else null end) as downvotes (
from comment c SELECT
left join comment_like cl on c.id = cl.comment_id u.banned
group by c.id FROM
user_ u
WHERE
c.creator_id = u.id) AS banned,
(
SELECT
cb.id::bool
FROM
community_user_ban cb,
post p
WHERE
c.creator_id = cb.user_id
AND p.id = c.post_id
AND p.community_id = cb.community_id) AS banned_from_community,
(
SELECT
name
FROM
user_
WHERE
c.creator_id = user_.id) AS creator_name,
coalesce(sum(cl.score), 0) AS score,
count(
CASE WHEN cl.score = 1 THEN
1
ELSE
NULL
END) AS upvotes,
count(
CASE WHEN cl.score = - 1 THEN
1
ELSE
NULL
END) AS downvotes
FROM
comment c
LEFT JOIN comment_like cl ON c.id = cl.comment_id
GROUP BY
c.id
) )
SELECT
ac.*,
u.id AS user_id,
coalesce(cl.score, 0) AS my_vote,
(
SELECT
cs.id::bool
FROM
comment_saved cs
WHERE
u.id = cs.user_id
AND cs.comment_id = ac.id) AS saved
FROM
user_ u
CROSS JOIN all_comment ac
LEFT JOIN comment_like cl ON u.id = cl.user_id
AND ac.id = cl.comment_id
UNION ALL
SELECT
ac.*,
NULL AS user_id,
NULL AS my_vote,
NULL AS saved
FROM
all_comment ac;
select CREATE VIEW reply_view AS
ac.*, with closereply AS (
u.id as user_id, SELECT
coalesce(cl.score, 0) as my_vote, c2.id,
(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved c2.creator_id AS sender_id,
from user_ u c.creator_id AS recipient_id
cross join all_comment ac FROM
left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id comment c
INNER JOIN comment c2 ON c.id = c2.parent_id
union all WHERE
c2.creator_id != c.creator_id
select -- Do union where post is null
ac.*, UNION
null as user_id, SELECT
null as my_vote, c.id,
null as saved c.creator_id AS sender_id,
from all_comment ac p.creator_id AS recipient_id
; FROM
comment c,
create view reply_view as post p
with closereply as ( WHERE
select c.post_id = p.id
c2.id, AND c.parent_id IS NULL
c2.creator_id as sender_id, AND c.creator_id != p.creator_id
c.creator_id as recipient_id
from comment c
inner join comment c2 on c.id = c2.parent_id
where c2.creator_id != c.creator_id
-- Do union where post is null
union
select
c.id,
c.creator_id as sender_id,
p.creator_id as recipient_id
from comment c, post p
where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
) )
select cv.*, SELECT
closereply.recipient_id cv.*,
from comment_view cv, closereply closereply.recipient_id
where closereply.id = cv.id FROM
; comment_view cv,
closereply
WHERE
closereply.id = cv.id;

View File

@ -1,8 +1,16 @@
drop table mod_remove_post; DROP TABLE mod_remove_post;
drop table mod_lock_post;
drop table mod_remove_comment; DROP TABLE mod_lock_post;
drop table mod_remove_community;
drop table mod_ban; DROP TABLE mod_remove_comment;
drop table mod_ban_from_community;
drop table mod_add; DROP TABLE mod_remove_community;
drop table mod_add_community;
DROP TABLE mod_ban;
DROP TABLE mod_ban_from_community;
DROP TABLE mod_add;
DROP TABLE mod_add_community;

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