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:
- &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
# https://github.com/woodpecker-ci/woodpecker/issues/1716
@ -48,6 +64,7 @@ pipeline:
- "api_tests/node_modules"
secrets:
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
when: *slow_check_paths
toml_fmt:
image: tamasfe/taplo:0.8.1
@ -65,8 +82,18 @@ pipeline:
- rustup toolchain install nightly-2023-07-10
- rustup component add rustfmt --toolchain nightly-2023-07-10
- 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)
check_api_common_default_features:
@ -75,8 +102,7 @@ pipeline:
CARGO_HOME: .cargo
commands:
- cargo check --package lemmy_api_common
# when:
# platform: linux/amd64
when: *slow_check_paths
lemmy_api_common_doesnt_depend_on_diesel:
image: *muslrust_image
@ -84,8 +110,7 @@ pipeline:
CARGO_HOME: .cargo
commands:
- "! cargo tree -p lemmy_api_common --no-default-features -i diesel"
# when:
# platform: linux/amd64
when: *slow_check_paths
lemmy_api_common_works_with_wasm:
image: *muslrust_image
@ -94,6 +119,7 @@ pipeline:
commands:
- "rustup target add wasm32-unknown-unknown"
- "cargo check --target wasm32-unknown-unknown -p lemmy_api_common"
when: *slow_check_paths
check_defaults_hjson_updated:
image: *muslrust_image
@ -103,8 +129,7 @@ pipeline:
- export LEMMY_CONFIG_LOCATION=./config/config.hjson
- ./scripts/update_config_defaults.sh config/defaults_current.hjson
- diff config/defaults.hjson config/defaults_current.hjson
# when:
# platform: linux/amd64
when: *slow_check_paths
check_diesel_schema:
image: willsquire/diesel-cli
@ -115,6 +140,7 @@ pipeline:
- diesel migration run
- diesel print-schema --config-file=diesel.toml > tmp.schema
- diff tmp.schema crates/db_schema/src/schema.rs
when: *slow_check_paths
check_diesel_migration_revertable:
image: willsquire/diesel-cli
@ -124,13 +150,14 @@ pipeline:
commands:
- diesel migration run
- diesel migration redo
when: *slow_check_paths
cargo_clippy:
image: *muslrust_image
environment:
CARGO_HOME: .cargo
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
- cargo clippy --workspace --tests --all-targets --features console --
-D warnings -D deprecated -D clippy::perf -D clippy::complexity
@ -147,8 +174,7 @@ pipeline:
-D clippy::needless_collect
-D clippy::unwrap_used
-D clippy::indexing_slicing
# when:
# platform: linux/amd64
when: *slow_check_paths
cargo_test:
image: *muslrust_image
@ -159,8 +185,7 @@ pipeline:
commands:
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
- cargo test --workspace --no-fail-fast
# when:
# platform: linux/amd64
when: *slow_check_paths
cargo_build:
image: *muslrust_image
@ -169,8 +194,7 @@ pipeline:
commands:
- cargo build
- mv target/x86_64-unknown-linux-musl/debug/lemmy_server target/lemmy_server
# when:
# platform: linux/amd64
when: *slow_check_paths
run_federation_tests:
image: node:alpine
@ -183,8 +207,7 @@ pipeline:
- cd api_tests/
- yarn
- yarn api-test
# when:
# platform: linux/amd64
when: *slow_check_paths
rebuild-cache:
image: meltwater/drone-cache:v1
@ -208,6 +231,7 @@ pipeline:
- "api_tests/node_modules"
secrets:
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
when: *slow_check_paths
publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
@ -257,5 +281,3 @@ services:
environment:
POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password
# when:
# platform: linux/amd64

2
Cargo.lock generated
View File

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

View File

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

View File

@ -9,7 +9,7 @@
"scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
"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": {
"@types/jest": "^29.5.1",

View File

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

View File

@ -7,14 +7,14 @@ pushd ..
cargo build
rm target/lemmy_server || true
cp target/debug/lemmy_server target/lemmy_server
killall -s1 lemmy_server || true
./api_tests/prepare-drone-federation-test.sh
popd
yarn
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
psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE"
done

View File

@ -30,10 +30,12 @@ import {
getCommentParentId,
resolveCommunity,
getPersonDetails,
getReplies,
getUnreadCount,
} from "./shared";
import { CommentView } from "lemmy-js-client/dist/types/CommentView";
let postRes: PostResponse;
let postOnAlphaRes: PostResponse;
beforeAll(async () => {
await setupLogins();
@ -42,7 +44,7 @@ beforeAll(async () => {
await followBeta(gamma);
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
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 () => {
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.community.local).toBe(false);
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 () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// Federate the comment first
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
@ -113,7 +115,7 @@ test("Update a comment", async () => {
test("Delete a comment", async () => {
// 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)
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 () => {
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
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);
// beta will unremove the comment
let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
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(
alpha,
postRes.post_view.post.id,
postOnAlphaRes.post_view.post.id,
);
expect(refetchedPostComments2.comments[0].comment.removed).toBe(false);
assertCommentFederation(
@ -249,7 +252,7 @@ test("Remove a comment from admin and community on different instance", 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.
// 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 () => {
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
let betaComment = (
@ -301,13 +304,14 @@ test("Federated comment like", async () => {
expect(like.comment_view.counts.score).toBe(2);
// 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);
});
test("Reply to a comment", async () => {
// Create a comment on alpha, find it on beta
let commentRes = await createComment(alpha, postRes.post_view.post.id);
test("Reply to a comment from another instance, get notification", async () => {
// Create a root-level trunk-branch comment on alpha
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
// find that comment id on beta
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment;
@ -316,9 +320,7 @@ test("Reply to a comment", async () => {
throw "Missing beta comment";
}
// find that comment id on beta
// Reply from beta
// Reply from beta, extending the branch
let replyRes = await createComment(
beta,
betaComment.post.id,
@ -332,11 +334,13 @@ test("Reply to a comment", async () => {
);
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
// comment, isn't working.
// 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];
expect(alphaComment.comment.content).toBeDefined();
expect(getCommentParentId(alphaComment.comment)).toBe(
@ -346,15 +350,33 @@ test("Reply to a comment", async () => {
expect(alphaComment.creator.local).toBe(false);
expect(alphaComment.counts.score).toBe(1);
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 () => {
// Create a mention on alpha
test("Mention beta from alpha", async () => {
// 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 commentRes = await createComment(alpha, postRes.post_view.post.id);
let mentionRes = await createComment(
alpha,
postRes.post_view.post.id,
postOnAlphaRes.post_view.post.id,
commentRes.comment_view.comment.id,
mentionContent,
);
@ -363,15 +385,44 @@ test("Mention beta", async () => {
expect(mentionRes.comment_view.creator.local).toBe(true);
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);
expect(mentionsRes.mentions[0].comment.content).toBeDefined();
expect(mentionsRes.mentions[0].community.local).toBe(true);
expect(mentionsRes.mentions[0].creator.local).toBe(false);
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 () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id);
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
let betaComment = (
await resolveComment(beta, commentRes.comment_view.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);
// B creates a post, and two comments, should be invisible to A
let postRes = await createPost(beta, 2);
expect(postRes.post_view.post.name).toBeDefined();
let postOnBetaRes = await createPost(beta, 2);
expect(postOnBetaRes.post_view.post.name).toBeDefined();
let parentCommentContent = "An invisible top level comment from beta";
let parentCommentRes = await createComment(
beta,
postRes.post_view.post.id,
postOnBetaRes.post_view.post.id,
undefined,
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 childCommentRes = await createComment(
beta,
postRes.post_view.post.id,
postOnBetaRes.post_view.post.id,
parentCommentRes.comment_view.comment.id,
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);
// 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) {
throw "Missing alpha post B";
}
@ -564,10 +616,11 @@ test("Report a comment", async () => {
if (!betaCommunity) {
throw "Missing beta community";
}
let postRes = (await createPost(beta, betaCommunity.community.id)).post_view
.post;
expect(postRes).toBeDefined();
let commentRes = (await createComment(beta, postRes.id)).comment_view.comment;
let postOnBetaRes = (await createPost(beta, betaCommunity.community.id))
.post_view.post;
expect(postOnBetaRes).toBeDefined();
let commentRes = (await createComment(beta, postOnBetaRes.id)).comment_view
.comment;
expect(commentRes).toBeDefined();
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 { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
import { EditPost } from "lemmy-js-client/dist/types/EditPost";
@ -325,6 +332,24 @@ export async function getComments(
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(
api: API,
comment: Comment,

View File

@ -1,9 +1,10 @@
use crate::Perform;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_comment_response,
comment::{CommentResponse, CreateCommentLike},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, check_downvotes_enabled, local_user_view_from_jwt},
};
use lemmy_db_schema::{
@ -17,70 +18,80 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use std::ops::Deref;
#[async_trait::async_trait(?Send)]
impl Perform for CreateCommentLike {
type Response = CommentResponse;
#[tracing::instrument(skip(context))]
pub async fn like_comment(
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))]
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();
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
check_downvotes_enabled(data.score, &local_site)?;
let comment_id = data.comment_id;
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
let comment_id = data.comment_id;
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
check_community_ban(
local_user_view.person.id,
orig_comment.community.id,
&mut context.pool(),
)
.await?;
check_community_ban(
local_user_view.person.id,
orig_comment.community.id,
&mut context.pool(),
)
.await?;
// 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);
}
// 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 {
comment_id: data.comment_id,
post_id: orig_comment.post.id,
person_id: local_user_view.person.id,
score: data.score,
};
let like_form = CommentLikeForm {
comment_id: data.comment_id,
post_id: orig_comment.post.id,
person_id: local_user_view.person.id,
score: data.score,
};
// Remove any likes first
let person_id = local_user_view.person.id;
// Remove any likes first
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
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add {
CommentLike::like(&mut context.pool(), &like_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntLikeComment)?;
}
// Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add {
CommentLike::like(&mut context.pool(), &like_form)
.await
.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(
context,
context.deref(),
comment_id,
Some(local_user_view),
None,
recipient_ids,
)
.await
}
.await?,
))
}

View File

@ -1,8 +1,10 @@
use crate::{check_report_reason, Perform};
use actix_web::web::Data;
use crate::check_report_reason;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
comment::{CommentReportResponse, CreateCommentReport},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_community_ban,
local_user_view_from_jwt,
@ -21,55 +23,60 @@ use lemmy_db_views::structs::{CommentReportView, CommentView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
/// Creates a comment report and notifies the moderators of the community
#[async_trait::async_trait(?Send)]
impl Perform for CreateCommentReport {
type Response = CommentReportResponse;
#[tracing::instrument(skip(context))]
pub async fn create_comment_report(
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))]
async fn perform(
&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(data.reason.trim());
check_report_reason(&reason, &local_site)?;
let reason = sanitize_html(self.reason.trim());
check_report_reason(&reason, &local_site)?;
let person_id = local_user_view.person.id;
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;
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?;
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 {
creator_id: person_id,
comment_id,
original_comment_text: comment_view.comment.content,
reason,
};
let report = CommentReport::report(&mut context.pool(), &report_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let report = CommentReport::report(&mut context.pool(), &report_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let comment_report_view =
CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
let comment_report_view =
CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
// Email the admins
if local_site.reports_email_admins {
send_new_report_email_to_admins(
&comment_report_view.creator.name,
&comment_report_view.comment_creator.name,
&mut context.pool(),
context.settings(),
)
.await?;
}
Ok(CommentReportResponse {
comment_report_view,
})
// Email the admins
if local_site.reports_email_admins {
send_new_report_email_to_admins(
&comment_report_view.creator.name,
&comment_report_view.comment_creator.name,
&mut context.pool(),
context.settings(),
)
.await?;
}
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
community::{AddModToCommunity, AddModToCommunityResponse},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_mod_or_admin, local_user_view_from_jwt},
};
use lemmy_db_schema::{
@ -15,58 +16,62 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
impl Perform for AddModToCommunity {
type Response = AddModToCommunityResponse;
#[tracing::instrument(skip(context))]
pub async fn add_mod_to_community(
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))]
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;
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?;
let community = Community::read(&mut context.pool(), community_id).await?;
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 })
// Verify that only mods or admins can add mod
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?;
let community = Community::read(&mut context.pool(), community_id).await?;
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?;
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{
is_mod_or_admin,
local_user_view_from_jwt,
@ -28,77 +29,85 @@ use lemmy_utils::{
utils::{time::naive_from_unix, validation::is_valid_body_field},
};
#[async_trait::async_trait(?Send)]
impl Perform for BanFromCommunity {
type Response = BanFromCommunityResponse;
#[tracing::instrument(skip(context))]
pub async fn ban_from_community(
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))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<BanFromCommunityResponse, LemmyError> {
let data: &BanFromCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let banned_person_id = data.person_id;
let remove_data = data.remove_data.unwrap_or(false);
let expires = data.expires.map(naive_from_unix);
let community_id = data.community_id;
let banned_person_id = data.person_id;
let remove_data = data.remove_data.unwrap_or(false);
let expires = data.expires.map(naive_from_unix);
// Verify that only mods or admins can ban
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id,
data.community_id,
)
.await?;
is_valid_body_field(&data.reason, false)?;
// Verify that only mods or admins can ban
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id).await?;
is_valid_body_field(&data.reason, false)?;
let community_user_ban_form = CommunityPersonBanForm {
community_id: data.community_id,
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,
person_id: data.person_id,
expires: Some(expires),
person_id: banned_person_id,
pending: false,
};
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,
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,
})
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(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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
community::{BlockCommunity, BlockCommunityResponse},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{
@ -15,52 +16,56 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
impl Perform for BlockCommunity {
type Response = BlockCommunityResponse;
#[tracing::instrument(skip(context))]
pub async fn block_community(
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))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<BlockCommunityResponse, LemmyError> {
let data: &BlockCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community_id = data.community_id;
let person_id = local_user_view.person.id;
let community_block_form = CommunityBlockForm {
person_id,
community_id,
};
let community_id = data.community_id;
let person_id = local_user_view.person.id;
let community_block_form = CommunityBlockForm {
if data.block {
CommunityBlock::block(&mut context.pool(), &community_block_form)
.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,
community_id,
pending: false,
};
if data.block {
CommunityBlock::block(&mut context.pool(), &community_block_form)
.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,
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,
})
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?;
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
community::{CommunityResponse, FollowCommunity},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt},
};
use lemmy_db_schema::{
@ -15,54 +16,56 @@ use lemmy_db_schema::{
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
impl Perform for FollowCommunity {
type Response = CommunityResponse;
#[tracing::instrument(skip(context))]
pub async fn follow_community(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> {
let data: &FollowCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let community = Community::read(&mut context.pool(), data.community_id).await?;
let mut community_follower_form = CommunityFollowerForm {
community_id: community.id,
person_id: local_user_view.person.id,
pending: false,
};
let community_id = data.community_id;
let community = Community::read(&mut context.pool(), community_id).await?;
let mut community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
person_id: local_user_view.person.id,
pending: false,
};
if data.follow {
if community.local {
check_community_ban(local_user_view.person.id, community.id, &mut context.pool()).await?;
check_community_deleted_or_removed(community.id, &mut context.pool()).await?;
if data.follow {
if community.local {
check_community_ban(local_user_view.person.id, community_id, &mut context.pool()).await?;
check_community_deleted_or_removed(community_id, &mut context.pool()).await?;
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} else {
// Mark as pending, the actual federation activity is sent via `SendActivity` handler
community_follower_form.pending = true;
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
}
}
if !data.follow {
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
} else {
// Mark as pending, the actual federation activity is sent via `SendActivity` handler
community_follower_form.pending = true;
CommunityFollower::follow(&mut context.pool(), &community_follower_form)
.await
.with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?;
}
let 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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_community_response,
community::{CommunityResponse, HideCommunity},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt, sanitize_html_opt},
};
use lemmy_db_schema::{
@ -15,36 +16,38 @@ use lemmy_db_schema::{
};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
impl Perform for HideCommunity {
type Response = CommunityResponse;
#[tracing::instrument(skip(context))]
pub async fn hide_community(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> {
let data: &HideCommunity = self;
let community_form = CommunityUpdateForm::builder()
.hidden(Some(data.hidden))
.build();
// 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)?;
let mod_hide_community_form = ModHideCommunityForm {
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_form = CommunityUpdateForm::builder()
.hidden(Some(data.hidden))
.build();
let community_id = data.community_id;
let community = Community::update(&mut context.pool(), community_id, &community_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?;
let mod_hide_community_form = ModHideCommunityForm {
community_id: data.community_id,
mod_person_id: local_user_view.person.id,
reason: sanitize_html_opt(&data.reason),
hidden: Some(data.hidden),
};
ModHideCommunity::create(&mut context.pool(), &mod_hide_community_form).await?;
let community_id = data.community_id;
Community::update(&mut context.pool(), community_id, &community_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunityHiddenStatus)?;
ActivityChannel::submit_activity(
SendActivityData::UpdateCommunity(local_user_view.person.clone(), community),
&context,
)
.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;
mod ban;
mod block;
mod follow;
mod hide;
mod transfer;
pub mod add_mod;
pub mod ban;
pub mod block;
pub mod follow;
pub mod hide;
pub mod transfer;

View File

@ -1,8 +1,9 @@
use crate::Perform;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
person::{BanPerson, BanPersonResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{is_admin, local_user_view_from_jwt, remove_user_data, sanitize_html_opt},
};
use lemmy_db_schema::{
@ -17,65 +18,68 @@ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
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)]
impl Perform for BanPerson {
type Response = BanPersonResponse;
// Make sure user is an admin
is_admin(&local_user_view)?;
#[tracing::instrument(skip(context))]
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?;
is_valid_body_field(&data.reason, false)?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let expires = data.expires.map(naive_from_unix);
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;
let banned_person_id = data.person_id;
let expires = data.expires.map(naive_from_unix);
let person = Person::update(
// 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(),
banned_person_id,
&PersonUpdateForm::builder()
.banned(Some(ban))
.ban_expires(Some(expires))
.build(),
context.settings(),
context.client(),
)
.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,
})
.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_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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{FeaturePost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_community_ban,
check_community_deleted_or_removed,
@ -22,67 +23,65 @@ use lemmy_db_schema::{
};
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl Perform for FeaturePost {
type Response = PostResponse;
#[tracing::instrument(skip(context))]
pub async fn feature_post(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> {
let data: &FeaturePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
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?;
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,
orig_post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).await?;
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
} 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()
};
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{CreatePostLike, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_community_ban,
check_community_deleted_or_removed,
@ -14,66 +15,76 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
source::{
community::Community,
local_site::LocalSite,
post::{Post, PostLike, PostLikeForm},
},
traits::{Crud, Likeable},
};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use std::ops::Deref;
#[async_trait::async_trait(?Send)]
impl Perform for CreatePostLike {
type Response = PostResponse;
#[tracing::instrument(skip(context))]
pub async fn like_post(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> {
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_downvotes_enabled(data.score, &local_site)?;
// Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, &local_site)?;
// Check for a community ban
let post_id = data.post_id;
let post = Post::read(&mut context.pool(), post_id).await?;
// Check for a community ban
let post_id = data.post_id;
let post = Post::read(&mut context.pool(), post_id).await?;
check_community_ban(
local_user_view.person.id,
post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?;
check_community_ban(
local_user_view.person.id,
post.community_id,
&mut context.pool(),
)
.await?;
check_community_deleted_or_removed(post.community_id, &mut context.pool()).await?;
let like_form = PostLikeForm {
post_id: data.post_id,
person_id: local_user_view.person.id,
score: data.score,
};
let like_form = PostLikeForm {
post_id: data.post_id,
person_id: local_user_view.person.id,
score: data.score,
};
// Remove any likes first
let person_id = local_user_view.person.id;
// Remove any likes first
let person_id = local_user_view.person.id;
PostLike::remove(&mut context.pool(), person_id, post_id).await?;
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);
if do_add {
PostLike::like(&mut context.pool(), &like_form)
.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
// Only add the like if the score isnt 0
let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
if do_add {
PostLike::like(&mut context.pool(), &like_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
}
// 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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{LockPost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_community_ban,
check_community_deleted_or_removed,
@ -20,58 +21,56 @@ use lemmy_db_schema::{
};
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl Perform for LockPost {
type Response = PostResponse;
#[tracing::instrument(skip(context))]
pub async fn lock_post(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> {
let data: &LockPost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
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?;
check_community_deleted_or_removed(orig_post.community_id, &mut context.pool()).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 mods can lock
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
// Verify that only the mods can lock
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
// Update the post
let post_id = data.post_id;
let locked = data.locked;
let post = Post::update(
&mut context.pool(),
post_id,
&PostUpdateForm::builder().locked(Some(locked)).build(),
)
.await?;
// Update the post
let post_id = data.post_id;
let locked = data.locked;
Post::update(
&mut context.pool(),
post_id,
&PostUpdateForm::builder().locked(Some(locked)).build(),
)
.await?;
// Mod tables
let form = ModLockPostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
locked: Some(locked),
};
ModLockPost::create(&mut context.pool(), &form).await?;
// Mod tables
let form = ModLockPostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
locked: Some(locked),
};
ModLockPost::create(&mut context.pool(), &form).await?;
let person_id = local_user_view.person.id;
ActivityChannel::submit_activity(
SendActivityData::LockPost(post, local_user_view.person, data.locked),
&context,
)
.await?;
build_post_response(
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
}
build_post_response(&context, orig_post.community_id, person_id, post_id).await
}

View File

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

View File

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

View File

@ -5,7 +5,7 @@ use crate::{
post::PostResponse,
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::{
newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId},
source::{
@ -39,10 +39,10 @@ pub async fn build_comment_response(
}
pub async fn build_community_response(
context: &Data<LemmyContext>,
context: &LemmyContext,
local_user_view: LocalUserView,
community_id: CommunityId,
) -> Result<CommunityResponse, LemmyError> {
) -> Result<Json<CommunityResponse>, LemmyError> {
let is_mod_or_admin =
is_mod_or_admin(&mut context.pool(), local_user_view.person.id, community_id)
.await
@ -57,10 +57,10 @@ pub async fn build_community_response(
.await?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(CommunityResponse {
Ok(Json(CommunityResponse {
community_view,
discussion_languages,
})
}))
}
pub async fn build_post_response(
@ -68,7 +68,7 @@ pub async fn build_post_response(
community_id: CommunityId,
person_id: PersonId,
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)
.await
.is_ok();
@ -79,7 +79,7 @@ pub async fn build_post_response(
Some(is_mod_or_admin),
)
.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

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 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 once_cell::sync::{Lazy, OnceCell};
use tokio::{
@ -12,6 +27,7 @@ use tokio::{
},
task::JoinHandle,
};
use url::Url;
type MatchOutgoingActivitiesBoxed =
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)]
pub enum SendActivityData {
CreatePost(Post),
UpdatePost(Post),
DeletePost(Post, Person, DeletePost),
RemovePost(Post, Person, RemovePost),
LockPost(Post, Person, bool),
FeaturePost(Post, Person, bool),
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

View File

@ -342,9 +342,8 @@ pub async fn send_password_reset_email(
let token = uuid::Uuid::new_v4().to_string();
// Insert the row
let token2 = token.clone();
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 lang = get_interface_language(user);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
use crate::PerformCrud;
use activitypub_federation::http_signatures::generate_actor_keypair;
use actix_web::web::Data;
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_community_response,
community::{CommunityResponse, CreateCommunity},
@ -42,107 +41,104 @@ use lemmy_utils::{
},
};
#[async_trait::async_trait(?Send)]
impl PerformCrud for CreateCommunity {
type Response = CommunityResponse;
#[tracing::instrument(skip(context))]
pub async fn create_community(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> {
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
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
}

View File

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

View File

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

View File

@ -1,9 +1,10 @@
use crate::PerformCrud;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_community_response,
community::{CommunityResponse, EditCommunity},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html_opt},
};
use lemmy_db_schema::{
@ -22,65 +23,68 @@ use lemmy_utils::{
utils::{slurs::check_slurs_opt, validation::is_valid_body_field},
};
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditCommunity {
type Response = CommunityResponse;
#[tracing::instrument(skip(context))]
pub async fn update_community(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CommunityResponse, LemmyError> {
let data: &EditCommunity = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs_opt(&data.title, &slur_regex)?;
check_slurs_opt(&data.description, &slur_regex)?;
is_valid_body_field(&data.description, false)?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs_opt(&data.title, &slur_regex)?;
check_slurs_opt(&data.description, &slur_regex)?;
is_valid_body_field(&data.description, false)?;
let title = sanitize_html_opt(&data.title);
let description = sanitize_html_opt(&data.description);
let title = sanitize_html_opt(&data.title);
let description = sanitize_html_opt(&data.description);
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let description = diesel_option_overwrite(description);
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let description = diesel_option_overwrite(description);
// 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)
// 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
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
build_community_response(context, local_user_view, 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;
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
@ -13,41 +13,38 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl PerformCrud for CreateCustomEmoji {
type Response = CustomEmojiResponse;
#[tracing::instrument(skip(context))]
pub async fn create_custom_emoji(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> {
let data: &CreateCustomEmoji = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let shortcode = sanitize_html(data.shortcode.to_lowercase().trim());
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let shortcode = sanitize_html(data.shortcode.to_lowercase().trim());
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let emoji_form = CustomEmojiInsertForm::builder()
.local_site_id(local_site.id)
.shortcode(shortcode)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
let emoji_form = CustomEmojiInsertForm::builder()
.local_site_id(local_site.id)
.shortcode(shortcode)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
.build();
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).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();
let emoji = CustomEmoji::create(&mut context.pool(), &emoji_form).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 })
keywords.push(keyword_form);
}
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
custom_emoji::{DeleteCustomEmoji, DeleteCustomEmojiResponse},
@ -8,24 +8,18 @@ use lemmy_api_common::{
use lemmy_db_schema::source::custom_emoji::CustomEmoji;
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl PerformCrud for DeleteCustomEmoji {
type Response = DeleteCustomEmojiResponse;
#[tracing::instrument(skip(context))]
pub async fn delete_custom_emoji(
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))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<DeleteCustomEmojiResponse, LemmyError> {
let data: &DeleteCustomEmoji = self;
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,
})
}
// Make sure user is an admin
is_admin(&local_user_view)?;
CustomEmoji::delete(&mut context.pool(), data.id).await?;
Ok(Json(DeleteCustomEmojiResponse {
id: data.id,
success: true,
}))
}

View File

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

View File

@ -1,5 +1,5 @@
use crate::PerformCrud;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
@ -13,40 +13,37 @@ use lemmy_db_schema::source::{
use lemmy_db_views::structs::CustomEmojiView;
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditCustomEmoji {
type Response = CustomEmojiResponse;
#[tracing::instrument(skip(context))]
pub async fn update_custom_emoji(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<CustomEmojiResponse, LemmyError> {
let data: &EditCustomEmoji = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin
is_admin(&local_user_view)?;
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let alt_text = sanitize_html(&data.alt_text);
let category = sanitize_html(&data.category);
let emoji_form = CustomEmojiUpdateForm::builder()
.local_site_id(local_site.id)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
let emoji_form = CustomEmojiUpdateForm::builder()
.local_site_id(local_site.id)
.alt_text(alt_text)
.category(category)
.image_url(data.clone().image_url.into())
.build();
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
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();
let emoji = CustomEmoji::update(&mut context.pool(), data.id, &emoji_form).await?;
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 })
keywords.push(keyword_form);
}
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 community;
pub mod custom_emoji;
@ -9,10 +5,3 @@ pub mod post;
pub mod private_message;
pub mod site;
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{DeletePost, PostResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, check_community_deleted_or_removed, local_user_view_from_jwt},
};
use lemmy_db_schema::{
@ -12,52 +13,50 @@ use lemmy_db_schema::{
};
use lemmy_utils::error::{LemmyError, LemmyErrorType};
#[async_trait::async_trait(?Send)]
impl PerformCrud for DeletePost {
type Response = PostResponse;
#[tracing::instrument(skip(context))]
pub async fn delete_post(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> {
let data: &DeletePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
// 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
// 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 = 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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{PostResponse, RemovePost},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_ban, is_mod_or_admin, local_user_view_from_jwt},
};
use lemmy_db_schema::{
@ -15,58 +16,56 @@ use lemmy_db_schema::{
};
use lemmy_utils::error::LemmyError;
#[async_trait::async_trait(?Send)]
impl PerformCrud for RemovePost {
type Response = PostResponse;
#[tracing::instrument(skip(context))]
pub async fn remove_post(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<PostResponse, LemmyError> {
let data: &RemovePost = self;
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
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?;
check_community_ban(
local_user_view.person.id,
orig_post.community_id,
&mut context.pool(),
)
.await?;
// Verify that only the mods can remove
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
// Verify that only the mods can remove
is_mod_or_admin(
&mut context.pool(),
local_user_view.person.id,
orig_post.community_id,
)
.await?;
// Update the post
let post_id = data.post_id;
let removed = data.removed;
let post = Post::update(
&mut context.pool(),
post_id,
&PostUpdateForm::builder().removed(Some(removed)).build(),
)
.await?;
// Update the post
let post_id = data.post_id;
let removed = data.removed;
Post::update(
&mut context.pool(),
post_id,
&PostUpdateForm::builder().removed(Some(removed)).build(),
)
.await?;
// Mod tables
let form = ModRemovePostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
removed: Some(removed),
reason: data.reason.clone(),
};
ModRemovePost::create(&mut context.pool(), &form).await?;
// Mod tables
let form = ModRemovePostForm {
mod_person_id: local_user_view.person.id,
post_id: data.post_id,
removed: Some(removed),
reason: data.reason.clone(),
};
ModRemovePost::create(&mut context.pool(), &form).await?;
let person_id = local_user_view.person.id;
ActivityChannel::submit_activity(
SendActivityData::RemovePost(post, local_user_view.person, data.0),
&context,
)
.await?;
build_post_response(
context,
orig_post.community_id,
local_user_view.person.id,
post_id,
)
.await
}
build_post_response(&context, orig_post.community_id, person_id, post_id).await
}

View File

@ -1,10 +1,11 @@
use crate::PerformCrud;
use actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
build_response::build_post_response,
context::LemmyContext,
post::{EditPost, PostResponse},
request::fetch_site_data,
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_community_ban,
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},
},
};
use std::ops::Deref;
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditPost {
type Response = PostResponse;
#[tracing::instrument(skip(context))]
pub async fn update_post(
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))]
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();
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.
// Issue link: https://github.com/LemmyNet/lemmy/issues/2287
let url = Some(data_url.map(clean_url_params).map(Into::into));
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs_opt(&data.name, &slur_regex)?;
check_slurs_opt(&data.body, &slur_regex)?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs_opt(&data.name, &slur_regex)?;
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
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 = 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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
private_message::{CreatePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{
check_person_block,
generate_local_apub_endpoint,
@ -27,78 +28,77 @@ use lemmy_utils::{
utils::{slurs::remove_slurs, validation::is_valid_body_field},
};
#[async_trait::async_trait(?Send)]
impl PerformCrud for CreatePrivateMessage {
type Response = PrivateMessageResponse;
#[tracing::instrument(skip(context))]
pub async fn create_private_message(
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))]
async fn perform(
&self,
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);
let content = remove_slurs(&content, &local_site_to_slur_regex(&local_site));
is_valid_body_field(&Some(content.clone()), false)?;
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)?;
check_person_block(
local_user_view.person.id,
data.recipient_id,
&mut context.pool(),
)
.await?;
check_person_block(
local_user_view.person.id,
data.recipient_id,
&mut context.pool(),
)
.await?;
let private_message_form = PrivateMessageInsertForm::builder()
.content(content.clone())
.creator_id(local_user_view.person.id)
.recipient_id(data.recipient_id)
.build();
let private_message_form = PrivateMessageInsertForm::builder()
.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(),
)
let inserted_private_message = PrivateMessage::create(&mut context.pool(), &private_message_form)
.await
.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
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;
}
let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?;
Ok(PrivateMessageResponse {
private_message_view: view,
})
// Send email to the local recipient, if one exists
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
private_message::{DeletePrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt,
};
use lemmy_db_schema::{
@ -12,42 +13,41 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
#[async_trait::async_trait(?Send)]
impl PerformCrud for DeletePrivateMessage {
type Response = PrivateMessageResponse;
#[tracing::instrument(skip(context))]
pub async fn delete_private_message(
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))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<PrivateMessageResponse, LemmyError> {
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,
})
// 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;
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
private_message::{EditPrivateMessage, PrivateMessageResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{local_site_to_slur_regex, local_user_view_from_jwt, sanitize_html},
};
use lemmy_db_schema::{
@ -19,48 +20,47 @@ use lemmy_utils::{
utils::{slurs::remove_slurs, validation::is_valid_body_field},
};
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditPrivateMessage {
type Response = PrivateMessageResponse;
#[tracing::instrument(skip(context))]
pub async fn update_private_message(
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))]
async fn perform(
&self,
context: &Data<LemmyContext>,
) -> Result<PrivateMessageResponse, LemmyError> {
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,
})
// 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?;
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::http_signatures::generate_actor_keypair;
use actix_web::web::Data;
use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair};
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
person::{LoginResponse, Register},
@ -38,177 +37,173 @@ use lemmy_utils::{
},
};
#[async_trait::async_trait(?Send)]
impl PerformCrud for Register {
type Response = LoginResponse;
#[tracing::instrument(skip(context))]
pub async fn register(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<LoginResponse, LemmyError> {
let data: &Register = self;
if local_site.registration_mode == RegistrationMode::Closed {
return Err(LemmyErrorType::RegistrationClosed)?;
}
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;
password_length_check(&data.password)?;
honeypot_check(&data.honeypot)?;
if local_site.registration_mode == RegistrationMode::Closed {
return Err(LemmyErrorType::RegistrationClosed)?;
}
if local_site.require_email_verification && data.email.is_none() {
return Err(LemmyErrorType::EmailRequired)?;
}
password_length_check(&data.password)?;
honeypot_check(&data.honeypot)?;
if local_site.site_setup && require_registration_application && data.answer.is_none() {
return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?;
}
if local_site.require_email_verification && data.email.is_none() {
return Err(LemmyErrorType::EmailRequired)?;
}
// Make sure passwords match
if data.password != data.password_verify {
return Err(LemmyErrorType::PasswordsDoNotMatch)?;
}
if local_site.site_setup && require_registration_application && data.answer.is_none() {
return Err(LemmyErrorType::RegistrationApplicationAnswerRequired)?;
}
// Make sure passwords match
if data.password != data.password_verify {
return Err(LemmyErrorType::PasswordsDoNotMatch)?;
}
if local_site.site_setup && local_site.captcha_enabled {
if let Some(captcha_uuid) = &data.captcha_uuid {
let uuid = uuid::Uuid::parse_str(captcha_uuid)?;
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 {
if local_site.site_setup && local_site.captcha_enabled {
if let Some(captcha_uuid) = &data.captcha_uuid {
let uuid = uuid::Uuid::parse_str(captcha_uuid)?;
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)?;
}
}
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.username, &slur_regex)?;
check_slurs_opt(&data.answer, &slur_regex)?;
let username = sanitize_html(&data.username);
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.username, &slur_regex)?;
check_slurs_opt(&data.answer, &slur_regex)?;
let username = sanitize_html(&data.username);
let actor_keypair = generate_actor_keypair()?;
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
let actor_id = generate_local_apub_endpoint(
EndpointType::Person,
&data.username,
&context.settings().get_protocol_and_hostname(),
)?;
let actor_keypair = generate_actor_keypair()?;
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
let actor_id = generate_local_apub_endpoint(
EndpointType::Person,
&data.username,
&context.settings().get_protocol_and_hostname(),
)?;
if let Some(email) = &data.email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?;
}
if let Some(email) = &data.email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
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
let person_form = PersonInsertForm::builder()
.name(username)
.actor_id(Some(actor_id.clone()))
.private_key(Some(actor_keypair.private_key))
.public_key(actor_keypair.public_key)
.inbox_url(Some(generate_inbox_url(&actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?))
// If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup))
.instance_id(site_view.site.instance_id)
.build();
// Register the new person
let person_form = PersonInsertForm::builder()
.name(username)
.actor_id(Some(actor_id.clone()))
.private_key(Some(actor_keypair.private_key))
.public_key(actor_keypair.public_key)
.inbox_url(Some(generate_inbox_url(&actor_id)?))
.shared_inbox_url(Some(generate_shared_inbox_url(&actor_id)?))
// If its the initial site setup, they are an admin
.admin(Some(!local_site.site_setup))
.instance_id(site_view.site.instance_id)
.build();
// insert the person
let inserted_person = Person::create(&mut context.pool(), &person_form)
.await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
// insert the person
let inserted_person = Person::create(&mut context.pool(), &person_form)
.await
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
// 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.
let accepted_application = Some(!require_registration_application);
// 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.
let accepted_application = Some(!require_registration_application);
// Create the local user
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.email(data.email.as_deref().map(str::to_lowercase))
.password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type))
.build();
// Create the local user
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.email(data.email.as_deref().map(str::to_lowercase))
.password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw))
.accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type))
.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 {
// Create the registration application
let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id,
// We already made sure answer was not null above
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,
if local_site.site_setup && require_registration_application {
// Create the registration application
let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id,
// We already made sure answer was not null above
answer: data.answer.clone().expect("must have an answer"),
};
// 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");
RegistrationApplication::create(&mut context.pool(), &form).await?;
}
send_verification_email(
&local_user_view,
&email,
&mut context.pool(),
context.settings(),
)
.await?;
login_response.verify_email_sent = true;
}
// Email the admins
if local_site.application_email_admins {
send_new_applicant_email_to_admins(&data.username, &mut context.pool(), context.settings())
.await?;
}
if require_registration_application {
login_response.registration_created = true;
}
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
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 actix_web::web::Data;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use bcrypt::verify;
use lemmy_api_common::{
context::LemmyContext,
person::{DeleteAccount, DeleteAccountResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::local_user_view_from_jwt,
};
use lemmy_utils::error::{LemmyError, LemmyErrorType};
#[async_trait::async_trait(?Send)]
impl PerformCrud for DeleteAccount {
type Response = DeleteAccountResponse;
#[tracing::instrument(skip(context))]
pub async fn delete_account(
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))]
async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError> {
let data = self;
let local_user_view = local_user_view_from_jwt(data.auth.as_ref(), context).await?;
// Verify the password
let valid: bool = verify(
&data.password,
&local_user_view.local_user.password_encrypted,
)
.unwrap_or(false);
if !valid {
return Err(LemmyErrorType::IncorrectLogin)?;
}
Ok(DeleteAccountResponse {})
// Verify the password
let valid: bool = verify(
&data.password,
&local_user_view.local_user.password_encrypted,
)
.unwrap_or(false);
if !valid {
return Err(LemmyErrorType::IncorrectLogin)?;
}
ActivityChannel::submit_activity(
SendActivityData::DeleteUser(local_user_view.person),
&context,
)
.await?;
Ok(Json(DeleteAccountResponse {}))
}

View File

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

View File

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

View File

@ -1,10 +1,9 @@
use crate::{
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
objects::{community::ApubCommunity, instance::ApubSite},
protocol::{
activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
objects::{group::Group, instance::Instance},
},
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -12,19 +11,18 @@ use activitypub_federation::{
traits::{Actor, Object},
};
use chrono::NaiveDateTime;
use lemmy_api_common::{
community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext,
person::{BanPerson, BanPersonResponse},
utils::local_user_view_from_jwt,
};
use lemmy_api_common::{community::BanFromCommunity, context::LemmyContext, person::BanPerson};
use lemmy_db_schema::{
newtypes::CommunityId,
source::{community::Community, person::Person, site::Site},
traits::Crud,
utils::DbPool,
};
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 url::Url;
@ -132,87 +130,74 @@ async fn generate_cc(
})
}
#[async_trait::async_trait]
impl SendActivity for BanPerson {
type Response = BanPersonResponse;
pub(crate) async fn send_ban_from_site(
mod_: Person,
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(
request: &Self,
_response: &Self::Response,
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 {
// if the action affects a local user, federate to other instances
if banned_user.local {
if data.ban {
BlockUser::send(
&SiteOrCommunity::Community(community),
&banned_person,
&local_user_view.person.clone().into(),
request.remove_data.unwrap_or(false),
request.reason.clone(),
&site,
&banned_user.into(),
&mod_.into(),
data.remove_data.unwrap_or(false),
data.reason.clone(),
expires,
context,
&context,
)
.await
} else {
UndoBlockUser::send(
&SiteOrCommunity::Community(community),
&banned_person,
&local_user_view.person.clone().into(),
request.reason.clone(),
context,
&site,
&banned_user.into(),
&mod_.into(),
data.reason.clone(),
&context,
)
.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},
InCommunity,
},
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -22,13 +21,12 @@ use activitypub_federation::{
traits::{ActivityHandler, Actor},
};
use lemmy_api_common::{
community::{AddModToCommunity, AddModToCommunityResponse},
context::LemmyContext,
post::{FeaturePost, PostResponse},
utils::{generate_featured_url, generate_moderators_url, local_user_view_from_jwt},
utils::{generate_featured_url, generate_moderators_url},
};
use lemmy_db_schema::{
impls::community::CollectionType,
newtypes::{CommunityId, PersonId},
source::{
activity::ActivitySendTargets,
community::{Community, CommunityModerator, CommunityModeratorForm},
@ -174,61 +172,41 @@ impl ActivityHandler for CollectionAdd {
}
}
#[async_trait::async_trait]
impl SendActivity for AddModToCommunity {
type Response = AddModToCommunityResponse;
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 updated_mod: ApubPerson = Person::read(&mut context.pool(), request.person_id)
.await?
.into();
if request.added {
CollectionAdd::send_add_mod(
&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
}
pub(crate) async fn send_add_mod_to_community(
actor: Person,
community_id: CommunityId,
updated_mod_id: PersonId,
added: bool,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let actor: ApubPerson = actor.into();
let community: ApubCommunity = Community::read(&mut context.pool(), community_id)
.await?
.into();
let updated_mod: ApubPerson = Person::read(&mut context.pool(), updated_mod_id)
.await?
.into();
if added {
CollectionAdd::send_add_mod(&community, &updated_mod, &actor, &context).await
} else {
CollectionRemove::send_remove_mod(&community, &updated_mod, &actor, &context).await
}
}
#[async_trait::async_trait]
impl SendActivity for FeaturePost {
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?
.into();
let post = response.post_view.post.clone().into();
let person = local_user_view.person.into();
if request.featured {
CollectionAdd::send_add_featured_post(&community, &post, &person, context).await
} else {
CollectionRemove::send_remove_featured_post(&community, &post, &person, context).await
}
pub(crate) async fn send_feature_post(
post: Post,
actor: Person,
featured: bool,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let actor: ApubPerson = actor.into();
let post: ApubPost = post.into();
let community = Community::read(&mut context.pool(), post.community_id)
.await?
.into();
if featured {
CollectionAdd::send_add_featured_post(&community, &post, &actor, &context).await
} else {
CollectionRemove::send_remove_featured_post(&community, &post, &actor, &context).await
}
}

View File

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

View File

@ -4,7 +4,6 @@ use crate::{
objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::report::Report, InCommunity},
PostOrComment,
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -12,16 +11,13 @@ use activitypub_federation::{
kinds::activity::FlagType,
traits::{ActivityHandler, Actor},
};
use lemmy_api_common::{
comment::{CommentReportResponse, CreateCommentReport},
context::LemmyContext,
post::{CreatePostReport, PostReportResponse},
utils::{local_user_view_from_jwt, sanitize_html},
};
use lemmy_api_common::{context::LemmyContext, utils::sanitize_html};
use lemmy_db_schema::{
source::{
activity::ActivitySendTargets,
comment_report::{CommentReport, CommentReportForm},
community::Community,
person::Person,
post_report::{PostReport, PostReportForm},
},
traits::Reportable,
@ -29,58 +25,17 @@ use lemmy_db_schema::{
use lemmy_utils::error::LemmyError;
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 {
#[tracing::instrument(skip_all)]
async fn send(
pub(crate) async fn send(
object_id: ObjectId<PostOrComment>,
actor: &ApubPerson,
community_id: ObjectId<ApubCommunity>,
actor: Person,
community: Community,
reason: String,
context: &Data<LemmyContext>,
context: Data<LemmyContext>,
) -> 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 id = generate_activity_id(
kind.clone(),
@ -97,7 +52,7 @@ impl Report {
};
// todo: this should probably filter and only send if the community is remote?
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,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::community::update::UpdateCommunity, InCommunity},
SendActivity,
};
use activitypub_federation::{
config::Data,
kinds::{activity::UpdateType, public},
traits::{ActivityHandler, Actor, Object},
};
use lemmy_api_common::{
community::{CommunityResponse, EditCommunity, HideCommunity},
context::LemmyContext,
utils::local_user_view_from_jwt,
};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
source::{activity::ActivitySendTargets, community::Community},
source::{activity::ActivitySendTargets, community::Community, person::Person},
traits::Crud,
};
use lemmy_utils::error::LemmyError;
use url::Url;
#[async_trait::async_trait]
impl SendActivity for EditCommunity {
type Response = CommunityResponse;
pub(crate) async fn send_update_community(
community: Community,
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(
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
}
}
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
}
let activity = AnnouncableActivities::UpdateCommunity(update);
send_activity_in_community(
activity,
&actor,
&community,
ActivitySendTargets::empty(),
true,
&context,
)
.await
}
#[async_trait::async_trait]
@ -112,18 +91,3 @@ impl ActivityHandler for UpdateCommunity {
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},
InCommunity,
},
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -25,7 +24,6 @@ use activitypub_federation::{
};
use lemmy_api_common::{
build_response::send_local_notifs,
comment::{CommentResponse, EditComment},
context::LemmyContext,
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 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 {
#[tracing::instrument(skip(comment, person_id, kind, context))]
pub(crate) async fn send(

View File

@ -14,7 +14,6 @@ use crate::{
activities::{create_or_update::page::CreateOrUpdatePage, CreateOrUpdateType},
InCommunity,
},
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -22,10 +21,7 @@ use activitypub_federation::{
protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object},
};
use lemmy_api_common::{
context::LemmyContext,
post::{EditPost, PostResponse},
};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
aggregates::structs::PostAggregates,
newtypes::PersonId,
@ -40,25 +36,6 @@ use lemmy_db_schema::{
use lemmy_utils::error::{LemmyError, LemmyErrorType};
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 {
pub(crate) async fn new(
post: ApubPost,

View File

@ -6,7 +6,6 @@ use crate::{
create_or_update::chat_message::CreateOrUpdateChatMessage,
CreateOrUpdateType,
},
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -22,77 +21,33 @@ use lemmy_db_schema::{
source::{activity::ActivitySendTargets, person::Person, private_message::PrivateMessage},
traits::Crud,
};
use lemmy_db_views::structs::PrivateMessageView;
use lemmy_utils::error::LemmyError;
use url::Url;
#[async_trait::async_trait]
impl SendActivity for CreatePrivateMessage {
type Response = PrivateMessageResponse;
pub(crate) async fn send_create_or_update_pm(
pm_view: PrivateMessageView,
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(
_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::Create,
context,
)
.await
}
}
#[async_trait::async_trait]
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
}
let id = generate_activity_id(
kind.clone(),
&context.settings().get_protocol_and_hostname(),
)?;
let create_or_update = CreateOrUpdateChatMessage {
id: id.clone(),
actor: actor.id().into(),
to: [recipient.id().into()],
object: ApubPrivateMessage(pm_view.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, &actor, inbox, true).await
}
#[async_trait::async_trait]

View File

@ -3,7 +3,6 @@ use crate::{
insert_received_activity,
objects::person::ApubPerson,
protocol::activities::deletion::delete_user::DeleteUser,
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -17,45 +16,38 @@ use lemmy_api_common::{
utils::{delete_user_account, local_user_view_from_jwt},
};
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 url::Url;
#[async_trait::async_trait]
impl SendActivity for DeleteAccount {
type Response = DeleteAccountResponse;
pub async fn delete_user(person: Person, context: Data<LemmyContext>) -> Result<(), LemmyError> {
let actor: ApubPerson = person.into();
delete_user_account(
actor.id,
&mut context.pool(),
context.settings(),
context.client(),
)
.await?;
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 actor: ApubPerson = local_user_view.person.into();
delete_user_account(
actor.id,
&mut context.pool(),
context.settings(),
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 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();
inboxes.set_all_instances(true);
send_lemmy_activity(context, delete, &actor, inboxes, true).await?;
send_lemmy_activity(&context, delete, &actor, inboxes, true).await?;
Ok(())
}
}

View File

@ -19,7 +19,6 @@ use crate::{
activities::deletion::{delete::Delete, undo_delete::UndoDelete},
InCommunity,
},
SendActivity,
};
use activitypub_federation::{
config::Data,
@ -28,15 +27,9 @@ use activitypub_federation::{
protocol::verification::verify_domains_match,
traits::{Actor, Object},
};
use lemmy_api_common::{
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_api_common::context::LemmyContext;
use lemmy_db_schema::{
newtypes::CommunityId,
source::{
activity::ActivitySendTargets,
comment::{Comment, CommentUpdateForm},
@ -55,170 +48,10 @@ pub mod delete;
pub mod delete_user;
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
/// action was done by a normal user.
#[tracing::instrument(skip_all)]
async fn send_apub_delete_in_community(
pub(crate) async fn send_apub_delete_in_community(
actor: Person,
community: Community,
object: DeletableObjects,
@ -246,12 +79,44 @@ async fn send_apub_delete_in_community(
.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)]
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,
pm: PrivateMessage,
deleted: bool,
context: &Data<LemmyContext>,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let recipient_id = pm.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 inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox());
if deleted {
let delete: Delete = Delete::new(actor, deletable, recipient.id(), None, None, context)?;
send_lemmy_activity(context, delete, actor, inbox, true).await?;
let delete: Delete = Delete::new(actor, deletable, recipient.id(), None, None, &context)?;
send_lemmy_activity(&context, delete, actor, inbox, true).await?;
} else {
let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, context)?;
send_lemmy_activity(context, undo, actor, inbox, true).await?;
let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, &context)?;
send_lemmy_activity(&context, undo, actor, inbox, true).await?;
};
Ok(())
}

View File

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

View File

@ -1,6 +1,25 @@
use self::following::send_follow_community;
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},
protocol::activities::{
community::report::Report,
create_or_update::{note::CreateOrUpdateNote, page::CreateOrUpdatePage},
CreateOrUpdateType,
},
@ -208,15 +227,102 @@ pub async fn match_outgoing_activities(
) -> LemmyResult<()> {
let context = context.reset_request_count();
let fed_task = async {
use SendActivityData::*;
match data {
SendActivityData::CreatePost(post) => {
CreatePost(post) => {
let creator_id = post.creator_id;
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;
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 {

View File

@ -2,23 +2,16 @@ use crate::{
activities::community::send_activity_in_community,
activity_lists::AnnouncableActivities,
fetcher::post_or_comment::PostOrComment,
objects::{comment::ApubComment, person::ApubPerson, post::ApubPost},
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::voting::{
undo_vote::UndoVote,
vote::{Vote, VoteType},
},
SendActivity,
};
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
use lemmy_api_common::{
comment::{CommentResponse, CreateCommentLike},
context::LemmyContext,
post::{CreatePostLike, PostResponse},
sensitive::Sensitive,
utils::local_user_view_from_jwt,
};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
newtypes::CommunityId,
newtypes::DbUrl,
source::{
activity::ActivitySendTargets,
comment::{CommentLike, CommentLikeForm},
@ -26,84 +19,36 @@ use lemmy_db_schema::{
person::Person,
post::{PostLike, PostLikeForm},
},
traits::{Crud, Likeable},
traits::Likeable,
};
use lemmy_utils::error::LemmyError;
pub mod undo_vote;
pub mod vote;
#[async_trait::async_trait]
impl SendActivity for CreatePostLike {
type Response = PostResponse;
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,
pub(crate) async fn send_like_activity(
object_id: DbUrl,
actor: Person,
community: Community,
score: i16,
jwt: &Sensitive<String>,
context: &Data<LemmyContext>,
context: Data<LemmyContext>,
) -> Result<(), LemmyError> {
let community = Community::read(&mut context.pool(), community_id)
.await?
.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 object_id: ObjectId<PostOrComment> = object_id.try_into()?;
let actor: ApubPerson = actor.into();
let community: ApubCommunity = community.into();
let empty = ActivitySendTargets::empty();
// score of 1 means upvote, -1 downvote, 0 undo a previous vote
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);
send_activity_in_community(activity, &actor, &community, empty, false, context).await
send_activity_in_community(activity, &actor, &community, empty, false, &context).await
} else {
// 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 undo_vote = UndoVote::new(vote, &actor, &community, context)?;
let vote = Vote::new(object_id, &actor, &community, VoteType::Like, &context)?;
let undo_vote = UndoVote::new(vote, &actor, &community, &context)?;
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",
"lemmy_utils",
"activitypub_federation",
"sha2",
"regex",
"once_cell",
"serde_json",
@ -60,7 +59,6 @@ diesel-async = { workspace = true, features = [
"postgres",
"deadpool",
], optional = true }
sha2 = { workspace = true, optional = true }
regex = { workspace = true, optional = true }
once_cell = { workspace = true, optional = true }
diesel_ltree = { workspace = true, optional = true }

View File

@ -156,15 +156,6 @@ impl Crud for Comment {
type InsertForm = CommentInsertForm;
type UpdateForm = CommentUpdateForm;
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]]
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 UpdateForm = CommentReplyUpdateForm;
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(
pool: &mut DbPool<'_>,

View File

@ -27,20 +27,6 @@ impl Crud for Community {
type InsertForm = CommunityInsertForm;
type UpdateForm = CommunityUpdateForm;
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> {
let is_new_community = match &form.actor_id {

View File

@ -69,16 +69,7 @@ impl Crud for LocalUser {
type InsertForm = LocalUserInsertForm;
type UpdateForm = LocalUserUpdateForm;
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> {
let conn = &mut get_conn(pool).await?;
let mut form_with_encrypted_password = form.clone();

View File

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

View File

@ -1,11 +1,6 @@
use crate::{
newtypes::LocalUserId,
schema::password_reset_request::dsl::{
local_user_id,
password_reset_request,
published,
token_encrypted,
},
schema::password_reset_request::dsl::{local_user_id, password_reset_request, published, token},
source::password_reset_request::{PasswordResetRequest, PasswordResetRequestForm},
traits::Crud,
utils::{get_conn, DbPool},
@ -17,20 +12,13 @@ use diesel::{
QueryDsl,
};
use diesel_async::RunQueryDsl;
use sha2::{Digest, Sha256};
#[async_trait]
impl Crud for PasswordResetRequest {
type InsertForm = PasswordResetRequestForm;
type UpdateForm = PasswordResetRequestForm;
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> {
let conn = &mut get_conn(pool).await?;
insert_into(password_reset_request)
@ -55,29 +43,22 @@ impl PasswordResetRequest {
pub async fn create_token(
pool: &mut DbPool<'_>,
from_local_user_id: LocalUserId,
token: &str,
token_: String,
) -> 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 {
local_user_id: from_local_user_id,
token_encrypted: token_hash,
token: token_,
};
Self::create(pool, &form).await
}
pub async fn read_from_token(
pool: &mut DbPool<'_>,
token: &str,
token_: &str,
) -> Result<PasswordResetRequest, Error> {
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
.filter(token_encrypted.eq(token_hash))
.filter(token.eq(token_))
.filter(published.gt(now - 1.days()))
.first::<Self>(conn)
.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)]
mod tests {
#![allow(clippy::unwrap_used)]
@ -148,17 +121,16 @@ mod tests {
let inserted_local_user = LocalUser::create(pool, &new_local_user).await.unwrap();
let token = "nope";
let token_encrypted_ = "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce";
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
.unwrap();
let expected_password_reset_request = PasswordResetRequest {
id: inserted_password_reset_request.id,
local_user_id: inserted_local_user.id,
token_encrypted: token_encrypted_.to_string(),
token: token.to_string(),
published: inserted_password_reset_request.published,
};

View File

@ -27,12 +27,7 @@ impl Crud for Person {
.first::<Self>(conn)
.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> {
let conn = &mut get_conn(pool).await?;
insert_into(person::table)

View File

@ -13,13 +13,6 @@ impl Crud for PersonMention {
type InsertForm = PersonMentionInsertForm;
type UpdateForm = PersonMentionUpdateForm;
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(
pool: &mut DbPool<'_>,

View File

@ -38,15 +38,6 @@ impl Crud for Post {
type InsertForm = PostInsertForm;
type UpdateForm = PostUpdateForm;
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> {
let conn = &mut get_conn(pool).await?;

View File

@ -15,16 +15,6 @@ impl Crud for PrivateMessage {
type InsertForm = PrivateMessageInsertForm;
type UpdateForm = PrivateMessageUpdateForm;
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> {
let conn = &mut get_conn(pool).await?;
@ -48,12 +38,6 @@ impl Crud for PrivateMessage {
.get_result::<Self>(conn)
.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 {

View File

@ -26,11 +26,6 @@ impl Crud for RegistrationApplication {
.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(
pool: &mut DbPool<'_>,
id_: Self::IdType,
@ -42,13 +37,6 @@ impl Crud for RegistrationApplication {
.get_result::<Self>(conn)
.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 {

View File

@ -58,11 +58,6 @@ impl Crud for Site {
.get_result::<Self>(conn)
.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 {

View File

@ -548,7 +548,7 @@ diesel::table! {
diesel::table! {
password_reset_request (id) {
id -> Int4,
token_encrypted -> Text,
token -> Text,
published -> Timestamp,
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))]
pub struct PasswordResetRequest {
pub id: i32,
pub token_encrypted: String,
pub token: String,
pub published: chrono::NaiveDateTime,
pub local_user_id: LocalUserId,
}
@ -16,5 +16,5 @@ pub struct PasswordResetRequest {
#[cfg_attr(feature = "full", diesel(table_name = password_reset_request))]
pub struct PasswordResetRequestForm {
pub local_user_id: LocalUserId,
pub token_encrypted: String,
pub token: String,
}

View File

@ -1,34 +1,64 @@
use crate::{
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]
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 UpdateForm;
type IdType;
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error>
where
Self: Sized;
async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result<Self, Error>
where
Self: Sized;
type IdType: Send;
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error>;
async fn read(pool: &mut DbPool<'_>, id: Self::IdType) -> Result<Self, Error> {
let query: Find<Self> = Self::table().find(id);
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.
async fn update(
pool: &mut DbPool<'_>,
id: Self::IdType,
form: &Self::UpdateForm,
) -> Result<Self, Error>
where
Self: Sized;
async fn delete(_pool: &mut DbPool<'_>, _id: Self::IdType) -> Result<usize, Error>
where
Self: Sized,
Self::IdType: Send,
{
async { Err(Error::NotFound) }.await
) -> Result<Self, Error>;
async fn delete(pool: &mut DbPool<'_>, id: Self::IdType) -> Result<usize, Error> {
let query: Delete<Find<Self>> = diesel::delete(Self::table().find(id));
let conn = &mut *get_conn(pool).await?;
query.execute(conn).await
}
}

View File

@ -308,7 +308,10 @@ fn queries<'a>() -> Queries<
.map(|l| l.local_user.show_read_posts)
.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() {

View File

@ -48,7 +48,9 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
} else {
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 {
version: Some("2.0".to_string()),
software: Some(NodeInfoSoftware {

View File

@ -1,6 +1,7 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- 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
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- 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
-- in the modified columns)
@ -16,21 +12,25 @@
--
-- 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
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
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
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
IF (NEW IS DISTINCT FROM OLD AND NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at) THEN
NEW.updated_at := CURRENT_TIMESTAMP;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
$$
LANGUAGE plpgsql;

View File

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

View File

@ -1,23 +1,25 @@
create table user_ (
id serial primary key,
name varchar(20) not null,
fedi_name varchar(40) not null,
preferred_username varchar(20),
password_encrypted text not null,
email text unique,
icon bytea,
admin boolean default false not null,
banned boolean default false not null,
published timestamp not null default now(),
updated timestamp,
unique(name, fedi_name)
CREATE TABLE user_ (
id serial PRIMARY KEY,
name varchar(20) NOT NULL,
fedi_name varchar(40) NOT NULL,
preferred_username varchar(20),
password_encrypted text NOT NULL,
email text UNIQUE,
icon bytea,
admin boolean DEFAULT FALSE NOT NULL,
banned boolean DEFAULT FALSE NOT NULL,
published timestamp NOT NULL DEFAULT now(),
updated timestamp,
UNIQUE (name, fedi_name)
);
create table user_ban (
id serial primary key,
user_id int references user_ on update cascade on delete cascade not null,
published timestamp not null default now(),
unique (user_id)
CREATE TABLE user_ban (
id serial PRIMARY KEY,
user_id int REFERENCES user_ ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
published timestamp NOT NULL DEFAULT now(),
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 community_user_ban;;
drop table community_moderator;
drop table community_follower;
drop table community;
drop table category;
DROP TABLE site;
DROP TABLE community_user_ban;
;
DROP TABLE community_moderator;
DROP TABLE community_follower;
DROP TABLE community;
DROP TABLE category;

View File

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

View File

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

View File

@ -1,37 +1,38 @@
create table post (
id serial primary key,
name varchar(100) not null,
url text, -- These are both optional, a post can just have a title
body text,
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,
removed boolean default false not null,
locked boolean default false not null,
published timestamp not null default now(),
updated timestamp
CREATE TABLE post (
id serial PRIMARY KEY,
name varchar(100) NOT NULL,
url text, -- These are both optional, a post can just have a title
body text,
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,
removed boolean DEFAULT FALSE NOT NULL,
locked boolean DEFAULT FALSE NOT NULL,
published timestamp NOT NULL DEFAULT now(),
updated timestamp
);
create table post_like (
id serial primary key,
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,
score smallint not null, -- -1, or 1 for dislike, like, no row for no opinion
published timestamp not null default now(),
unique(post_id, user_id)
CREATE TABLE post_like (
id serial PRIMARY KEY,
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,
score smallint NOT NULL, -- -1, or 1 for dislike, like, no row for no opinion
published timestamp NOT NULL DEFAULT now(),
UNIQUE (post_id, user_id)
);
create table post_saved (
id serial primary key,
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,
published timestamp not null default now(),
unique(post_id, user_id)
CREATE TABLE post_saved (
id serial PRIMARY KEY,
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,
published timestamp NOT NULL DEFAULT now(),
UNIQUE (post_id, user_id)
);
create table post_read (
id serial primary key,
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,
published timestamp not null default now(),
unique(post_id, user_id)
CREATE TABLE post_read (
id serial PRIMARY KEY,
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,
published timestamp NOT NULL DEFAULT now(),
UNIQUE (post_id, user_id)
);

View File

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

View File

@ -1,29 +1,30 @@
create table comment (
id serial primary key,
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,
parent_id int references comment on update cascade on delete cascade,
content text not null,
removed boolean default false not null,
read boolean default false not null,
published timestamp not null default now(),
updated timestamp
CREATE TABLE comment (
id serial PRIMARY KEY,
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,
parent_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
content text NOT NULL,
removed boolean DEFAULT FALSE NOT NULL,
read boolean DEFAULT FALSE NOT NULL,
published timestamp NOT NULL DEFAULT now(),
updated timestamp
);
create table comment_like (
id serial primary key,
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,
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
published timestamp not null default now(),
unique(comment_id, user_id)
CREATE TABLE comment_like (
id serial PRIMARY KEY,
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,
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
published timestamp NOT NULL DEFAULT now(),
UNIQUE (comment_id, user_id)
);
create table comment_saved (
id serial primary key,
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,
published timestamp not null default now(),
unique(comment_id, user_id)
CREATE TABLE comment_saved (
id serial PRIMARY KEY,
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,
published timestamp NOT NULL DEFAULT now(),
UNIQUE (comment_id, user_id)
);

View File

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

View File

@ -1,51 +1,107 @@
-- Rank = ScaleFactor * sign(Score) * log(1 + abs(Score)) / (Time + 2)^Gravity
create or replace function hot_rank(
score numeric,
published timestamp without time zone)
returns integer as $$
begin
-- 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;
end; $$
CREATE OR REPLACE FUNCTION hot_rank (score numeric, published timestamp without time zone)
RETURNS integer
AS $$
BEGIN
-- 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;
END;
$$
LANGUAGE plpgsql;
create view post_view as
with all_post as
(
select
p.*,
(select name from user_ where p.creator_id = user_.id) as creator_name,
(select name from 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
CREATE VIEW post_view AS
with all_post AS (
SELECT
p.*,
(
SELECT
name
FROM
user_
WHERE
p.creator_id = user_.id) AS creator_name,
(
SELECT
name
FROM
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_moderator_view;
drop view community_follower_view;
drop view community_user_ban_view;
drop view site_view;
DROP VIEW community_view;
DROP VIEW community_moderator_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
with all_community as
(
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 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
CREATE VIEW community_view AS
with all_community AS (
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
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
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
CREATE VIEW community_moderator_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_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
ac.*,
null as user_id,
null as subscribed
from all_community ac
;
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 community_moderator_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_moderator 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;
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 comment_view;
DROP VIEW reply_view;
DROP VIEW comment_view;

View File

@ -1,60 +1,114 @@
create view comment_view as
with all_comment as
(
select
c.*,
(select community_id from post p where p.id = c.post_id),
(select u.banned 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
CREATE VIEW comment_view AS
with all_comment AS (
SELECT
c.*,
(
SELECT
community_id
FROM
post p
WHERE
p.id = c.post_id),
(
SELECT
u.banned
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
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
;
create view reply_view as
with closereply as (
select
c2.id,
c2.creator_id as sender_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
CREATE VIEW reply_view AS
with closereply AS (
SELECT
c2.id,
c2.creator_id AS sender_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.*,
closereply.recipient_id
from comment_view cv, closereply
where closereply.id = cv.id
;
SELECT
cv.*,
closereply.recipient_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_lock_post;
drop table mod_remove_comment;
drop table mod_remove_community;
drop table mod_ban;
drop table mod_ban_from_community;
drop table mod_add;
drop table mod_add_community;
DROP TABLE mod_remove_post;
DROP TABLE mod_lock_post;
DROP TABLE mod_remove_comment;
DROP TABLE mod_remove_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