Moving settings to Database. (#2492)

* Moving settings to Database.

- Moves many settings into the database. Fixes #2285
- Adds a local_site and instance table. Fixes #2365 . Fixes #2368
- Separates SQL update an insert forms, to avoid runtime errors.
- Adds TypedBuilder to all the SQL forms, instead of default.

* Fix weird clippy issue.

* Removing extra lines.

* Some fixes from suggestions.

* Fixing apub tests.

* Using instance creation helper function.

* Move forms to their own line.

* Trying to fix local_site_data, still broken.

* Fixing federation tests.

* Trying to fix check features 1.

* Addressing PR comments.

* Adding check_apub to all verify functions.
pull/2517/head
Dessalines 2 years ago committed by GitHub
parent 276a8c2bd3
commit 235cc8b228
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

4
Cargo.lock generated

@ -2005,6 +2005,7 @@ dependencies = [
"lemmy_db_views_moderator",
"lemmy_utils",
"percent-encoding",
"regex",
"reqwest",
"reqwest-middleware",
"rosetta-i18n",
@ -2096,6 +2097,7 @@ dependencies = [
"sha2",
"strum",
"strum_macros",
"typed-builder",
"url",
]
@ -2110,6 +2112,7 @@ dependencies = [
"serial_test",
"tracing",
"typed-builder",
"url",
]
[[package]]
@ -2227,6 +2230,7 @@ dependencies = [
"strum_macros",
"tracing",
"tracing-error",
"typed-builder",
"url",
"uuid 1.1.2",
]

@ -4,11 +4,11 @@
"browser": true
},
"plugins": [
"jane"
"@typescript-eslint"
],
"extends": [
"plugin:jane/recommended",
"plugin:jane/typescript"
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {

@ -1,4 +1,4 @@
module.exports = Object.assign(require('eslint-plugin-jane/prettier-ts'), {
arrowParens: 'avoid',
module.exports = Object.assign(require("eslint-plugin-prettier"), {
arrowParens: "avoid",
semi: true,
});

@ -14,15 +14,17 @@
"devDependencies": {
"@sniptt/monads": "^0.5.10",
"@types/jest": "^26.0.23",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"class-transformer": "^0.5.1",
"eslint": "^8.20.0",
"eslint-plugin-jane": "^11.2.2",
"eslint": "^8.25.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.0.6",
"lemmy-js-client": "0.17.0-rc.37",
"lemmy-js-client": "0.17.0-rc.47",
"node-fetch": "^2.6.1",
"prettier": "^2.7.1",
"reflect-metadata": "^0.1.13",
"ts-jest": "^27.0.3",
"typescript": "^4.6.4"
"typescript": "^4.8.4"
}
}

@ -1,7 +1,7 @@
jest.setTimeout(180000);
import {None, Some} from '@sniptt/monads';
import { CommentView } from 'lemmy-js-client';
import { PostResponse } from 'lemmy-js-client';
import { None, Some } from "@sniptt/monads";
import { CommentView } from "lemmy-js-client";
import { PostResponse } from "lemmy-js-client";
import {
alpha,
@ -31,7 +31,7 @@ import {
getComments,
getCommentParentId,
resolveCommunity,
} from './shared';
} from "./shared";
let postRes: PostResponse;
@ -41,10 +41,7 @@ beforeAll(async () => {
await followBeta(alpha);
await followBeta(gamma);
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
postRes = await createPost(
alpha,
betaCommunity.unwrap().community.id
);
postRes = await createPost(alpha, betaCommunity.unwrap().community.id);
});
afterAll(async () => {
@ -65,7 +62,7 @@ function assertCommentFederation(
expect(commentOne.comment.removed).toBe(commentOne.comment.removed);
}
test('Create a comment', async () => {
test("Create a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
expect(commentRes.comment_view.comment.content).toBeDefined();
expect(commentRes.comment_view.community.local).toBe(false);
@ -73,7 +70,9 @@ test('Create a comment', async () => {
expect(commentRes.comment_view.counts.score).toBe(1);
// Make sure that comment is liked on beta
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
expect(betaComment).toBeDefined();
expect(betaComment.community.local).toBe(true);
expect(betaComment.creator.local).toBe(false);
@ -81,15 +80,17 @@ test('Create a comment', async () => {
assertCommentFederation(betaComment, commentRes.comment_view);
});
test('Create a comment in a non-existent post', async () => {
let commentRes = await createComment(alpha, -1, None) as any;
expect(commentRes.error).toBe('couldnt_find_post');
test("Create a comment in a non-existent post", async () => {
let commentRes = (await createComment(alpha, -1, None)) as any;
expect(commentRes.error).toBe("couldnt_find_post");
});
test('Update a comment', async () => {
test("Update a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
// Federate the comment first
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment;
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment;
assertCommentFederation(betaComment.unwrap(), commentRes.comment_view);
let updateCommentRes = await editComment(
@ -97,23 +98,19 @@ test('Update a comment', async () => {
commentRes.comment_view.comment.id
);
expect(updateCommentRes.comment_view.comment.content).toBe(
'A jest test federated comment update'
"A jest test federated comment update"
);
expect(updateCommentRes.comment_view.community.local).toBe(false);
expect(updateCommentRes.comment_view.creator.local).toBe(true);
// Make sure that post is updated on beta
let betaCommentUpdated = (await resolveComment(
beta,
commentRes.comment_view.comment
)).comment.unwrap();
assertCommentFederation(
betaCommentUpdated,
updateCommentRes.comment_view
);
let betaCommentUpdated = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
assertCommentFederation(betaCommentUpdated, updateCommentRes.comment_view);
});
test('Delete a comment', async () => {
test("Delete a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
let deleteCommentRes = await deleteComment(
@ -125,8 +122,11 @@ test('Delete a comment', async () => {
expect(deleteCommentRes.comment_view.comment.content).toBe("");
// Make sure that comment is undefined on beta
let betaCommentRes = await resolveComment(beta, commentRes.comment_view.comment) as any;
expect(betaCommentRes.error).toBe('couldnt_find_object');
let betaCommentRes = (await resolveComment(
beta,
commentRes.comment_view.comment
)) as any;
expect(betaCommentRes.error).toBe("couldnt_find_object");
let undeleteCommentRes = await deleteComment(
alpha,
@ -136,15 +136,14 @@ test('Delete a comment', async () => {
expect(undeleteCommentRes.comment_view.comment.deleted).toBe(false);
// Make sure that comment is undeleted on beta
let betaComment2 = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
let betaComment2 = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
expect(betaComment2.comment.deleted).toBe(false);
assertCommentFederation(
betaComment2,
undeleteCommentRes.comment_view
);
assertCommentFederation(betaComment2, undeleteCommentRes.comment_view);
});
test('Remove a comment from admin and community on the same instance', async () => {
test("Remove a comment from admin and community on the same instance", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
// Get the id for beta
@ -158,14 +157,20 @@ test('Remove a comment from admin and community on the same instance', async ()
expect(removeCommentRes.comment_view.comment.content).toBe("");
// Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
let refetchedPostComments = await getComments(alpha, postRes.post_view.post.id);
let refetchedPostComments = await getComments(
alpha,
postRes.post_view.post.id
);
expect(refetchedPostComments.comments[0].comment.removed).toBe(true);
let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
// Make sure that comment is unremoved on beta
let refetchedPostComments2 = await getComments(alpha, postRes.post_view.post.id);
let refetchedPostComments2 = await getComments(
alpha,
postRes.post_view.post.id
);
expect(refetchedPostComments2.comments[0].comment.removed).toBe(false);
assertCommentFederation(
refetchedPostComments2.comments[0],
@ -173,7 +178,7 @@ test('Remove a comment from admin and community on the same instance', async ()
);
});
test('Remove a comment from admin and community on different instance', async () => {
test("Remove a comment from admin and community on different instance", async () => {
let alpha_user = await registerUser(alpha);
let newAlphaApi: API = {
client: alpha.client,
@ -186,11 +191,17 @@ test('Remove a comment from admin and community on different instance', async ()
newAlphaApi,
newCommunity.community_view.community.id
);
let commentRes = await createComment(newAlphaApi, newPost.post_view.post.id, None);
let commentRes = await createComment(
newAlphaApi,
newPost.post_view.post.id,
None
);
expect(commentRes.comment_view.comment.content).toBeDefined();
// Beta searches that to cache it, then removes it
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
let removeCommentRes = await removeComment(
beta,
true,
@ -199,29 +210,39 @@ test('Remove a comment from admin and community on different instance', async ()
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
// Make sure its not removed on alpha
let refetchedPostComments = await getComments(alpha, newPost.post_view.post.id);
let refetchedPostComments = await getComments(
alpha,
newPost.post_view.post.id
);
expect(refetchedPostComments.comments[0].comment.removed).toBe(false);
assertCommentFederation(refetchedPostComments.comments[0], commentRes.comment_view);
assertCommentFederation(
refetchedPostComments.comments[0],
commentRes.comment_view
);
});
test('Unlike a comment', async () => {
test("Unlike a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
expect(unlike.comment_view.counts.score).toBe(0);
// Make sure that post is unliked on beta
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
expect(betaComment).toBeDefined();
expect(betaComment.community.local).toBe(true);
expect(betaComment.creator.local).toBe(false);
expect(betaComment.counts.score).toBe(0);
});
test('Federated comment like', async () => {
test("Federated comment like", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
// Find the comment on beta
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
let like = await likeComment(beta, 1, betaComment.comment);
expect(like.comment_view.counts.score).toBe(2);
@ -231,10 +252,12 @@ test('Federated comment like', async () => {
expect(postComments.comments[0].counts.score).toBe(2);
});
test('Reply to a comment', async () => {
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, None);
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
// find that comment id on beta
@ -247,7 +270,9 @@ test('Reply to a comment', async () => {
expect(replyRes.comment_view.comment.content).toBeDefined();
expect(replyRes.comment_view.community.local).toBe(true);
expect(replyRes.comment_view.creator.local).toBe(true);
expect(getCommentParentId(replyRes.comment_view.comment).unwrap()).toBe(betaComment.comment.id);
expect(getCommentParentId(replyRes.comment_view.comment).unwrap()).toBe(
betaComment.comment.id
);
expect(replyRes.comment_view.counts.score).toBe(1);
// Make sure that comment is seen on alpha
@ -257,16 +282,18 @@ test('Reply to a comment', async () => {
let postComments = await getComments(alpha, postRes.post_view.post.id);
let alphaComment = postComments.comments[0];
expect(alphaComment.comment.content).toBeDefined();
expect(getCommentParentId(alphaComment.comment).unwrap()).toBe(postComments.comments[1].comment.id);
expect(getCommentParentId(alphaComment.comment).unwrap()).toBe(
postComments.comments[1].comment.id
);
expect(alphaComment.community.local).toBe(false);
expect(alphaComment.creator.local).toBe(false);
expect(alphaComment.counts.score).toBe(1);
assertCommentFederation(alphaComment, replyRes.comment_view);
});
test('Mention beta', async () => {
test("Mention beta", async () => {
// Create a mention on alpha
let mentionContent = 'A test mention of @lemmy_beta@lemmy-beta:8551';
let mentionContent = "A test mention of @lemmy_beta@lemmy-beta:8551";
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
let mentionRes = await createComment(
alpha,
@ -286,23 +313,29 @@ test('Mention beta', async () => {
expect(mentionsRes.mentions[0].counts.score).toBe(1);
});
test('Comment Search', async () => {
test("Comment Search", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id, None);
let betaComment = (await resolveComment(beta, commentRes.comment_view.comment)).comment.unwrap();
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment.unwrap();
assertCommentFederation(betaComment, commentRes.comment_view);
});
test('A and G subscribe to B (center) A posts, G mentions B, it gets announced to A', async () => {
test("A and G subscribe to B (center) A posts, G mentions B, it gets announced to A", async () => {
// Create a local post
let alphaCommunity = (await resolveCommunity(alpha, "!main@lemmy-alpha:8541")).community.unwrap();
let alphaCommunity = (
await resolveCommunity(alpha, "!main@lemmy-alpha:8541")
).community.unwrap();
let alphaPost = await createPost(alpha, alphaCommunity.community.id);
expect(alphaPost.post_view.community.local).toBe(true);
// Make sure gamma sees it
let gammaPost = (await resolvePost(gamma, alphaPost.post_view.post)).post.unwrap();
let gammaPost = (
await resolvePost(gamma, alphaPost.post_view.post)
).post.unwrap();
let commentContent =
'A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551';
"A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551";
let commentRes = await createComment(
gamma,
gammaPost.post.id,
@ -315,12 +348,18 @@ test('A and G subscribe to B (center) A posts, G mentions B, it gets announced t
expect(commentRes.comment_view.counts.score).toBe(1);
// Make sure alpha sees it
let alphaPostComments2 = await getComments(alpha, alphaPost.post_view.post.id);
let alphaPostComments2 = await getComments(
alpha,
alphaPost.post_view.post.id
);
expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
expect(alphaPostComments2.comments[0].community.local).toBe(true);
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
assertCommentFederation(alphaPostComments2.comments[0], commentRes.comment_view);
assertCommentFederation(
alphaPostComments2.comments[0],
commentRes.comment_view
);
// Make sure beta has mentions
let mentionsRes = await getMentions(beta);
@ -331,28 +370,32 @@ test('A and G subscribe to B (center) A posts, G mentions B, it gets announced t
// expect(mentionsRes.mentions[0].score).toBe(1);
});
test('Check that activity from another instance is sent to third instance', async () => {
test("Check that activity from another instance is sent to third instance", async () => {
// Alpha and gamma users follow beta community
let alphaFollow = await followBeta(alpha);
expect(alphaFollow.community_view.community.local).toBe(false);
expect(alphaFollow.community_view.community.name).toBe('main');
expect(alphaFollow.community_view.community.name).toBe("main");
let gammaFollow = await followBeta(gamma);
expect(gammaFollow.community_view.community.local).toBe(false);
expect(gammaFollow.community_view.community.name).toBe('main');
expect(gammaFollow.community_view.community.name).toBe("main");
// Create a post on beta
let betaPost = await createPost(beta, 2);
expect(betaPost.post_view.community.local).toBe(true);
// Make sure gamma and alpha see it
let gammaPost = (await resolvePost(gamma, betaPost.post_view.post)).post.unwrap();
let gammaPost = (
await resolvePost(gamma, betaPost.post_view.post)
).post.unwrap();
expect(gammaPost.post).toBeDefined();
let alphaPost = (await resolvePost(alpha, betaPost.post_view.post)).post.unwrap();
let alphaPost = (
await resolvePost(alpha, betaPost.post_view.post)
).post.unwrap();
expect(alphaPost.post).toBeDefined();
// The bug: gamma comments, and alpha should see it.
let commentContent = 'Comment from gamma';
let commentContent = "Comment from gamma";
let commentRes = await createComment(
gamma,
gammaPost.post.id,
@ -370,13 +413,16 @@ test('Check that activity from another instance is sent to third instance', asyn
expect(alphaPostComments2.comments[0].community.local).toBe(false);
expect(alphaPostComments2.comments[0].creator.local).toBe(false);
expect(alphaPostComments2.comments[0].counts.score).toBe(1);
assertCommentFederation(alphaPostComments2.comments[0], commentRes.comment_view);
assertCommentFederation(
alphaPostComments2.comments[0],
commentRes.comment_view
);
await unfollowRemotes(alpha);
await unfollowRemotes(gamma);
});
test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.', async () => {
test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.", async () => {
// Unfollow all remote communities
let site = await unfollowRemotes(alpha);
expect(
@ -387,7 +433,7 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
let postRes = await createPost(beta, 2);
expect(postRes.post_view.post.name).toBeDefined();
let parentCommentContent = 'An invisible top level comment from beta';
let parentCommentContent = "An invisible top level comment from beta";
let parentCommentRes = await createComment(
beta,
postRes.post_view.post.id,
@ -399,7 +445,7 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
);
// B creates a comment, then a child one of that.
let childCommentContent = 'An invisible child comment from beta';
let childCommentContent = "An invisible child comment from beta";
let childCommentRes = await createComment(
beta,
postRes.post_view.post.id,
@ -413,50 +459,62 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde
// Follow beta again
let follow = await followBeta(alpha);
expect(follow.community_view.community.local).toBe(false);
expect(follow.community_view.community.name).toBe('main');
expect(follow.community_view.community.name).toBe("main");
// An update to the child comment on beta, should push the post, parent, and child to alpha now
let updatedCommentContent = 'An update child comment from beta';
let updatedCommentContent = Some("An update child comment from beta");
let updateRes = await editComment(
beta,
childCommentRes.comment_view.comment.id,
updatedCommentContent
);
expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent);
expect(updateRes.comment_view.comment.content).toBe(
updatedCommentContent.unwrap()
);
// Get the post from alpha
let alphaPostB = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap();
let alphaPostB = (
await resolvePost(alpha, postRes.post_view.post)
).post.unwrap();
let alphaPost = await getPost(alpha, alphaPostB.post.id);
let alphaPostComments = await getComments(alpha, alphaPostB.post.id);
expect(alphaPost.post_view.post.name).toBeDefined();
assertCommentFederation(alphaPostComments.comments[1], parentCommentRes.comment_view);
assertCommentFederation(alphaPostComments.comments[0], updateRes.comment_view);
assertCommentFederation(
alphaPostComments.comments[1],
parentCommentRes.comment_view
);
assertCommentFederation(
alphaPostComments.comments[0],
updateRes.comment_view
);
expect(alphaPost.post_view.community.local).toBe(false);
expect(alphaPost.post_view.creator.local).toBe(false);
await unfollowRemotes(alpha);
});
test('Report a comment', async () => {
test("Report a comment", async () => {
let betaCommunity = (await resolveBetaCommunity(beta)).community.unwrap();
let postRes = (await createPost(beta, betaCommunity.community.id)).post_view.post;
let postRes = (await createPost(beta, betaCommunity.community.id)).post_view
.post;
expect(postRes).toBeDefined();
let commentRes = (await createComment(beta, postRes.id, None)).comment_view.comment;
let commentRes = (await createComment(beta, postRes.id, None)).comment_view
.comment;
expect(commentRes).toBeDefined();
let alphaComment = (await resolveComment(alpha, commentRes)).comment.unwrap().comment;
let alphaReport = (await reportComment(alpha, alphaComment.id, randomString(10)))
.comment_report_view.comment_report;
let alphaComment = (await resolveComment(alpha, commentRes)).comment.unwrap()
.comment;
let alphaReport = (
await reportComment(alpha, alphaComment.id, randomString(10))
).comment_report_view.comment_report;
let betaReport = (await listCommentReports(beta)).comment_reports[0].comment_report;
let betaReport = (await listCommentReports(beta)).comment_reports[0]
.comment_report;
expect(betaReport).toBeDefined();
expect(betaReport.resolved).toBe(false);
expect(betaReport.original_comment_text).toBe(alphaReport.original_comment_text);
expect(betaReport.original_comment_text).toBe(
alphaReport.original_comment_text
);
expect(betaReport.reason).toBe(alphaReport.reason);
});
function N(gamma: API, id: number, N: any, commentContent: string) {
throw new Error('Function not implemented.');
}

@ -1,5 +1,5 @@
jest.setTimeout(120000);
import { CommunityView } from 'lemmy-js-client';
import { CommunityView } from "lemmy-js-client";
import {
alpha,
@ -18,7 +18,7 @@ import {
createPost,
getPost,
resolvePost,
} from './shared';
} from "./shared";
beforeAll(async () => {
await setupLogins();
@ -34,8 +34,12 @@ function assertCommunityFederation(
expect(communityOne.community.description.unwrapOr("none")).toBe(
communityTwo.community.description.unwrapOr("none")
);
expect(communityOne.community.icon.unwrapOr("none")).toBe(communityTwo.community.icon.unwrapOr("none"));
expect(communityOne.community.banner.unwrapOr("none")).toBe(communityTwo.community.banner.unwrapOr("none"));
expect(communityOne.community.icon.unwrapOr("none")).toBe(
communityTwo.community.icon.unwrapOr("none")
);
expect(communityOne.community.banner.unwrapOr("none")).toBe(
communityTwo.community.banner.unwrapOr("none")
);
expect(communityOne.community.published).toBe(
communityTwo.community.published
);
@ -44,35 +48,35 @@ function assertCommunityFederation(
expect(communityOne.community.deleted).toBe(communityTwo.community.deleted);
}
test('Create community', async () => {
test("Create community", async () => {
let communityRes = await createCommunity(alpha);
expect(communityRes.community_view.community.name).toBeDefined();
// A dupe check
let prevName = communityRes.community_view.community.name;
let communityRes2: any = await createCommunity(alpha, prevName);
expect(communityRes2['error']).toBe('community_already_exists');
expect(communityRes2["error"]).toBe("community_already_exists");
// Cache the community on beta, make sure it has the other fields
let searchShort = `!${prevName}@lemmy-alpha:8541`;
let betaCommunity = (await resolveCommunity(beta, searchShort)).community.unwrap();
let betaCommunity = (
await resolveCommunity(beta, searchShort)
).community.unwrap();
assertCommunityFederation(betaCommunity, communityRes.community_view);
});
test('Delete community', async () => {
test("Delete community", async () => {
let communityRes = await createCommunity(beta);
// Cache the community on Alpha
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community.unwrap();
let alphaCommunity = (
await resolveCommunity(alpha, searchShort)
).community.unwrap();
assertCommunityFederation(alphaCommunity, communityRes.community_view);
// Follow the community from alpha
let follow = await followCommunity(
alpha,
true,
alphaCommunity.community.id
);
let follow = await followCommunity(alpha, true, alphaCommunity.community.id);
// Make sure the follow response went through
expect(follow.community_view.community.local).toBe(false);
@ -83,7 +87,9 @@ test('Delete community', async () => {
communityRes.community_view.community.id
);
expect(deleteCommunityRes.community_view.community.deleted).toBe(true);
expect(deleteCommunityRes.community_view.community.title).toBe(communityRes.community_view.community.title);
expect(deleteCommunityRes.community_view.community.title).toBe(
communityRes.community_view.community.title
);
// Make sure it got deleted on A
let communityOnAlphaDeleted = await getCommunity(
@ -110,20 +116,18 @@ test('Delete community', async () => {
);
});
test('Remove community', async () => {
test("Remove community", async () => {
let communityRes = await createCommunity(beta);
// Cache the community on Alpha
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community.unwrap();
let alphaCommunity = (
await resolveCommunity(alpha, searchShort)
).community.unwrap();
assertCommunityFederation(alphaCommunity, communityRes.community_view);
// Follow the community from alpha
let follow = await followCommunity(
alpha,
true,
alphaCommunity.community.id
);
let follow = await followCommunity(alpha, true, alphaCommunity.community.id);
// Make sure the follow response went through
expect(follow.community_view.community.local).toBe(false);
@ -134,7 +138,9 @@ test('Remove community', async () => {
communityRes.community_view.community.id
);
expect(removeCommunityRes.community_view.community.removed).toBe(true);
expect(removeCommunityRes.community_view.community.title).toBe(communityRes.community_view.community.title);
expect(removeCommunityRes.community_view.community.title).toBe(
communityRes.community_view.community.title
);
// Make sure it got Removed on A
let communityOnAlphaRemoved = await getCommunity(
@ -161,34 +167,53 @@ test('Remove community', async () => {
);
});
test('Search for beta community', async () => {
test("Search for beta community", async () => {
let communityRes = await createCommunity(beta);
expect(communityRes.community_view.community.name).toBeDefined();
let searchShort = `!${communityRes.community_view.community.name}@lemmy-beta:8551`;
let alphaCommunity = (await resolveCommunity(alpha, searchShort)).community.unwrap();
let alphaCommunity = (
await resolveCommunity(alpha, searchShort)
).community.unwrap();
assertCommunityFederation(alphaCommunity, communityRes.community_view);
});
test('Admin actions in remote community are not federated to origin', async () => {
test("Admin actions in remote community are not federated to origin", async () => {
// create a community on alpha
let communityRes = (await createCommunity(alpha)).community_view;
expect(communityRes.community.name).toBeDefined();
// gamma follows community and posts in it
let gammaCommunity = (await resolveCommunity(gamma, communityRes.community.actor_id)).community.unwrap();
let gammaFollow = (await followCommunity(gamma, true, gammaCommunity.community.id));
let gammaCommunity = (
await resolveCommunity(gamma, communityRes.community.actor_id)
).community.unwrap();
let gammaFollow = await followCommunity(
gamma,
true,
gammaCommunity.community.id
);
expect(gammaFollow.community_view.subscribed).toBe("Subscribed");
let gammaPost = (await createPost(gamma, gammaCommunity.community.id)).post_view;
let gammaPost = (await createPost(gamma, gammaCommunity.community.id))
.post_view;
expect(gammaPost.post.id).toBeDefined();
expect(gammaPost.creator_banned_from_community).toBe(false);
// admin of beta decides to ban gamma from community
let betaCommunity = (await resolveCommunity(beta, communityRes.community.actor_id)).community.unwrap();
let bannedUserInfo1 = (await getSite(gamma)).my_user.unwrap().local_user_view.person;
let bannedUserInfo2 = (await resolvePerson(beta, bannedUserInfo1.actor_id)).person.unwrap();
let banRes = (await banPersonFromCommunity(beta, bannedUserInfo2.person.id, betaCommunity.community.id, true, true));
console.log(banRes);
let betaCommunity = (
await resolveCommunity(beta, communityRes.community.actor_id)
).community.unwrap();
let bannedUserInfo1 = (await getSite(gamma)).my_user.unwrap().local_user_view
.person;
let bannedUserInfo2 = (
await resolvePerson(beta, bannedUserInfo1.actor_id)
).person.unwrap();
let banRes = await banPersonFromCommunity(
beta,
bannedUserInfo2.person.id,
betaCommunity.community.id,
true,
true
);
expect(banRes.banned).toBe(true);
// ban doesnt federate to community's origin instance alpha
@ -196,6 +221,6 @@ test('Admin actions in remote community are not federated to origin', async () =
expect(alphaPost.creator_banned_from_community).toBe(false);
// and neither to gamma
let gammaPost2 = (await getPost(gamma, gammaPost.post.id));
let gammaPost2 = await getPost(gamma, gammaPost.post.id);
expect(gammaPost2.post_view.creator_banned_from_community).toBe(false);
});

@ -1,5 +1,6 @@
jest.setTimeout(120000);
import {SubscribedType} from 'lemmy-js-client';
import { SubscribedType } from "lemmy-js-client";
import {
alpha,
setupLogins,
@ -7,8 +8,7 @@ import {
followCommunity,
unfollowRemotes,
getSite,
delay,
} from './shared';
} from "./shared";
beforeAll(async () => {
await setupLogins();
@ -18,24 +18,20 @@ afterAll(async () => {
await unfollowRemotes(alpha);
});
test('Follow federated community', async () => {
test("Follow federated community", async () => {
let betaCommunity = (await resolveBetaCommunity(alpha)).community.unwrap();
let follow = await followCommunity(
alpha,
true,
betaCommunity.community.id
);
let follow = await followCommunity(alpha, true, betaCommunity.community.id);
// Make sure the follow response went through
expect(follow.community_view.community.local).toBe(false);
expect(follow.community_view.community.name).toBe('main');
expect(follow.community_view.community.name).toBe("main");
expect(follow.community_view.subscribed).toBe(SubscribedType.Subscribed);
// Check it from local
let site = await getSite(alpha);
let remoteCommunityId = site.my_user.unwrap().follows.find(
c => c.community.local == false
).community.id;
let remoteCommunityId = site.my_user
.unwrap()
.follows.find(c => c.community.local == false).community.id;
expect(remoteCommunityId).toBeDefined();
expect(site.my_user.unwrap().follows.length).toBe(2);

@ -1,6 +1,7 @@
jest.setTimeout(120000);
import {None} from '@sniptt/monads';
import { PostView, CommunityView } from 'lemmy-js-client';
import { None } from "@sniptt/monads";
import { PostView, CommunityView } from "lemmy-js-client";
import {
alpha,
beta,
@ -33,8 +34,8 @@ import {
API,
getSite,
unfollows,
resolveCommunity
} from './shared';
resolveCommunity,
} from "./shared";
let betaCommunity: CommunityView;
@ -52,12 +53,22 @@ afterAll(async () => {
function assertPostFederation(postOne: PostView, postTwo: PostView) {
expect(postOne.post.ap_id).toBe(postTwo.post.ap_id);
expect(postOne.post.name).toBe(postTwo.post.name);
expect(postOne.post.body.unwrapOr("none")).toBe(postTwo.post.body.unwrapOr("none"));
expect(postOne.post.url.unwrapOr("none")).toBe(postTwo.post.url.unwrapOr("none"));
expect(postOne.post.body.unwrapOr("none")).toBe(
postTwo.post.body.unwrapOr("none")
);
expect(postOne.post.url.unwrapOr("https://google.com/")).toBe(
postTwo.post.url.unwrapOr("https://google.com/")
);
expect(postOne.post.nsfw).toBe(postTwo.post.nsfw);
expect(postOne.post.embed_title.unwrapOr("none")).toBe(postTwo.post.embed_title.unwrapOr("none"));
expect(postOne.post.embed_description.unwrapOr("none")).toBe(postTwo.post.embed_description.unwrapOr("none"));
expect(postOne.post.embed_html.unwrapOr("none")).toBe(postTwo.post.embed_html.unwrapOr("none"));
expect(postOne.post.embed_title.unwrapOr("none")).toBe(
postTwo.post.embed_title.unwrapOr("none")
);
expect(postOne.post.embed_description.unwrapOr("none")).toBe(
postTwo.post.embed_description.unwrapOr("none")
);
expect(postOne.post.embed_video_url.unwrapOr("none")).toBe(
postTwo.post.embed_video_url.unwrapOr("none")
);
expect(postOne.post.published).toBe(postTwo.post.published);
expect(postOne.community.actor_id).toBe(postTwo.community.actor_id);
expect(postOne.post.locked).toBe(postTwo.post.locked);
@ -65,7 +76,7 @@ function assertPostFederation(postOne: PostView, postTwo: PostView) {
expect(postOne.post.deleted).toBe(postTwo.post.deleted);
}
test('Create a post', async () => {
test("Create a post", async () => {
let postRes = await createPost(alpha, betaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
expect(postRes.post_view.community.local).toBe(false);
@ -73,7 +84,9 @@ test('Create a post', async () => {
expect(postRes.post_view.counts.score).toBe(1);
// Make sure that post is liked on beta
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost).toBeDefined();
expect(betaPost.community.local).toBe(true);
@ -83,19 +96,19 @@ test('Create a post', async () => {
// Delta only follows beta, so it should not see an alpha ap_id
let deltaPost = (await resolvePost(delta, postRes.post_view.post)).post;
expect(deltaPost.isNone()).toBe(true)
expect(deltaPost.isNone()).toBe(true);
// Epsilon has alpha blocked, it should not see the alpha post
let epsilonPost = (await resolvePost(epsilon, postRes.post_view.post)).post;
expect(epsilonPost.isNone()).toBe(true);
});
test('Create a post in a non-existent community', async () => {
let postRes = await createPost(alpha, -2) as any;
expect(postRes.error).toBe('couldnt_find_community');
test("Create a post in a non-existent community", async () => {
let postRes = (await createPost(alpha, -2)) as any;
expect(postRes.error).toBe("couldnt_find_community");
});
test('Unlike a post', async () => {
test("Unlike a post", async () => {
let postRes = await createPost(alpha, betaCommunity.community.id);
let unlike = await likePost(alpha, 0, postRes.post_view.post);
expect(unlike.post_view.counts.score).toBe(0);
@ -105,7 +118,9 @@ test('Unlike a post', async () => {
expect(unlike2.post_view.counts.score).toBe(0);
// Make sure that post is unliked on beta
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost).toBeDefined();
expect(betaPost.community.local).toBe(true);
expect(betaPost.creator.local).toBe(false);
@ -113,36 +128,42 @@ test('Unlike a post', async () => {
assertPostFederation(betaPost, postRes.post_view);
});
test('Update a post', async () => {
test("Update a post", async () => {
let postRes = await createPost(alpha, betaCommunity.community.id);
let updatedName = 'A jest test federated post, updated';
let updatedName = "A jest test federated post, updated";
let updatedPost = await editPost(alpha, postRes.post_view.post);
expect(updatedPost.post_view.post.name).toBe(updatedName);
expect(updatedPost.post_view.community.local).toBe(false);
expect(updatedPost.post_view.creator.local).toBe(true);
// Make sure that post is updated on beta
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost.community.local).toBe(true);
expect(betaPost.creator.local).toBe(false);
expect(betaPost.post.name).toBe(updatedName);
assertPostFederation(betaPost, updatedPost.post_view);
// Make sure lemmy beta cannot update the post
let updatedPostBeta = await editPost(beta, betaPost.post) as any;
expect(updatedPostBeta.error).toBe('no_post_edit_allowed');
let updatedPostBeta = (await editPost(beta, betaPost.post)) as any;
expect(updatedPostBeta.error).toBe("no_post_edit_allowed");
});
test('Sticky a post', async () => {
test("Sticky a post", async () => {
let postRes = await createPost(alpha, betaCommunity.community.id);
let betaPost1 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost1 = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
let stickiedPostRes = await stickyPost(beta, true, betaPost1.post);
expect(stickiedPostRes.post_view.post.stickied).toBe(true);
// Make sure that post is stickied on beta
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost.community.local).toBe(true);
expect(betaPost.creator.local).toBe(false);
expect(betaPost.post.stickied).toBe(true);
@ -152,25 +173,33 @@ test('Sticky a post', async () => {
expect(unstickiedPost.post_view.post.stickied).toBe(false);
// Make sure that post is unstickied on beta
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost2 = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost2.community.local).toBe(true);
expect(betaPost2.creator.local).toBe(false);
expect(betaPost2.post.stickied).toBe(false);
// Make sure that gamma cannot sticky the post on beta
let gammaPost = (await resolvePost(gamma, postRes.post_view.post)).post.unwrap();
let gammaPost = (
await resolvePost(gamma, postRes.post_view.post)
).post.unwrap();
let gammaTrySticky = await stickyPost(gamma, true, gammaPost.post);
let betaPost3 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost3 = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(gammaTrySticky.post_view.post.stickied).toBe(true);
expect(betaPost3.post.stickied).toBe(false);
});
test('Lock a post', async () => {
test("Lock a post", async () => {
await followCommunity(alpha, true, betaCommunity.community.id);
let postRes = await createPost(alpha, betaCommunity.community.id);
// Lock the post
let betaPost1 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost1 = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
let lockedPostRes = await lockPost(beta, true, betaPost1.post);
expect(lockedPostRes.post_view.post.locked).toBe(true);
@ -181,7 +210,7 @@ test('Lock a post', async () => {
// Try to make a new comment there, on alpha
let comment: any = await createComment(alpha, alphaPost1.post.id, None);
expect(comment['error']).toBe('locked');
expect(comment["error"]).toBe("locked");
// Unlock a post
let unlockedPost = await lockPost(beta, false, betaPost1.post);
@ -199,7 +228,7 @@ test('Lock a post', async () => {
expect(commentAlpha).toBeDefined();
});
test('Delete a post', async () => {
test("Delete a post", async () => {
let postRes = await createPost(alpha, betaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
@ -217,26 +246,38 @@ test('Delete a post', async () => {
expect(undeletedPost.post_view.post.deleted).toBe(false);
// Make sure lemmy beta sees post is undeleted
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost2 = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost2.post.deleted).toBe(false);
assertPostFederation(betaPost2, undeletedPost.post_view);
// Make sure lemmy beta cannot delete the post
let deletedPostBeta = await deletePost(beta, true, betaPost2.post) as any;
expect(deletedPostBeta.error).toStrictEqual('no_post_edit_allowed');
let deletedPostBeta = (await deletePost(beta, true, betaPost2.post)) as any;
expect(deletedPostBeta.error).toStrictEqual("no_post_edit_allowed");
});
test('Remove a post from admin and community on different instance', async () => {
let gammaCommunity = await resolveCommunity(gamma, betaCommunity.community.actor_id);
let postRes = await createPost(gamma, gammaCommunity.community.unwrap().community.id);
test("Remove a post from admin and community on different instance", async () => {
let gammaCommunity = await resolveCommunity(
gamma,
betaCommunity.community.actor_id
);
let postRes = await createPost(
gamma,
gammaCommunity.community.unwrap().community.id
);
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap();
let alphaPost = (
await resolvePost(alpha, postRes.post_view.post)
).post.unwrap();
let removedPost = await removePost(alpha, true, alphaPost.post);
expect(removedPost.post_view.post.removed).toBe(true);
expect(removedPost.post_view.post.name).toBe(postRes.post_view.post.name);
// Make sure lemmy beta sees post is NOT removed
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost.post.removed).toBe(false);
// Undelete
@ -244,12 +285,14 @@ test('Remove a post from admin and community on different instance', async () =>
expect(undeletedPost.post_view.post.removed).toBe(false);
// Make sure lemmy beta sees post is undeleted
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost2 = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost2.post.removed).toBe(false);
assertPostFederation(betaPost2, undeletedPost.post_view);
});
test('Remove a post from admin and community on same instance', async () => {
test("Remove a post from admin and community on same instance", async () => {
await followBeta(alpha);
let postRes = await createPost(alpha, betaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
@ -279,26 +322,31 @@ test('Remove a post from admin and community on same instance', async () => {
await unfollowRemotes(alpha);
});
test('Search for a post', async () => {
test("Search for a post", async () => {
await unfollowRemotes(alpha);
let postRes = await createPost(alpha, betaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
let betaPost = (await resolvePost(beta, postRes.post_view.post)).post.unwrap();
let betaPost = (
await resolvePost(beta, postRes.post_view.post)
).post.unwrap();
expect(betaPost.post.name).toBeDefined();
});
test('Enforce site ban for federated user', async () => {
test("Enforce site ban for federated user", async () => {
// create a test user
let alphaUserJwt = await registerUser(alpha);
expect(alphaUserJwt).toBeDefined();
let alpha_user: API = {
client: alpha.client,
auth: alphaUserJwt.jwt,
client: alpha.client,
auth: alphaUserJwt.jwt,
};
let alphaUserActorId = (await getSite(alpha_user)).my_user.unwrap().local_user_view.person.actor_id;
let alphaUserActorId = (await getSite(alpha_user)).my_user.unwrap()
.local_user_view.person.actor_id;
expect(alphaUserActorId).toBeDefined();
let alphaPerson = (await resolvePerson(alpha_user, alphaUserActorId)).person.unwrap();
let alphaPerson = (
await resolvePerson(alpha_user, alphaUserActorId)
).person.unwrap();
expect(alphaPerson).toBeDefined();
// alpha makes post in beta community, it federates to beta instance
@ -307,7 +355,12 @@ test('Enforce site ban for federated user', async () => {
expect(searchBeta1.posts[0]).toBeDefined();
// ban alpha from its instance
let banAlpha = await banPersonFromSite(alpha, alphaPerson.person.id, true, true);
let banAlpha = await banPersonFromSite(
alpha,
alphaPerson.person.id,
true,
true
);
expect(banAlpha.banned).toBe(true);
// alpha ban should be federated to beta
@ -319,7 +372,12 @@ test('Enforce site ban for federated user', async () => {
expect(searchBeta2.posts[0]).toBeUndefined();
// Unban alpha
let unBanAlpha = await banPersonFromSite(alpha, alphaPerson.person.id, false, false);
let unBanAlpha = await banPersonFromSite(
alpha,
alphaPerson.person.id,
false,
false
);
expect(unBanAlpha.banned).toBe(false);
// alpha makes new post in beta community, it federates
@ -327,11 +385,11 @@ test('Enforce site ban for federated user', async () => {
let searchBeta3 = await searchPostLocal(beta, postRes2.post_view.post);
expect(searchBeta3.posts[0]).toBeDefined();
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId)
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId);
expect(alphaUserOnBeta2.person.unwrap().person.banned).toBe(false);
});
test('Enforce community ban for federated user', async () => {
test("Enforce community ban for federated user", async () => {
let alphaShortname = `@lemmy_alpha@lemmy-alpha:8541`;
let alphaPerson = (await resolvePerson(beta, alphaShortname)).person.unwrap();
expect(alphaPerson).toBeDefined();
@ -342,7 +400,13 @@ test('Enforce community ban for federated user', async () => {
expect(searchBeta1.posts[0]).toBeDefined();
// ban alpha from beta community
let banAlpha = await banPersonFromCommunity(beta, alphaPerson.person.id, 2, true, true);
let banAlpha = await banPersonFromCommunity(
beta,
alphaPerson.person.id,
2,
true,
true
);
expect(banAlpha.banned).toBe(true);
// ensure that the post by alpha got removed
@ -373,29 +437,37 @@ test('Enforce community ban for federated user', async () => {
expect(searchBeta2.posts[0]).toBeDefined();
});
test('A and G subscribe to B (center) A posts, it gets announced to G', async () => {
test("A and G subscribe to B (center) A posts, it gets announced to G", async () => {
let postRes = await createPost(alpha, betaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
let betaPost = (await resolvePost(gamma, postRes.post_view.post)).post.unwrap();
let betaPost = (
await resolvePost(gamma, postRes.post_view.post)
).post.unwrap();
expect(betaPost.post.name).toBeDefined();
});
test('Report a post', async () => {
test("Report a post", async () => {
let betaCommunity = (await resolveBetaCommunity(beta)).community.unwrap();
let postRes = await createPost(beta, betaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post.unwrap();
let alphaReport = (await reportPost(alpha, alphaPost.post.id, randomString(10)))
.post_report_view.post_report;
let alphaPost = (
await resolvePost(alpha, postRes.post_view.post)
).post.unwrap();
let alphaReport = (
await reportPost(alpha, alphaPost.post.id, randomString(10))
).post_report_view.post_report;
let betaReport = (await listPostReports(beta)).post_reports[0].post_report;
expect(betaReport).toBeDefined();
expect(betaReport.resolved).toBe(false);
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name);
expect(betaReport.original_post_url.unwrapOr("none")).toBe(alphaReport.original_post_url.unwrapOr("none"));
expect(betaReport.original_post_body.unwrapOr("none")).toBe(alphaReport.original_post_body.unwrapOr("none"));
expect(betaReport.original_post_url.unwrapOr("none")).toBe(
alphaReport.original_post_url.unwrapOr("none")
);
expect(betaReport.original_post_body.unwrapOr("none")).toBe(
alphaReport.original_post_body.unwrapOr("none")
);
expect(betaReport.reason).toBe(alphaReport.reason);
});

@ -9,7 +9,7 @@ import {
listPrivateMessages,
deletePrivateMessage,
unfollowRemotes,
} from './shared';
} from "./shared";
let recipient_id: number;
@ -23,7 +23,7 @@ afterAll(async () => {
await unfollowRemotes(alpha);
});
test('Create a private message', async () => {
test("Create a private message", async () => {
let pmRes = await createPrivateMessage(alpha, recipient_id);
expect(pmRes.private_message_view.private_message.content).toBeDefined();
expect(pmRes.private_message_view.private_message.local).toBe(true);
@ -37,8 +37,8 @@ test('Create a private message', async () => {
expect(betaPms.private_messages[0].recipient.local).toBe(true);
});
test('Update a private message', async () => {
let updatedContent = 'A jest test federated private message edited';
test("Update a private message", async () => {
let updatedContent = "A jest test federated private message edited";
let pmRes = await createPrivateMessage(alpha, recipient_id);
let pmUpdated = await editPrivateMessage(
@ -55,7 +55,7 @@ test('Update a private message', async () => {
);
});
test('Delete a private message', async () => {
test("Delete a private message", async () => {
let pmRes = await createPrivateMessage(alpha, recipient_id);
let betaPms1 = await listPrivateMessages(beta);
let deletedPmRes = await deletePrivateMessage(

@ -1,4 +1,4 @@
import {None, Some, Option} from '@sniptt/monads';
import { None, Some, Option } from "@sniptt/monads";
import {
Login,
LoginResponse,
@ -63,8 +63,8 @@ import {
EditSite,
CommentSortType,
GetComments,
GetCommentsResponse
} from 'lemmy-js-client';
GetCommentsResponse,
} from "lemmy-js-client";
export interface API {
client: LemmyHttp;
@ -72,59 +72,59 @@ export interface API {
}
export let alpha: API = {
client: new LemmyHttp('http://127.0.0.1:8541'),
client: new LemmyHttp("http://127.0.0.1:8541"),
auth: None,
};
export let beta: API = {
client: new LemmyHttp('http://127.0.0.1:8551'),
client: new LemmyHttp("http://127.0.0.1:8551"),
auth: None,
};
export let gamma: API = {
client: new LemmyHttp('http://127.0.0.1:8561'),
client: new LemmyHttp("http://127.0.0.1:8561"),
auth: None,
};
export let delta: API = {
client: new LemmyHttp('http://127.0.0.1:8571'),
client: new LemmyHttp("http://127.0.0.1:8571"),
auth: None,
};
export let epsilon: API = {
client: new LemmyHttp('http://127.0.0.1:8581'),
client: new LemmyHttp("http://127.0.0.1:8581"),
auth: None,
};
const password = 'lemmylemmy'
const password = "lemmylemmy";
export async function setupLogins() {
let formAlpha = new Login({
username_or_email: 'lemmy_alpha',
username_or_email: "lemmy_alpha",
password,
});
let resAlpha = alpha.client.login(formAlpha);
let formBeta = new Login({
username_or_email: 'lemmy_beta',
username_or_email: "lemmy_beta",
password,
});
let resBeta = beta.client.login(formBeta);
let formGamma = new Login({
username_or_email: 'lemmy_gamma',
username_or_email: "lemmy_gamma",
password,
});
let resGamma = gamma.client.login(formGamma);
let formDelta = new Login({
username_or_email: 'lemmy_delta',
username_or_email: "lemmy_delta",
password,
});
let resDelta = delta.client.login(formDelta);
let formEpsilon = new Login({
username_or_email: 'lemmy_epsilon',
username_or_email: "lemmy_epsilon",
password,
});
let resEpsilon = epsilon.client.login(formEpsilon);
@ -145,6 +145,8 @@ export async function setupLogins() {
// Registration applications are now enabled by default, need to disable them
let editSiteForm = new EditSite({
require_application: Some(false),
federation_debug: Some(true),
name: None,
sidebar: None,
description: None,
@ -155,23 +157,74 @@ export async function setupLogins() {
enable_nsfw: None,
community_creation_admin_only: None,
require_email_verification: None,
require_application: Some(false),
application_question: None,
private_instance: None,
default_theme: None,
legal_information: None,
default_post_listing_type: None,
legal_information: None,
application_email_admins: None,
hide_modlog_mod_names: None,
discussion_languages: None,
slur_filter_regex: None,
actor_name_max_length: None,
rate_limit_message: Some(999),
rate_limit_message_per_second: None,
rate_limit_post: Some(999),
rate_limit_post_per_second: None,
rate_limit_register: Some(999),
rate_limit_register_per_second: None,
rate_limit_image: Some(999),
rate_limit_image_per_second: None,
rate_limit_comment: Some(999),
rate_limit_comment_per_second: None,
rate_limit_search: Some(999),
rate_limit_search_per_second: None,
federation_enabled: None,
federation_strict_allowlist: None,
federation_http_fetch_retry_limit: None,
federation_worker_count: None,
captcha_enabled: None,
captcha_difficulty: None,
allowed_instances: None,
blocked_instances: None,
auth: "",
});
// Set the blocks and auths for each
editSiteForm.auth = alpha.auth.unwrap();
editSiteForm.allowed_instances = Some([
"lemmy-beta",
"lemmy-gamma",
"lemmy-delta",
"lemmy-epsilon",
]);
await alpha.client.editSite(editSiteForm);
editSiteForm.auth = beta.auth.unwrap();
editSiteForm.allowed_instances = Some([
"lemmy-alpha",
"lemmy-gamma",
"lemmy-delta",
"lemmy-epsilon",
]);
await beta.client.editSite(editSiteForm);
editSiteForm.auth = gamma.auth.unwrap();
editSiteForm.allowed_instances = Some([
"lemmy-alpha",
"lemmy-beta",
"lemmy-delta",
"lemmy-epsilon",
]);
await gamma.client.editSite(editSiteForm);
editSiteForm.allowed_instances = Some(["lemmy-beta"]);
editSiteForm.auth = delta.auth.unwrap();
await delta.client.editSite(editSiteForm);
editSiteForm.auth = epsilon.auth.unwrap();
editSiteForm.allowed_instances = Some([]);
editSiteForm.blocked_instances = Some(["lemmy-alpha"]);
await epsilon.client.editSite(editSiteForm);
// Create the main alpha/beta communities
@ -185,7 +238,7 @@ export async function createPost(
): Promise<PostResponse> {
let name = randomString(5);
let body = Some(randomString(10));
let url = Some('https://google.com/');
let url = Some("https://google.com/");
let form = new CreatePost({
name,
url,
@ -194,12 +247,13 @@ export async function createPost(
community_id,
nsfw: None,
honeypot: None,
language_id: None,
});
return api.client.createPost(form);
}
export async function editPost(api: API, post: Post): Promise<PostResponse> {
let name = Some('A jest test federated post, updated');
let name = Some("A jest test federated post, updated");
let form = new EditPost({
name,
post_id: post.id,
@ -207,6 +261,7 @@ export async function editPost(api: API, post: Post): Promise<PostResponse> {
nsfw: None,
url: None,
body: None,
language_id: None,
});
return api.client.editPost(form);
}
@ -342,7 +397,7 @@ export async function resolveBetaCommunity(
): Promise<ResolveObjectResponse> {
// Use short-hand search url
let form = new ResolveObject({
q: '!main@lemmy-beta:8551',
q: "!main@lemmy-beta:8551",
auth: api.auth,
});
return api.client.resolveObject(form);
@ -415,7 +470,7 @@ export async function followCommunity(
let form = new FollowCommunity({
community_id,
follow,
auth: api.auth.unwrap()
auth: api.auth.unwrap(),
});
return api.client.followCommunity(form);
}
@ -428,7 +483,7 @@ export async function likePost(
let form = new CreatePostLike({
post_id: post.id,
score: score,
auth: api.auth.unwrap()
auth: api.auth.unwrap(),
});
return api.client.likePost(form);
@ -438,13 +493,14 @@ export async function createComment(
api: API,
post_id: number,
parent_id: Option<number>,
content = 'a jest test comment'
content = "a jest test comment"
): Promise<CommentResponse> {
let form = new CreateComment({
content,
post_id,
parent_id,
form_id: None,
language_id: None,
auth: api.auth.unwrap(),
});
return api.client.createComment(form);
@ -453,13 +509,15 @@ export async function createComment(
export async function editComment(
api: API,
comment_id: number,
content = 'A jest test federated comment update'
content = Some("A jest test federated comment update")
): Promise<CommentResponse> {
let form = new EditComment({
content,
comment_id,
form_id: None,
auth: api.auth.unwrap()
language_id: None,
distinguished: None,
auth: api.auth.unwrap(),
});
return api.client.editComment(form);
}
@ -491,7 +549,9 @@ export async function removeComment(
return api.client.removeComment(form);
}
export async function getMentions(api: API): Promise<GetPersonMentionsResponse> {
export async function getMentions(
api: API
): Promise<GetPersonMentionsResponse> {
let form = new GetPersonMentions({
sort: Some(CommentSortType.New),
unread_only: Some(false),
@ -519,7 +579,7 @@ export async function createCommunity(
api: API,
name_: string = randomString(5)
): Promise<CommunityResponse> {
let description = Some('a sample description');
let description = Some("a sample description");
let form = new CreateCommunity({
name: name_,
title: name_,
@ -577,7 +637,7 @@ export async function createPrivateMessage(
api: API,
recipient_id: number
): Promise<PrivateMessageResponse> {
let content = 'A jest test federated private message';
let content = "A jest test federated private message";
let form = new CreatePrivateMessage({
content,
recipient_id,
@ -590,7 +650,7 @@ export async function editPrivateMessage(
api: API,
private_message_id: number
): Promise<PrivateMessageResponse> {
let updatedContent = 'A jest test federated private message edited';
let updatedContent = "A jest test federated private message edited";
let form = new EditPrivateMessage({
content: updatedContent,
private_message_id,
@ -630,18 +690,18 @@ export async function registerUser(
return api.client.register(form);
}
export async function saveUserSettingsBio(
api: API
): Promise<LoginResponse> {
export async function saveUserSettingsBio(api: API): Promise<LoginResponse> {
let form = new SaveUserSettings({
show_nsfw: Some(true),
theme: Some('darkly'),
theme: Some("darkly"),
default_sort_type: Some(Object.keys(SortType).indexOf(SortType.Active)),
default_listing_type: Some(Object.keys(ListingType).indexOf(ListingType.All)),
lang: Some('en'),
default_listing_type: Some(
Object.keys(ListingType).indexOf(ListingType.All)
),
interface_language: Some("en"),
show_avatars: Some(true),
send_notifications_to_email: Some(false),
bio: Some('a changed bio'),
bio: Some("a changed bio"),
avatar: None,
banner: None,
display_name: None,
@ -652,6 +712,7 @@ export async function saveUserSettingsBio(
show_bot_accounts: None,
show_new_post_notifs: None,
bot_account: None,
discussion_languages: None,
auth: api.auth.unwrap(),
});
return saveUserSettings(api, form);
@ -660,18 +721,20 @@ export async function saveUserSettingsBio(
export async function saveUserSettingsFederated(
api: API
): Promise<LoginResponse> {
let avatar = Some('https://image.flaticon.com/icons/png/512/35/35896.png');
let banner = Some('https://image.flaticon.com/icons/png/512/36/35896.png');
let bio = Some('a changed bio');
let avatar = Some("https://image.flaticon.com/icons/png/512/35/35896.png");
let banner = Some("https://image.flaticon.com/icons/png/512/36/35896.png");
let bio = Some("a changed bio");
let form = new SaveUserSettings({
show_nsfw: Some(false),
theme: Some(''),
theme: Some(""),
default_sort_type: Some(Object.keys(SortType).indexOf(SortType.Hot)),
default_listing_type: Some(Object.keys(ListingType).indexOf(ListingType.All)),
lang: Some(''),
default_listing_type: Some(
Object.keys(ListingType).indexOf(ListingType.All)
),
interface_language: Some(""),
avatar,
banner,
display_name: Some('user321'),
display_name: Some("user321"),
show_avatars: Some(false),
send_notifications_to_email: Some(false),
bio,
@ -682,6 +745,7 @@ export async function saveUserSettingsFederated(
bot_account: None,
show_bot_accounts: None,
show_new_post_notifs: None,
discussion_languages: None,
auth: api.auth.unwrap(),
});
return await saveUserSettings(alpha, form);
@ -694,19 +758,15 @@ export async function saveUserSettings(
return api.client.saveUserSettings(form);
}
export async function deleteUser(
api: API
): Promise<DeleteAccountResponse> {
export async function deleteUser(api: API): Promise<DeleteAccountResponse> {
let form = new DeleteAccount({
auth: api.auth.unwrap(),
password
password,
});
return api.client.deleteAccount(form);
}
export async function getSite(
api: API
): Promise<GetSiteResponse> {
export async function getSite(api: API): Promise<GetSiteResponse> {
let form = new GetSite({
auth: api.auth,
});
@ -725,14 +785,12 @@ export async function listPrivateMessages(
return api.client.getPrivateMessages(form);
}
export async function unfollowRemotes(
api: API
): Promise<GetSiteResponse> {
export async function unfollowRemotes(api: API): Promise<GetSiteResponse> {
// Unfollow all remote communities
let site = await getSite(api);
let remoteFollowed = site.my_user.unwrap().follows.filter(
c => c.community.local == false
);
let remoteFollowed = site.my_user
.unwrap()
.follows.filter(c => c.community.local == false);
for (let cu of remoteFollowed) {
await followCommunity(api, false, cu.community.id);
}
@ -743,7 +801,11 @@ export async function unfollowRemotes(
export async function followBeta(api: API): Promise<CommunityResponse> {
let betaCommunity = (await resolveBetaCommunity(api)).community;
if (betaCommunity.isSome()) {
let follow = await followCommunity(api, true, betaCommunity.unwrap().community.id);
let follow = await followCommunity(
api,
true,
betaCommunity.unwrap().community.id
);
return follow;
} else {
return Promise.reject("no community worked");
@ -763,7 +825,9 @@ export async function reportPost(
return api.client.createPostReport(form);
}
export async function listPostReports(api: API): Promise<ListPostReportsResponse> {
export async function listPostReports(
api: API
): Promise<ListPostReportsResponse> {
let form = new ListPostReports({
auth: api.auth.unwrap(),
page: None,
@ -787,7 +851,9 @@ export async function reportComment(
return api.client.createCommentReport(form);
}
export async function listCommentReports(api: API): Promise<ListCommentReportsResponse> {
export async function listCommentReports(
api: API
): Promise<ListCommentReportsResponse> {
let form = new ListCommentReports({
page: None,
limit: None,
@ -798,7 +864,7 @@ export async function listCommentReports(api: API): Promise<ListCommentReportsRe
return api.client.listCommentReports(form);
}
export function delay(millis: number = 500) {
export function delay(millis = 500) {
return new Promise(resolve => setTimeout(resolve, millis));
}
@ -811,8 +877,9 @@ export function wrapper(form: any): string {
}
export function randomString(length: number): string {
var result = '';
var characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
var result = "";
var characters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));

@ -1,8 +1,6 @@
jest.setTimeout(120000);
import {None} from '@sniptt/monads';
import {
PersonViewSafe,
} from 'lemmy-js-client';
import { None } from "@sniptt/monads";
import { PersonViewSafe } from "lemmy-js-client";
import {
alpha,
@ -20,7 +18,7 @@ import {
resolveComment,
saveUserSettingsFederated,
setupLogins,
} from './shared';
} from "./shared";
beforeAll(async () => {
await setupLogins();
@ -28,59 +26,82 @@ beforeAll(async () => {
let apShortname: string;
function assertUserFederation(userOne: PersonViewSafe, userTwo: PersonViewSafe) {
function assertUserFederation(
userOne: PersonViewSafe,
userTwo: PersonViewSafe
) {
expect(userOne.person.name).toBe(userTwo.person.name);
expect(userOne.person.display_name.unwrapOr("none")).toBe(userTwo.person.display_name.unwrapOr("none"));
expect(userOne.person.bio.unwrapOr("none")).toBe(userTwo.person.bio.unwrapOr("none"));
expect(userOne.person.display_name.unwrapOr("none")).toBe(
userTwo.person.display_name.unwrapOr("none")
);
expect(userOne.person.bio.unwrapOr("none")).toBe(
userTwo.person.bio.unwrapOr("none")
);
expect(userOne.person.actor_id).toBe(userTwo.person.actor_id);
expect(userOne.person.avatar.unwrapOr("none")).toBe(userTwo.person.avatar.unwrapOr("none"));
expect(userOne.person.banner.unwrapOr("none")).toBe(userTwo.person.banner.unwrapOr("none"));
expect(userOne.person.avatar.unwrapOr("none")).toBe(
userTwo.person.avatar.unwrapOr("none")
);
expect(userOne.person.banner.unwrapOr("none")).toBe(
userTwo.person.banner.unwrapOr("none")
);
expect(userOne.person.published).toBe(userTwo.person.published);
}
test('Create user', async () => {
test("Create user", async () => {
let userRes = await registerUser(alpha);
expect(userRes.jwt).toBeDefined();
alpha.auth = userRes.jwt;
let site = await getSite(alpha);
expect(site.my_user).toBeDefined();
apShortname = `@${site.my_user.unwrap().local_user_view.person.name}@lemmy-alpha:8541`;
apShortname = `@${
site.my_user.unwrap().local_user_view.person.name
}@lemmy-alpha:8541`;
});
test('Set some user settings, check that they are federated', async () => {
test("Set some user settings, check that they are federated", async () => {
await saveUserSettingsFederated(alpha);
let alphaPerson = (await resolvePerson(alpha, apShortname)).person.unwrap();
let betaPerson = (await resolvePerson(beta, apShortname)).person.unwrap();
assertUserFederation(alphaPerson, betaPerson);
});
test('Delete user', async () => {
test("Delete user", async () => {
let userRes = await registerUser(alpha);
expect(userRes.jwt).toBeDefined();
let user: API = {
client: alpha.client,
auth: userRes.jwt
}
auth: userRes.jwt,
};
// make a local post and comment
let alphaCommunity = (await resolveCommunity(user, '!main@lemmy-alpha:8541')).community.unwrap();
let localPost = (await createPost(user, alphaCommunity.community.id)).post_view.post;
let alphaCommunity = (
await resolveCommunity(user, "!main@lemmy-alpha:8541")
).community.unwrap();
let localPost = (await createPost(user, alphaCommunity.community.id))
.post_view.post;
expect(localPost).toBeDefined();
let localComment = (await createComment(user, localPost.id, None)).comment_view.comment;
let localComment = (await createComment(user, localPost.id, None))
.comment_view.comment;
expect(localComment).toBeDefined();
// make a remote post and comment
let betaCommunity = (await resolveBetaCommunity(user)).community.unwrap();
let remotePost = (await createPost(user, betaCommunity.community.id)).post_view.post;
let remotePost = (await createPost(user, betaCommunity.community.id))
.post_view.post;
expect(remotePost).toBeDefined();
let remoteComment = (await createComment(user, remotePost.id, None)).comment_view.comment;
let remoteComment = (await createComment(user, remotePost.id, None))
.comment_view.comment;
expect(remoteComment).toBeDefined();
await deleteUser(user);
expect((await resolvePost(alpha, localPost)).post.isNone()).toBe(true);
expect((await resolveComment(alpha, localComment)).comment.isNone()).toBe(true)
expect((await resolvePost(alpha, remotePost)).post.isNone()).toBe(true)
expect((await resolveComment(alpha, remoteComment)).comment.isNone()).toBe(true)
expect((await resolveComment(alpha, localComment)).comment.isNone()).toBe(
true
);
expect((await resolvePost(alpha, remotePost)).post.isNone()).toBe(true);
expect((await resolveComment(alpha, remoteComment)).comment.isNone()).toBe(
true
);
});

File diff suppressed because it is too large Load Diff

@ -2,11 +2,4 @@
# https://join-lemmy.org/docs/en/administration/configuration.html
{
hostname: lemmy-alpha
federation: {
enabled: true
}
slur_filter:
'''
(fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?)
'''
}

@ -14,64 +14,7 @@
# Maximum number of active sql connections
pool_size: 5
}
# rate limits for various user actions, by user ip
rate_limit: {
# Maximum number of messages created in interval
message: 180
# Interval length for message limit, in seconds
message_per_second: 60
# Maximum number of posts created in interval
post: 6
# Interval length for post limit, in seconds
post_per_second: 600
# Maximum number of registrations in interval
register: 3
# Interval length for registration limit, in seconds
register_per_second: 3600
# Maximum number of image uploads in interval
image: 6
# Interval length for image uploads, in seconds
image_per_second: 3600
# Maximum number of comments created in interval
comment: 6
# Interval length for comment limit, in seconds
comment_per_second: 600
search: 60
# Interval length for search limit, in seconds
search_per_second: 600
}
# Settings related to activitypub federation
federation: {
# Whether to enable activitypub federation.
enabled: false
# Allows and blocks are described here:
# https://join-lemmy.org/docs/en/administration/federation_getting_started.html
#
# list of instances with which federation is allowed
allowed_instances: [
instance1.tld
instance2.tld
/* ... */
]
# Instances which we never federate anything with (but previously federated objects are unaffected)
blocked_instances: [
string
/* ... */
]
# If true, only federate with instances on the allowlist and block everything else. If false
# use allowlist only for remote communities, and posts/comments in local communities
# (meaning remote communities will show content from arbitrary instances).
strict_allowlist: true
# Maximum number of HTTP requests allowed to handle a single incoming activity (or a single object fetch through the search).
http_fetch_retry_limit: 25
# Number of workers for sending outgoing activities. Search logs for Activity queue stats to
# see information. If running number is consistently close to the worker_count, you should
# increase it.
worker_count: 64
# Use federation debug mode. Allows connecting to http and localhost urls. Also sends outgoing
# activities synchronously for easier testing. Do not use in production.
debug: false
}
# Pictrs image server configuration.
pictrs: {
# Address where pictrs is available (for image hosting)
@ -79,12 +22,6 @@
# Set a custom pictrs API key. ( Required for deleting images )
api_key: "string"
}
captcha: {
# Whether captcha is required for signup
enabled: false
# Can be easy, medium, or hard
difficulty: "medium"
}
# Email sending configuration. All options except login/password are mandatory
email: {
# Hostname and port of the smtp server
@ -117,8 +54,4 @@
port: 8536
# Whether the site is available over TLS. Needs to be true for federation to work.
tls_enabled: true
# A regex list of slurs to block / hide
slur_filter: "(\bThis\b)|(\bis\b)|(\bsample\b)"
# Maximum length of local community and user names
actor_name_max_length: 20
}

@ -16,6 +16,7 @@ use lemmy_db_schema::{
source::{
comment::{CommentLike, CommentLikeForm},
comment_reply::CommentReply,
local_site::LocalSite,
},
traits::Likeable,
};
@ -35,13 +36,14 @@ impl Perform for CreateCommentLike {
websocket_id: Option<ConnectionId>,
) -> Result<CommentResponse, LemmyError> {
let data: &CreateCommentLike = self;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let mut recipient_ids = Vec::<LocalUserId>::new();
// Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, context.pool()).await?;
check_downvotes_enabled(data.score, &local_site)?;
let comment_id = data.comment_id;
let orig_comment = blocking(context.pool(), move |conn| {

@ -7,7 +7,10 @@ use lemmy_api_common::{
};
use lemmy_apub::protocol::activities::community::report::Report;
use lemmy_db_schema::{
source::comment_report::{CommentReport, CommentReportForm},
source::{
comment_report::{CommentReport, CommentReportForm},
local_site::LocalSite,
},
traits::Reportable,
};
use lemmy_db_views::structs::{CommentReportView, CommentView};
@ -28,9 +31,10 @@ impl Perform for CreateCommentReport {
let data: &CreateCommentReport = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let reason = self.reason.trim();
check_report_reason(reason, context)?;
check_report_reason(reason, &local_site)?;
let person_id = local_user_view.person.id;
let comment_id = data.comment_id;

@ -7,11 +7,10 @@ use lemmy_api_common::{
use lemmy_apub::protocol::activities::community::update::UpdateCommunity;
use lemmy_db_schema::{
source::{
community::{Community, CommunityForm},
community::{Community, CommunityUpdateForm},
moderator::{ModHideCommunity, ModHideCommunityForm},
},
traits::Crud,
utils::naive_now,
};
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
@ -33,20 +32,9 @@ impl Perform for HideCommunity {
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
is_admin(&local_user_view)?;
let community_id = data.community_id;
let read_community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let community_form = CommunityForm {
name: read_community.name,
title: read_community.title,
description: Some(read_community.description.to_owned()),
hidden: Some(data.hidden),
updated: Some(naive_now()),
..CommunityForm::default()
};
let community_form = CommunityUpdateForm::builder()
.hidden(Some(data.hidden))
.build();
let mod_hide_community_form = ModHideCommunityForm {
community_id: data.community_id,

@ -7,8 +7,10 @@ use lemmy_api_common::{
post::*,
private_message::*,
site::*,
utils::local_site_to_slur_regex,
websocket::*,
};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_utils::{error::LemmyError, utils::check_slurs, ConnectionId};
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
use serde::Deserialize;
@ -227,8 +229,10 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
}
/// Check size of report and remove whitespace
pub(crate) fn check_report_reason(reason: &str, context: &LemmyContext) -> Result<(), LemmyError> {
check_slurs(reason, &context.settings().slur_regex())?;
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
let slur_regex = &local_site_to_slur_regex(local_site);
check_slurs(reason, slur_regex)?;
if reason.is_empty() {
return Err(LemmyError::from_message("report_reason_required"));
}
@ -243,8 +247,9 @@ mod tests {
use lemmy_api_common::utils::check_validator_time;
use lemmy_db_schema::{
source::{
local_user::{LocalUser, LocalUserForm},
person::{Person, PersonForm},
instance::Instance,
local_user::{LocalUser, LocalUserInsertForm},
person::{Person, PersonInsertForm},
secret::Secret,
},
traits::Crud,
@ -258,19 +263,20 @@ mod tests {
let secret = Secret::init(conn).unwrap();
let settings = &SETTINGS.to_owned();
let new_person = PersonForm {
name: "Gerry9812".into(),
public_key: Some("pubkey".to_string()),
..PersonForm::default()
};
let inserted_instance = Instance::create(conn, "my_domain.tld").unwrap();
let new_person = PersonInsertForm::builder()
.name("Gerry9812".into())
.public_key("pubkey".to_string())
.instance_id(inserted_instance.id)
.build();
let inserted_person = Person::create(conn, &new_person).unwrap();
let local_user_form = LocalUserForm {
person_id: Some(inserted_person.id),
password_encrypted: Some("123456".to_string()),
..LocalUserForm::default()
};
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.password_encrypted("123456".to_string())
.build();
let inserted_local_user = LocalUser::create(conn, &local_user_form).unwrap();

@ -7,7 +7,7 @@ use lemmy_api_common::{
use lemmy_db_schema::{
source::{
moderator::{ModAdd, ModAddForm},
person::Person,
person::{Person, PersonUpdateForm},
},
traits::Crud,
};
@ -35,7 +35,11 @@ impl Perform for AddAdmin {
let added = data.added;
let added_person_id = data.person_id;
let added_admin = blocking(context.pool(), move |conn| {
Person::add_admin(conn, added_person_id, added)
Person::update(
conn,
added_person_id,
&PersonUpdateForm::builder().admin(Some(added)).build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;

@ -11,11 +11,11 @@ use lemmy_apub::{
use lemmy_db_schema::{
source::{
moderator::{ModBan, ModBanForm},
person::Person,
site::Site,
person::{Person, PersonUpdateForm},
},
traits::Crud,
};
use lemmy_db_views::structs::SiteView;
use lemmy_db_views_actor::structs::PersonViewSafe;
use lemmy_utils::{error::LemmyError, utils::naive_from_unix, ConnectionId};
use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperation};
@ -41,10 +41,18 @@ impl Perform for BanPerson {
let banned_person_id = data.person_id;
let expires = data.expires.map(naive_from_unix);
let ban_person = move |conn: &mut _| Person::ban_person(conn, banned_person_id, ban, expires);
let person = blocking(context.pool(), ban_person)
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
let person = blocking(context.pool(), move |conn| {
Person::update(
conn,
banned_person_id,
&PersonUpdateForm::builder()
.banned(Some(ban))
.ban_expires(Some(expires))
.build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_user"))?;
// Remove their data if that's desired
let remove_data = data.remove_data.unwrap_or(false);
@ -75,7 +83,12 @@ impl Perform for BanPerson {
})
.await??;
let site = SiteOrCommunity::Site(blocking(context.pool(), Site::read_local).await??.into());
let site = SiteOrCommunity::Site(
blocking(context.pool(), SiteView::read_local)
.await??
.site
.into(),
);
// if the action affects a local user, federate to other instances
if person.local {
if ban {

@ -2,8 +2,11 @@ use crate::{captcha_as_wav_base64, Perform};
use actix_web::web::Data;
use captcha::{gen, Difficulty};
use chrono::Duration;
use lemmy_api_common::person::{CaptchaResponse, GetCaptcha, GetCaptchaResponse};
use lemmy_db_schema::utils::naive_now;
use lemmy_api_common::{
person::{CaptchaResponse, GetCaptcha, GetCaptchaResponse},
utils::blocking,
};
use lemmy_db_schema::{source::local_site::LocalSite, utils::naive_now};
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::{messages::CaptchaItem, LemmyContext};
@ -17,13 +20,13 @@ impl Perform for GetCaptcha {
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<Self::Response, LemmyError> {
let captcha_settings = &context.settings().captcha;
let local_site = blocking(context.pool(), LocalSite::read).await??;
if !captcha_settings.enabled {
if !local_site.captcha_enabled {
return Ok(GetCaptchaResponse { ok: None });
}
let captcha = gen(match captcha_settings.difficulty.as_str() {
let captcha = gen(match local_site.captcha_difficulty.as_str() {
"easy" => Difficulty::Easy,
"hard" => Difficulty::Hard,
_ => Difficulty::Medium,

@ -5,7 +5,7 @@ use lemmy_api_common::{
person::{Login, LoginResponse},
utils::{blocking, check_registration_application, check_user_valid},
};
use lemmy_db_schema::source::site::Site;
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{claims::Claims, error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -22,6 +22,8 @@ impl Perform for Login {
) -> Result<LoginResponse, LemmyError> {
let data: &Login = self;
let local_site = blocking(context.pool(), LocalSite::read).await??;
// Fetch that username / email
let username_or_email = data.username_or_email.clone();
let local_user_view = blocking(context.pool(), move |conn| {
@ -45,12 +47,11 @@ impl Perform for Login {
local_user_view.person.deleted,
)?;
let site = blocking(context.pool(), Site::read_local).await??;
if site.require_email_verification && !local_user_view.local_user.email_verified {
if local_site.require_email_verification && !local_user_view.local_user.email_verified {
return Err(LemmyError::from_message("email_not_verified"));
}
check_registration_application(&site, &local_user_view, context.pool()).await?;
check_registration_application(&local_user_view, &local_site, context.pool()).await?;
// Return the jwt
Ok(LoginResponse {

@ -4,7 +4,10 @@ use lemmy_api_common::{
person::{MarkPersonMentionAsRead, PersonMentionResponse},
utils::{blocking, get_local_user_view_from_jwt},
};
use lemmy_db_schema::{source::person_mention::PersonMention, traits::Crud};
use lemmy_db_schema::{
source::person_mention::{PersonMention, PersonMentionUpdateForm},
traits::Crud,
};
use lemmy_db_views_actor::structs::PersonMentionView;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -34,12 +37,12 @@ impl Perform for MarkPersonMentionAsRead {
}
let person_mention_id = read_person_mention.id;
let read = data.read;
let update_mention =
move |conn: &mut _| PersonMention::update_read(conn, person_mention_id, read);
blocking(context.pool(), update_mention)
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
let read = Some(data.read);
blocking(context.pool(), move |conn| {
PersonMention::update(conn, person_mention_id, &PersonMentionUpdateForm { read })
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
let person_mention_id = read_person_mention.id;
let person_id = local_user_view.person.id;

@ -4,7 +4,10 @@ use lemmy_api_common::{
person::{CommentReplyResponse, MarkCommentReplyAsRead},
utils::{blocking, get_local_user_view_from_jwt},
};
use lemmy_db_schema::{source::comment_reply::CommentReply, traits::Crud};
use lemmy_db_schema::{
source::comment_reply::{CommentReply, CommentReplyUpdateForm},
traits::Crud,
};
use lemmy_db_views_actor::structs::CommentReplyView;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -34,11 +37,12 @@ impl Perform for MarkCommentReplyAsRead {
}
let comment_reply_id = read_comment_reply.id;
let read = data.read;
let update_reply = move |conn: &mut _| CommentReply::update_read(conn, comment_reply_id, read);
blocking(context.pool(), update_reply)
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
let read = Some(data.read);
blocking(context.pool(), move |conn| {
CommentReply::update(conn, comment_reply_id, &CommentReplyUpdateForm { read })
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
let comment_reply_id = read_comment_reply.id;
let person_id = local_user_view.person.id;

@ -7,12 +7,12 @@ use lemmy_api_common::{
use lemmy_db_schema::{
source::{
actor_language::LocalUserLanguage,
local_user::{LocalUser, LocalUserForm},
person::{Person, PersonForm},
site::Site,
local_site::LocalSite,
local_user::{LocalUser, LocalUserUpdateForm},
person::{Person, PersonUpdateForm},
},
traits::Crud,
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url, naive_now},
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url},
};
use lemmy_utils::{
claims::Claims,
@ -35,6 +35,7 @@ impl Perform for SaveUserSettings {
let data: &SaveUserSettings = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
@ -56,8 +57,7 @@ impl Perform for SaveUserSettings {
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
if let Some(email) = &email {
let site_fut = blocking(context.pool(), Site::read_local);
if email.is_none() && site_fut.await??.require_email_verification {
if email.is_none() && local_site.require_email_verification {
return Err(LemmyError::from_message("email_required"));
}
}
@ -71,7 +71,7 @@ impl Perform for SaveUserSettings {
if let Some(Some(display_name)) = &display_name {
if !is_valid_display_name(
display_name.trim(),
context.settings().actor_name_max_length,
local_site.actor_name_max_length as usize,
) {
return Err(LemmyError::from_message("invalid_username"));
}
@ -87,31 +87,15 @@ impl Perform for SaveUserSettings {
let person_id = local_user_view.person.id;
let default_listing_type = data.default_listing_type;
let default_sort_type = data.default_sort_type;
let password_encrypted = local_user_view.local_user.password_encrypted;
let public_key = Some(local_user_view.person.public_key);
let person_form = PersonForm {
name: local_user_view.person.name,
avatar,
banner,
inbox_url: None,
display_name,
published: None,
updated: Some(naive_now()),
banned: None,
deleted: None,
actor_id: None,
bio,
local: None,
admin: None,
private_key: None,
public_key,
last_refreshed_at: None,
shared_inbox_url: None,
matrix_user_id,
bot_account,
ban_expires: None,
};
let person_form = PersonUpdateForm::builder()
.display_name(display_name)
.bio(bio)
.matrix_user_id(matrix_user_id)
.bot_account(bot_account)
.avatar(avatar)
.banner(banner)
.build();
blocking(context.pool(), move |conn| {
Person::update(conn, person_id, &person_form)
@ -126,24 +110,20 @@ impl Perform for SaveUserSettings {
.await??;
}
let local_user_form = LocalUserForm {
person_id: Some(person_id),
email,
password_encrypted: Some(password_encrypted),
show_nsfw: data.show_nsfw,
show_bot_accounts: data.show_bot_accounts,
show_scores: data.show_scores,
theme: data.theme.to_owned(),
default_sort_type,
default_listing_type,
interface_language: data.interface_language.to_owned(),
show_avatars: data.show_avatars,
show_read_posts: data.show_read_posts,
show_new_post_notifs: data.show_new_post_notifs,
send_notifications_to_email: data.send_notifications_to_email,
email_verified: None,
accepted_application: None,
};
let local_user_form = LocalUserUpdateForm::builder()
.email(email)
.show_avatars(data.show_avatars)
.show_read_posts(data.show_read_posts)
.show_new_post_notifs(data.show_new_post_notifs)
.send_notifications_to_email(data.send_notifications_to_email)
.show_nsfw(data.show_nsfw)
.show_bot_accounts(data.show_bot_accounts)
.show_scores(data.show_scores)
.default_sort_type(default_sort_type)
.default_listing_type(default_listing_type)
.theme(data.theme.to_owned())
.interface_language(data.interface_language.to_owned())
.build();
let local_user_res = blocking(context.pool(), move |conn| {
LocalUser::update(conn, local_user_id, &local_user_form)

@ -7,7 +7,7 @@ use lemmy_api_common::{
use lemmy_db_schema::{
source::{
email_verification::EmailVerification,
local_user::{LocalUser, LocalUserForm},
local_user::{LocalUser, LocalUserUpdateForm},
},
traits::Crud,
};
@ -31,13 +31,12 @@ impl Perform for VerifyEmail {
.await?
.map_err(|e| LemmyError::from_error_message(e, "token_not_found"))?;
let form = LocalUserForm {
let form = LocalUserUpdateForm::builder()
// necessary in case this is a new signup
email_verified: Some(true),
.email_verified(Some(true))
// necessary in case email of an existing user was changed
email: Some(Some(verification.email)),
..LocalUserForm::default()
};
.email(Some(Some(verification.email)))
.build();
let local_user_id = verification.local_user_id;
blocking(context.pool(), move |conn| {
LocalUser::update(conn, local_user_id, &form)

@ -20,7 +20,10 @@ use lemmy_apub::{
},
};
use lemmy_db_schema::{
source::post::{Post, PostLike, PostLikeForm},
source::{
local_site::LocalSite,
post::{Post, PostLike, PostLikeForm},
},
traits::{Crud, Likeable},
};
use lemmy_utils::{error::LemmyError, ConnectionId};
@ -39,9 +42,10 @@ impl Perform for CreatePostLike {
let data: &CreatePostLike = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
// Don't do a downvote if site has downvotes disabled
check_downvotes_enabled(data.score, context.pool()).await?;
check_downvotes_enabled(data.score, &local_site)?;
// Check for a community ban
let post_id = data.post_id;

@ -17,7 +17,7 @@ use lemmy_apub::{
use lemmy_db_schema::{
source::{
moderator::{ModLockPost, ModLockPostForm},
post::Post,
post::{Post, PostUpdateForm},
},
traits::Crud,
};
@ -61,7 +61,11 @@ impl Perform for LockPost {
let post_id = data.post_id;
let locked = data.locked;
let updated_post: ApubPost = blocking(context.pool(), move |conn| {
Post::update_locked(conn, post_id, locked)
Post::update(
conn,
post_id,
&PostUpdateForm::builder().locked(Some(locked)).build(),
)
})
.await??
.into();

@ -17,7 +17,7 @@ use lemmy_apub::{
use lemmy_db_schema::{
source::{
moderator::{ModStickyPost, ModStickyPostForm},
post::Post,
post::{Post, PostUpdateForm},
},
traits::Crud,
};
@ -61,7 +61,11 @@ impl Perform for StickyPost {
let post_id = data.post_id;
let stickied = data.stickied;
let updated_post: ApubPost = blocking(context.pool(), move |conn| {
Post::update_stickied(conn, post_id, stickied)
Post::update(
conn,
post_id,
&PostUpdateForm::builder().stickied(Some(stickied)).build(),
)
})
.await??
.into();

@ -7,7 +7,10 @@ use lemmy_api_common::{
};
use lemmy_apub::protocol::activities::community::report::Report;
use lemmy_db_schema::{
source::post_report::{PostReport, PostReportForm},
source::{
local_site::LocalSite,
post_report::{PostReport, PostReportForm},
},
traits::Reportable,
};
use lemmy_db_views::structs::{PostReportView, PostView};
@ -28,9 +31,10 @@ impl Perform for CreatePostReport {
let data: &CreatePostReport = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let reason = self.reason.trim();
check_report_reason(reason, context)?;
check_report_reason(reason, &local_site)?;
let person_id = local_user_view.person.id;
let post_id = data.post_id;

@ -4,7 +4,10 @@ use lemmy_api_common::{
private_message::{MarkPrivateMessageAsRead, PrivateMessageResponse},
utils::{blocking, get_local_user_view_from_jwt},
};
use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
use lemmy_db_schema::{
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
traits::Crud,
};
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperation};
@ -36,7 +39,11 @@ impl Perform for MarkPrivateMessageAsRead {
let private_message_id = data.private_message_id;
let read = data.read;
blocking(context.pool(), move |conn| {
PrivateMessage::update_read(conn, private_message_id, read)
PrivateMessage::update(
conn,
private_message_id,
&PrivateMessageUpdateForm::builder().read(Some(read)).build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;

@ -7,6 +7,7 @@ use lemmy_api_common::{
use lemmy_db_schema::{
newtypes::CommunityId,
source::{
local_site::LocalSite,
private_message::PrivateMessage,
private_message_report::{PrivateMessageReport, PrivateMessageReportForm},
},
@ -28,9 +29,10 @@ impl Perform for CreatePrivateMessageReport {
) -> Result<Self::Response, LemmyError> {
let local_user_view =
get_local_user_view_from_jwt(&self.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let reason = self.reason.trim();
check_report_reason(reason, context)?;
check_report_reason(reason, &local_site)?;
let person_id = local_user_view.person.id;
let private_message_id = self.private_message_id;

@ -2,14 +2,14 @@ use crate::Perform;
use actix_web::web::Data;
use lemmy_api_common::{
site::{GetSiteResponse, LeaveAdmin},
utils::{blocking, build_federated_instances, get_local_user_view_from_jwt, is_admin},
utils::{blocking, get_local_user_view_from_jwt, is_admin},
};
use lemmy_db_schema::{
source::{
actor_language::SiteLanguage,
language::Language,
moderator::{ModAdd, ModAddForm},
person::Person,
person::{Person, PersonUpdateForm},
},
traits::Crud,
};
@ -42,7 +42,11 @@ impl Perform for LeaveAdmin {
let person_id = local_user_view.person.id;
blocking(context.pool(), move |conn| {
Person::leave_admin(conn, person_id)
Person::update(
conn,
person_id,
&PersonUpdateForm::builder().admin(Some(false)).build(),
)
})
.await??;
@ -59,18 +63,16 @@ impl Perform for LeaveAdmin {
let site_view = blocking(context.pool(), SiteView::read_local).await??;
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
let federated_instances = build_federated_instances(context.pool(), context.settings()).await?;
let all_languages = blocking(context.pool(), Language::read_all).await??;
let discussion_languages = blocking(context.pool(), SiteLanguage::read_local).await??;
Ok(GetSiteResponse {
site_view: Some(site_view),
site_view,
admins,
online: 0,
version: version::VERSION.to_string(),
my_user: None,
federated_instances,
federated_instances: None,
all_languages,
discussion_languages,
})

@ -12,7 +12,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
newtypes::{CommunityId, PersonId},
source::site::Site,
source::local_site::LocalSite,
ModlogActionType,
};
use lemmy_db_views_moderator::structs::{
@ -52,13 +52,13 @@ impl Perform for GetModlog {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, context.pool()).await?;
check_private_instance(&local_user_view, &local_site)?;
let type_ = data.type_.unwrap_or(All);
let community_id = data.community_id;
let site = blocking(context.pool(), Site::read_local).await??;
let (local_person_id, is_admin) = match local_user_view {
Some(s) => (s.person.id, is_admin(&s).is_ok()),
None => (PersonId(-1), false),
@ -71,7 +71,7 @@ impl Perform for GetModlog {
&& is_mod_or_admin(context.pool(), local_person_id, community_id_value)
.await
.is_ok();
let hide_modlog_names = site.hide_modlog_mod_names && !is_mod_of_community && !is_admin;
let hide_modlog_names = local_site.hide_modlog_mod_names && !is_mod_of_community && !is_admin;
let mod_person_id = if hide_modlog_names {
None

@ -6,8 +6,8 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
source::{
local_user::{LocalUser, LocalUserForm},
registration_application::{RegistrationApplication, RegistrationApplicationForm},
local_user::{LocalUser, LocalUserUpdateForm},
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
},
traits::Crud,
utils::diesel_option_overwrite,
@ -36,10 +36,9 @@ impl Perform for ApproveRegistrationApplication {
// Update the registration with reason, admin_id
let deny_reason = diesel_option_overwrite(&data.deny_reason);
let app_form = RegistrationApplicationForm {
admin_id: Some(local_user_view.person.id),
let app_form = RegistrationApplicationUpdateForm {
admin_id: Some(Some(local_user_view.person.id)),
deny_reason,
..RegistrationApplicationForm::default()
};
let registration_application = blocking(context.pool(), move |conn| {
@ -48,10 +47,9 @@ impl Perform for ApproveRegistrationApplication {
.await??;
// Update the local_user row
let local_user_form = LocalUserForm {
accepted_application: Some(data.approve),
..LocalUserForm::default()
};
let local_user_form = LocalUserUpdateForm::builder()
.accepted_application(Some(data.approve))
.build();
let approved_user_id = registration_application.local_user_id;
blocking(context.pool(), move |conn| {

@ -4,7 +4,7 @@ use lemmy_api_common::{
site::{ListRegistrationApplications, ListRegistrationApplicationsResponse},
utils::{blocking, get_local_user_view_from_jwt, is_admin},
};
use lemmy_db_schema::source::site::Site;
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::registration_application_view::RegistrationApplicationQuery;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -22,14 +22,13 @@ impl Perform for ListRegistrationApplications {
let data = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
// Make sure user is an admin
is_admin(&local_user_view)?;
let unread_only = data.unread_only;
let verified_email_only = blocking(context.pool(), Site::read_local)
.await??
.require_email_verification;
let verified_email_only = local_site.require_email_verification;
let page = data.page;
let limit = data.limit;

@ -4,7 +4,7 @@ use lemmy_api_common::{
site::{GetUnreadRegistrationApplicationCount, GetUnreadRegistrationApplicationCountResponse},
utils::{blocking, get_local_user_view_from_jwt, is_admin},
};
use lemmy_db_schema::source::site::Site;
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::structs::RegistrationApplicationView;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -21,13 +21,12 @@ impl Perform for GetUnreadRegistrationApplicationCount {
let data = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
// Only let admins do this
is_admin(&local_user_view)?;
let verified_email_only = blocking(context.pool(), Site::read_local)
.await??
.require_email_verification;
let verified_email_only = local_site.require_email_verification;
let registration_applications = blocking(context.pool(), move |conn| {
RegistrationApplicationView::get_unread_count(conn, verified_email_only)

@ -6,7 +6,7 @@ use lemmy_api_common::{
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
};
use lemmy_apub::fetcher::search::{search_query_to_object_id, SearchableObjects};
use lemmy_db_schema::{newtypes::PersonId, utils::DbPool};
use lemmy_db_schema::{newtypes::PersonId, source::local_site::LocalSite, utils::DbPool};
use lemmy_db_views::structs::{CommentView, PostView};
use lemmy_db_views_actor::structs::{CommunityView, PersonViewSafe};
use lemmy_utils::{error::LemmyError, ConnectionId};
@ -25,7 +25,8 @@ impl Perform for ResolveObject {
let local_user_view =
get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
.await?;
check_private_instance(&local_user_view, context.pool()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, &local_site)?;
let res = search_query_to_object_id(&self.q, local_user_view.is_none(), context)
.await

@ -6,7 +6,7 @@ use lemmy_api_common::{
};
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
use lemmy_db_schema::{
source::community::Community,
source::{community::Community, local_site::LocalSite},
traits::DeleteableOrRemoveable,
utils::post_to_comment_sort_type,
SearchType,
@ -31,7 +31,9 @@ impl Perform for Search {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
check_private_instance(&local_user_view, context.pool()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, &local_site)?;
let person_id = local_user_view.as_ref().map(|u| u.person.id);
let local_user = local_user_view.map(|l| l.local_user);

@ -35,6 +35,7 @@ percent-encoding = { version = "2.2.0", optional = true }
encoding = { version = "0.2.33", optional = true }
reqwest-middleware = { version = "0.1.6", optional = true }
webpage = { version = "1.4.0", default-features = false, features = ["serde"], optional = true }
regex = "1.6.0"
[dev-dependencies]
actix-rt = { version = "2.7.0", default-features = false }

@ -128,6 +128,30 @@ pub struct CreateSite {
pub application_email_admins: Option<bool>,
pub auth: Sensitive<String>,
pub hide_modlog_mod_names: Option<bool>,
pub legal_information: Option<String>,
pub slur_filter_regex: Option<String>,
pub actor_name_max_length: Option<i32>,
pub rate_limit_message: Option<i32>,
pub rate_limit_message_per_second: Option<i32>,
pub rate_limit_post: Option<i32>,
pub rate_limit_post_per_second: Option<i32>,
pub rate_limit_register: Option<i32>,
pub rate_limit_register_per_second: Option<i32>,
pub rate_limit_image: Option<i32>,
pub rate_limit_image_per_second: Option<i32>,
pub rate_limit_comment: Option<i32>,
pub rate_limit_comment_per_second: Option<i32>,
pub rate_limit_search: Option<i32>,
pub rate_limit_search_per_second: Option<i32>,
pub federation_enabled: Option<bool>,
pub federation_debug: Option<bool>,
pub federation_strict_allowlist: Option<bool>,
pub federation_http_fetch_retry_limit: Option<i32>,
pub federation_worker_count: Option<i32>,
pub captcha_enabled: Option<bool>,
pub captcha_difficulty: Option<String>,
pub allowed_instances: Option<Vec<String>>,
pub blocked_instances: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
@ -151,6 +175,29 @@ pub struct EditSite {
pub application_email_admins: Option<bool>,
pub hide_modlog_mod_names: Option<bool>,
pub discussion_languages: Option<Vec<LanguageId>>,
pub slur_filter_regex: Option<String>,
pub actor_name_max_length: Option<i32>,
pub rate_limit_message: Option<i32>,
pub rate_limit_message_per_second: Option<i32>,
pub rate_limit_post: Option<i32>,
pub rate_limit_post_per_second: Option<i32>,
pub rate_limit_register: Option<i32>,
pub rate_limit_register_per_second: Option<i32>,
pub rate_limit_image: Option<i32>,
pub rate_limit_image_per_second: Option<i32>,
pub rate_limit_comment: Option<i32>,
pub rate_limit_comment_per_second: Option<i32>,
pub rate_limit_search: Option<i32>,
pub rate_limit_search_per_second: Option<i32>,
pub federation_enabled: Option<bool>,
pub federation_debug: Option<bool>,
pub federation_strict_allowlist: Option<bool>,
pub federation_http_fetch_retry_limit: Option<i32>,
pub federation_worker_count: Option<i32>,
pub captcha_enabled: Option<bool>,
pub captcha_difficulty: Option<String>,
pub allowed_instances: Option<Vec<String>>,
pub blocked_instances: Option<Vec<String>>,
pub auth: Sensitive<String>,
}
@ -166,7 +213,7 @@ pub struct SiteResponse {
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GetSiteResponse {
pub site_view: Option<SiteView>, // Because the site might not be set up yet
pub site_view: SiteView,
pub admins: Vec<PersonViewSafe>,
pub online: usize,
pub version: String,

@ -4,16 +4,18 @@ use lemmy_db_schema::{
impls::person::is_banned,
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
source::{
comment::Comment,
community::Community,
comment::{Comment, CommentUpdateForm},
community::{Community, CommunityUpdateForm},
email_verification::{EmailVerification, EmailVerificationForm},
instance::Instance,
local_site::LocalSite,
local_site_rate_limit::LocalSiteRateLimit,
password_reset_request::PasswordResetRequest,
person::Person,
person::{Person, PersonUpdateForm},
person_block::PersonBlock,
post::{Post, PostRead, PostReadForm},
registration_application::RegistrationApplication,
secret::Secret,
site::Site,
},
traits::{Crud, Readable},
utils::DbPool,
@ -32,9 +34,11 @@ use lemmy_utils::{
claims::Claims,
email::{send_email, translations::Lang},
error::LemmyError,
rate_limit::RateLimitConfig,
settings::structs::Settings,
utils::generate_random_string,
utils::{build_slur_regex, generate_random_string},
};
use regex::Regex;
use reqwest_middleware::ClientWithMiddleware;
use rosetta_i18n::{Language, LanguageId};
use std::str::FromStr;
@ -265,67 +269,38 @@ pub async fn check_person_block(
}
#[tracing::instrument(skip_all)]
pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
if score == -1 {
let site = blocking(pool, Site::read_local).await??;
if !site.enable_downvotes {
return Err(LemmyError::from_message("downvotes_disabled"));
}
pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> {
if score == -1 && !local_site.enable_downvotes {
return Err(LemmyError::from_message("downvotes_disabled"));
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn check_private_instance(
pub fn check_private_instance(
local_user_view: &Option<LocalUserView>,
pool: &DbPool,
local_site: &LocalSite,
) -> Result<(), LemmyError> {
if local_user_view.is_none() {
let site = blocking(pool, Site::read_local).await?;
// The site might not be set up yet
if let Ok(site) = site {
if site.private_instance {
return Err(LemmyError::from_message("instance_is_private"));
}
}
if local_user_view.is_none() && local_site.private_instance {
return Err(LemmyError::from_message("instance_is_private"));
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn build_federated_instances(
local_site: &LocalSite,
pool: &DbPool,
settings: &Settings,
) -> Result<Option<FederatedInstances>, LemmyError> {
let federation_config = &settings.federation;
let hostname = &settings.hostname;
let federation = federation_config.to_owned();
if federation.enabled {
let distinct_communities = blocking(pool, move |conn| {
Community::distinct_federated_communities(conn)
})
.await??;
let allowed = federation.allowed_instances;
let blocked = federation.blocked_instances;
if local_site.federation_enabled {
// TODO I hate that this requires 3 queries
let linked = blocking(pool, Instance::linked).await??;
let allowed = blocking(pool, Instance::allowlist).await??;
let blocked = blocking(pool, Instance::blocklist).await??;
let mut linked = distinct_communities
.iter()
.map(|actor_id| Ok(actor_id.host_str().unwrap_or("").to_string()))
.collect::<Result<Vec<String>, LemmyError>>()?;
if let Some(allowed) = allowed.as_ref() {
linked.extend_from_slice(allowed);
}
if let Some(blocked) = blocked.as_ref() {
linked.retain(|a| !blocked.contains(a) && !a.eq(hostname));
}
// Sort and remove dupes
linked.sort_unstable();
linked.dedup();
// These can return empty vectors, so convert them to options
let allowed = (!allowed.is_empty()).then(|| allowed);
let blocked = (!blocked.is_empty()).then(|| blocked);
Ok(Some(FederatedInstances {
linked,
@ -467,6 +442,37 @@ fn lang_str_to_lang(lang: &str) -> Lang {
})
}
pub fn local_site_rate_limit_to_rate_limit_config(
local_site_rate_limit: &LocalSiteRateLimit,
) -> RateLimitConfig {
let l = local_site_rate_limit;
RateLimitConfig {
message: l.message,
message_per_second: l.message_per_second,
post: l.post,
post_per_second: l.post_per_second,
register: l.register,
register_per_second: l.register_per_second,
image: l.image,
image_per_second: l.image_per_second,
comment: l.comment,
comment_per_second: l.comment_per_second,
search: l.search,
search_per_second: l.search_per_second,
}
}
pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option<Regex> {
build_slur_regex(local_site.slur_filter_regex.as_deref())
}
pub fn local_site_opt_to_slur_regex(local_site: &Option<LocalSite>) -> Option<Regex> {
local_site
.as_ref()
.map(local_site_to_slur_regex)
.unwrap_or(None)
}
pub fn send_application_approved_email(
user: &LocalUserView,
settings: &Settings,
@ -506,11 +512,11 @@ pub async fn send_new_applicant_email_to_admins(
}
pub async fn check_registration_application(
site: &Site,
local_user_view: &LocalUserView,
local_site: &LocalSite,
pool: &DbPool,
) -> Result<(), LemmyError> {
if site.require_application
if local_site.require_application
&& !local_user_view.local_user.accepted_application
&& !local_user_view.person.admin
{
@ -531,19 +537,13 @@ pub async fn check_registration_application(
Ok(())
}
/// TODO this check should be removed after https://github.com/LemmyNet/lemmy/issues/868 is done.
pub async fn check_private_instance_and_federation_enabled(
pool: &DbPool,
settings: &Settings,
pub fn check_private_instance_and_federation_enabled(
local_site: &LocalSite,
) -> Result<(), LemmyError> {
let site_opt = blocking(pool, Site::read_local).await?;
if let Ok(site) = site_opt {
if site.private_instance && settings.federation.enabled {
return Err(LemmyError::from_message(
"Cannot have both private instance and federation enabled.",
));
}
if local_site.private_instance && local_site.federation_enabled {
return Err(LemmyError::from_message(
"Cannot have both private instance and federation enabled.",
));
}
Ok(())
}
@ -627,7 +627,14 @@ pub async fn remove_user_data(
// Update the fields to None
blocking(pool, move |conn| {
Person::remove_avatar_and_banner(conn, banned_person_id)
Person::update(
conn,
banned_person_id,
&PersonUpdateForm::builder()
.avatar(Some(None))
.banner(Some(None))
.build(),
)
})
.await??;
@ -656,8 +663,12 @@ pub async fn remove_user_data(
for first_mod_community in banned_user_first_communities {
let community_id = first_mod_community.community.id;
blocking(pool, move |conn: &mut _| {
Community::update_removed(conn, community_id, true)
blocking(pool, move |conn| {
Community::update(
conn,
community_id,
&CommunityUpdateForm::builder().removed(Some(true)).build(),
)
})
.await??;
@ -672,7 +683,14 @@ pub async fn remove_user_data(
}
// Update the fields to None
blocking(pool, move |conn| {
Community::remove_avatar_and_banner(conn, community_id)
Community::update(
conn,
community_id,
&CommunityUpdateForm::builder()
.icon(Some(None))
.banner(Some(None))
.build(),
)
})
.await??;
}
@ -713,7 +731,11 @@ pub async fn remove_user_data_in_community(
for comment_view in &comments {
let comment_id = comment_view.comment.id;
blocking(pool, move |conn| {
Comment::update_removed(conn, comment_id, true)
Comment::update(
conn,
comment_id,
&CommentUpdateForm::builder().removed(Some(true)).build(),
)
})
.await??;
}
@ -761,15 +783,11 @@ pub async fn delete_user_account(
Ok(())
}
pub async fn listing_type_with_site_default(
pub fn listing_type_with_site_default(
listing_type: Option<ListingType>,
pool: &DbPool,
local_site: &LocalSite,
) -> Result<ListingType, LemmyError> {
Ok(match listing_type {
Some(l) => l,
None => {
let site = blocking(pool, Site::read_local).await??;
ListingType::from_str(&site.default_post_listing_type)?
}
})
Ok(listing_type.unwrap_or(ListingType::from_str(
&local_site.default_post_listing_type,
)?))
}

@ -9,6 +9,7 @@ use lemmy_api_common::{
check_post_deleted_or_removed,
get_local_user_view_from_jwt,
get_post,
local_site_to_slur_regex,
},
};
use lemmy_apub::{
@ -20,9 +21,10 @@ use lemmy_apub::{
use lemmy_db_schema::{
source::{
actor_language::CommunityLanguage,
comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
comment_reply::CommentReply,
person_mention::PersonMention,
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm, CommentUpdateForm},
comment_reply::{CommentReply, CommentReplyUpdateForm},
local_site::LocalSite,
person_mention::{PersonMention, PersonMentionUpdateForm},
},
traits::{Crud, Likeable},
};
@ -50,9 +52,12 @@ impl PerformCrud for CreateComment {
let data: &CreateComment = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let content_slurs_removed =
remove_slurs(&data.content.to_owned(), &context.settings().slur_regex());
let content_slurs_removed = remove_slurs(
&data.content.to_owned(),
&local_site_to_slur_regex(&local_site),
);
// Check for a community ban
let post_id = data.post_id;
@ -97,13 +102,12 @@ impl PerformCrud for CreateComment {
})
.await??;
let comment_form = CommentForm {
content: content_slurs_removed,
post_id: data.post_id,
creator_id: local_user_view.person.id,
language_id: Some(language_id),
..CommentForm::default()
};
let comment_form = CommentInsertForm::builder()
.content(content_slurs_removed.to_owned())
.post_id(data.post_id)
.creator_id(local_user_view.person.id)
.language_id(Some(language_id))
.build();
// Create the comment
let comment_form2 = comment_form.clone();
@ -125,14 +129,18 @@ impl PerformCrud for CreateComment {
&inserted_comment_id.to_string(),
&protocol_and_hostname,
)?;
Ok(Comment::update_ap_id(conn, inserted_comment_id, apub_id)?)
Ok(Comment::update(
conn,
inserted_comment_id,
&CommentUpdateForm::builder().ap_id(Some(apub_id)).build(),
)?)
})
.await?
.map_err(|e| e.with_message("couldnt_create_comment"))?;
// Scan the comment for user mentions, add those rows
let post_id = post.id;
let mentions = scrape_text_for_mentions(&comment_form.content);
let mentions = scrape_text_for_mentions(&content_slurs_removed);
let recipient_ids = send_local_notifs(
mentions,
&updated_comment,
@ -175,7 +183,7 @@ impl PerformCrud for CreateComment {
.await?;
if let Ok(reply) = comment_reply {
blocking(context.pool(), move |conn| {
CommentReply::update_read(conn, reply.id, true)
CommentReply::update(conn, reply.id, &CommentReplyUpdateForm { read: Some(true) })
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_replies"))?;
@ -189,7 +197,11 @@ impl PerformCrud for CreateComment {
.await?;
if let Ok(mention) = person_mention {
blocking(context.pool(), move |conn| {
PersonMention::update_read(conn, mention.id, true)
PersonMention::update(
conn,
mention.id,
&PersonMentionUpdateForm { read: Some(true) },
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_person_mentions"))?;

@ -6,7 +6,11 @@ use lemmy_api_common::{
};
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::{
source::{comment::Comment, community::Community, post::Post},
source::{
comment::{Comment, CommentUpdateForm},
community::Community,
post::Post,
},
traits::Crud,
};
use lemmy_db_views::structs::CommentView;
@ -57,7 +61,11 @@ impl PerformCrud for DeleteComment {
// Do the delete
let deleted = data.deleted;
let updated_comment = blocking(context.pool(), move |conn| {
Comment::update_deleted(conn, comment_id, deleted)
Comment::update(
conn,
comment_id,
&CommentUpdateForm::builder().deleted(Some(deleted)).build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;

@ -11,7 +11,7 @@ use lemmy_api_common::{
};
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
use lemmy_db_schema::{
source::{comment::Comment, community::Community},
source::{comment::Comment, community::Community, local_site::LocalSite},
traits::{Crud, DeleteableOrRemoveable},
};
use lemmy_db_views::comment_view::CommentQuery;
@ -32,10 +32,11 @@ impl PerformCrud for GetComments {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
check_private_instance(&local_user_view, context.pool()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, &local_site)?;
let community_id = data.community_id;
let listing_type = listing_type_with_site_default(data.type_, context.pool()).await?;
let listing_type = listing_type_with_site_default(data.type_, &local_site)?;
let community_actor_id = if let Some(name) = &data.community_name {
resolve_actor_identifier::<ApubCommunity, Community>(name, context, true)

@ -4,6 +4,7 @@ use lemmy_api_common::{
comment::{CommentResponse, GetComment},
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::structs::CommentView;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -22,8 +23,9 @@ impl PerformCrud for GetComment {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, context.pool()).await?;
check_private_instance(&local_user_view, &local_site)?;
let person_id = local_user_view.map(|u| u.person.id);
let id = data.id;

@ -7,7 +7,7 @@ use lemmy_api_common::{
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::{
source::{
comment::Comment,
comment::{Comment, CommentUpdateForm},
community::Community,
moderator::{ModRemoveComment, ModRemoveCommentForm},
post::Post,
@ -60,7 +60,11 @@ impl PerformCrud for RemoveComment {
// Do the remove
let removed = data.removed;
let updated_comment = blocking(context.pool(), move |conn| {
Comment::update_removed(conn, comment_id, removed)
Comment::update(
conn,
comment_id,
&CommentUpdateForm::builder().removed(Some(removed)).build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;

@ -8,6 +8,7 @@ use lemmy_api_common::{
check_post_deleted_or_removed,
get_local_user_view_from_jwt,
is_mod_or_admin,
local_site_to_slur_regex,
},
};
use lemmy_apub::protocol::activities::{
@ -17,7 +18,8 @@ use lemmy_apub::protocol::activities::{
use lemmy_db_schema::{
source::{
actor_language::CommunityLanguage,
comment::{Comment, CommentForm},
comment::{Comment, CommentUpdateForm},
local_site::LocalSite,
},
traits::Crud,
};
@ -48,6 +50,7 @@ impl PerformCrud for EditComment {
let data: &EditComment = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let comment_id = data.comment_id;
let orig_comment = blocking(context.pool(), move |conn| {
@ -90,16 +93,13 @@ impl PerformCrud for EditComment {
let content_slurs_removed = data
.content
.as_ref()
.map(|c| remove_slurs(c, &context.settings().slur_regex()));
.map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site)));
let comment_id = data.comment_id;
let form = CommentForm {
creator_id: orig_comment.comment.creator_id,
post_id: orig_comment.comment.post_id,
content: content_slurs_removed.unwrap_or(orig_comment.comment.content),
distinguished: data.distinguished,
language_id: data.language_id,
..Default::default()
};
let form = CommentUpdateForm::builder()
.content(content_slurs_removed)
.distinguished(data.distinguished)
.language_id(data.language_id)
.build();
let updated_comment = blocking(context.pool(), move |conn| {
Comment::update(conn, comment_id, &form)
})

@ -3,7 +3,7 @@ use activitypub_federation::core::{object_id::ObjectId, signatures::generate_act
use actix_web::web::Data;
use lemmy_api_common::{
community::{CommunityResponse, CreateCommunity},
utils::{blocking, get_local_user_view_from_jwt, is_admin},
utils::{blocking, get_local_user_view_from_jwt, is_admin, local_site_to_slur_regex},
};
use lemmy_apub::{
generate_followers_url,
@ -14,20 +14,18 @@ use lemmy_apub::{
EndpointType,
};
use lemmy_db_schema::{
source::{
community::{
Community,
CommunityFollower,
CommunityFollowerForm,
CommunityForm,
CommunityModerator,
CommunityModeratorForm,
},
site::Site,
source::community::{
Community,
CommunityFollower,
CommunityFollowerForm,
CommunityInsertForm,
CommunityModerator,
CommunityModeratorForm,
},
traits::{Crud, Followable, Joinable},
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url},
utils::diesel_option_overwrite_to_url_create,
};
use lemmy_db_views::structs::SiteView;
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::{
error::LemmyError,
@ -49,8 +47,9 @@ impl PerformCrud for CreateCommunity {
let data: &CreateCommunity = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let site_view = blocking(context.pool(), SiteView::read_local).await??;
let local_site = site_view.local_site;
let local_site = blocking(context.pool(), Site::read_local).await??;
if local_site.community_creation_admin_only && is_admin(&local_user_view).is_err() {
return Err(LemmyError::from_message(
"only_admins_can_create_communities",
@ -58,15 +57,15 @@ impl PerformCrud for CreateCommunity {
}
// Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let description = diesel_option_overwrite(&data.description);
let icon = diesel_option_overwrite_to_url_create(&data.icon)?;
let banner = diesel_option_overwrite_to_url_create(&data.banner)?;
check_slurs(&data.name, &context.settings().slur_regex())?;
check_slurs(&data.title, &context.settings().slur_regex())?;
check_slurs_opt(&data.description, &context.settings().slur_regex())?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.name, &slur_regex)?;
check_slurs(&data.title, &slur_regex)?;
check_slurs_opt(&data.description, &slur_regex)?;
if !is_valid_actor_name(&data.name, context.settings().actor_name_max_length) {
if !is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize) {
return Err(LemmyError::from_message("invalid_community_name"));
}
@ -85,22 +84,22 @@ impl PerformCrud for CreateCommunity {
// When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?;
let community_form = CommunityForm {
name: data.name.to_owned(),
title: data.title.to_owned(),
description,
icon,
banner,
nsfw: data.nsfw,
actor_id: Some(community_actor_id.to_owned()),
private_key: Some(Some(keypair.private_key)),
public_key: Some(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(Some(generate_shared_inbox_url(&community_actor_id)?)),
posting_restricted_to_mods: data.posting_restricted_to_mods,
..CommunityForm::default()
};
let community_form = CommunityInsertForm::builder()
.name(data.name.to_owned())
.title(data.title.to_owned())
.description(data.description.to_owned())
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.actor_id(Some(community_actor_id.to_owned()))
.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 = blocking(context.pool(), move |conn| {
Community::create(conn, &community_form)

@ -5,7 +5,10 @@ use lemmy_api_common::{
utils::{blocking, get_local_user_view_from_jwt},
};
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::source::community::Community;
use lemmy_db_schema::{
source::community::{Community, CommunityUpdateForm},
traits::Crud,
};
use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
@ -40,7 +43,13 @@ impl PerformCrud for DeleteCommunity {
let community_id = data.community_id;
let deleted = data.deleted;
let updated_community = blocking(context.pool(), move |conn| {
Community::update_deleted(conn, community_id, deleted)
Community::update(
conn,
community_id,
&CommunityUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?;

@ -4,7 +4,7 @@ use lemmy_api_common::{
community::{ListCommunities, ListCommunitiesResponse},
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
};
use lemmy_db_schema::traits::DeleteableOrRemoveable;
use lemmy_db_schema::{source::local_site::LocalSite, traits::DeleteableOrRemoveable};
use lemmy_db_views_actor::community_view::CommunityQuery;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -23,8 +23,9 @@ impl PerformCrud for ListCommunities {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, context.pool()).await?;
check_private_instance(&local_user_view, &local_site)?;
let person_id = local_user_view.to_owned().map(|l| l.person.id);

@ -10,7 +10,12 @@ use lemmy_apub::{
};
use lemmy_db_schema::{
impls::actor_language::default_post_language,
source::{actor_language::CommunityLanguage, community::Community, site::Site},
source::{
actor_language::CommunityLanguage,
community::Community,
local_site::LocalSite,
site::Site,
},
traits::DeleteableOrRemoveable,
};
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
@ -31,12 +36,13 @@ impl PerformCrud for GetCommunity {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
if data.name.is_none() && data.id.is_none() {
return Err(LemmyError::from_message("no_id_given"));
}
check_private_instance(&local_user_view, context.pool()).await?;
check_private_instance(&local_user_view, &local_site)?;
let person_id = local_user_view.as_ref().map(|u| u.person.id);

@ -7,7 +7,7 @@ use lemmy_api_common::{
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::{
source::{
community::Community,
community::{Community, CommunityUpdateForm},
moderator::{ModRemoveCommunity, ModRemoveCommunityForm},
},
traits::Crud,
@ -36,7 +36,13 @@ impl PerformCrud for RemoveCommunity {
let community_id = data.community_id;
let removed = data.removed;
let updated_community = blocking(context.pool(), move |conn| {
Community::update_removed(conn, community_id, removed)
Community::update(
conn,
community_id,
&CommunityUpdateForm::builder()
.removed(Some(removed))
.build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_community"))?;

@ -2,17 +2,18 @@ use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{
community::{CommunityResponse, EditCommunity},
utils::{blocking, get_local_user_view_from_jwt},
utils::{blocking, get_local_user_view_from_jwt, local_site_to_slur_regex},
};
use lemmy_apub::protocol::activities::community::update::UpdateCommunity;
use lemmy_db_schema::{
newtypes::{LanguageId, PersonId},
source::{
actor_language::{CommunityLanguage, SiteLanguage},
community::{Community, CommunityForm},
community::{Community, CommunityUpdateForm},
local_site::LocalSite,
},
traits::Crud,
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url, naive_now},
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url},
};
use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::{error::LemmyError, utils::check_slurs_opt, ConnectionId};
@ -31,13 +32,15 @@ impl PerformCrud for EditCommunity {
let data: &EditCommunity = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let description = diesel_option_overwrite(&data.description);
check_slurs_opt(&data.title, &context.settings().slur_regex())?;
check_slurs_opt(&data.description, &context.settings().slur_regex())?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs_opt(&data.title, &slur_regex)?;
check_slurs_opt(&data.description, &slur_regex)?;
// Verify its a mod (only mods can edit it)
let community_id = data.community_id;
@ -66,22 +69,14 @@ impl PerformCrud for EditCommunity {
.await??;
}
let read_community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let community_form = CommunityForm {
name: read_community.name,
title: data.title.to_owned().unwrap_or(read_community.title),
description,
icon,
banner,
nsfw: data.nsfw,
posting_restricted_to_mods: data.posting_restricted_to_mods,
updated: Some(naive_now()),
..CommunityForm::default()
};
let community_form = CommunityUpdateForm::builder()
.title(data.title.to_owned())
.description(description)
.icon(icon)
.banner(banner)
.nsfw(data.nsfw)
.posting_restricted_to_mods(data.posting_restricted_to_mods)
.build();
let community_id = data.community_id;
let updated_community = blocking(context.pool(), move |conn| {

@ -9,6 +9,7 @@ use lemmy_api_common::{
check_community_deleted_or_removed,
get_local_user_view_from_jwt,
honeypot_check,
local_site_to_slur_regex,
mark_post_as_read,
},
};
@ -23,10 +24,10 @@ use lemmy_db_schema::{
source::{
actor_language::CommunityLanguage,
community::Community,
post::{Post, PostForm, PostLike, PostLikeForm},
local_site::LocalSite,
post::{Post, PostInsertForm, PostLike, PostLikeForm, PostUpdateForm},
},
traits::{Crud, Likeable},
utils::diesel_option_overwrite,
};
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::{
@ -52,15 +53,15 @@ impl PerformCrud for CreatePost {
let data: &CreatePost = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let slur_regex = &context.settings().slur_regex();
check_slurs(&data.name, slur_regex)?;
check_slurs_opt(&data.body, slur_regex)?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.name, &slur_regex)?;
check_slurs_opt(&data.body, &slur_regex)?;
honeypot_check(&data.honeypot)?;
let data_url = data.url.as_ref();
let url = Some(data_url.map(clean_url_params).map(Into::into)); // TODO no good way to handle a "clear"
let body = diesel_option_overwrite(&data.body);
let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
if !is_valid_post_title(&data.name) {
return Err(LemmyError::from_message("invalid_post_title"));
@ -89,7 +90,7 @@ impl PerformCrud for CreatePost {
let (metadata_res, thumbnail_url) =
fetch_site_data(context.client(), context.settings(), data_url).await;
let (embed_title, embed_description, embed_video_url) = metadata_res
.map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
.map(|u| (u.title, u.description, u.embed_video_url))
.unwrap_or_default();
let language_id = match data.language_id {
@ -106,20 +107,19 @@ impl PerformCrud for CreatePost {
})
.await??;
let post_form = PostForm {
name: data.name.trim().to_owned(),
url,
body,
community_id: data.community_id,
creator_id: local_user_view.person.id,
nsfw: data.nsfw,
embed_title,
embed_description,
embed_video_url,
language_id,
thumbnail_url: Some(thumbnail_url),
..PostForm::default()
};
let post_form = PostInsertForm::builder()
.name(data.name.trim().to_owned())
.url(url)
.body(data.body.to_owned())
.community_id(data.community_id)
.creator_id(local_user_view.person.id)
.nsfw(data.nsfw)
.embed_title(embed_title)
.embed_description(embed_description)
.embed_video_url(embed_video_url)
.language_id(language_id)
.thumbnail_url(thumbnail_url)
.build();
let inserted_post =
match blocking(context.pool(), move |conn| Post::create(conn, &post_form)).await? {
@ -143,7 +143,11 @@ impl PerformCrud for CreatePost {
&inserted_post_id.to_string(),
&protocol_and_hostname,
)?;
Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?)
Ok(Post::update(
conn,
inserted_post_id,
&PostUpdateForm::builder().ap_id(Some(apub_id)).build(),
)?)
})
.await?
.map_err(|e| e.with_message("couldnt_create_post"))?;

@ -11,7 +11,10 @@ use lemmy_api_common::{
};
use lemmy_apub::activities::deletion::{send_apub_delete_in_community, DeletableObjects};
use lemmy_db_schema::{
source::{community::Community, post::Post},
source::{
community::Community,
post::{Post, PostUpdateForm},
},
traits::Crud,
};
use lemmy_utils::{error::LemmyError, ConnectionId};
@ -56,7 +59,11 @@ impl PerformCrud for DeletePost {
let post_id = data.post_id;
let deleted = data.deleted;
let updated_post = blocking(context.pool(), move |conn| {
Post::update_deleted(conn, post_id, deleted)
Post::update(
conn,
post_id,
&PostUpdateForm::builder().deleted(Some(deleted)).build(),
)
})
.await??;

@ -10,7 +10,10 @@ use lemmy_api_common::{
},
};
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity};
use lemmy_db_schema::{source::community::Community, traits::DeleteableOrRemoveable};
use lemmy_db_schema::{
source::{community::Community, local_site::LocalSite},
traits::DeleteableOrRemoveable,
};
use lemmy_db_views::post_view::PostQuery;
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::LemmyContext;
@ -29,13 +32,14 @@ impl PerformCrud for GetPosts {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, context.pool()).await?;
check_private_instance(&local_user_view, &local_site)?;
let is_logged_in = local_user_view.is_some();
let sort = data.sort;
let listing_type = listing_type_with_site_default(data.type_, context.pool()).await?;
let listing_type = listing_type_with_site_default(data.type_, &local_site)?;
let page = data.page;
let limit = data.limit;

@ -6,7 +6,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
source::comment::Comment,
source::{comment::Comment, local_site::LocalSite},
traits::{Crud, DeleteableOrRemoveable},
};
use lemmy_db_views::structs::PostView;
@ -28,8 +28,9 @@ impl PerformCrud for GetPost {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, context.pool()).await?;
check_private_instance(&local_user_view, &local_site)?;
let person_id = local_user_view.map(|u| u.person.id);

@ -9,7 +9,7 @@ use lemmy_db_schema::{
source::{
community::Community,
moderator::{ModRemovePost, ModRemovePostForm},
post::Post,
post::{Post, PostUpdateForm},
},
traits::Crud,
};
@ -52,7 +52,11 @@ impl PerformCrud for RemovePost {
let post_id = data.post_id;
let removed = data.removed;
let updated_post = blocking(context.pool(), move |conn| {
Post::update_removed(conn, post_id, removed)
Post::update(
conn,
post_id,
&PostUpdateForm::builder().removed(Some(removed)).build(),
)
})
.await??;

@ -8,6 +8,7 @@ use lemmy_api_common::{
check_community_ban,
check_community_deleted_or_removed,
get_local_user_view_from_jwt,
local_site_to_slur_regex,
},
};
use lemmy_apub::protocol::activities::{
@ -17,10 +18,11 @@ use lemmy_apub::protocol::activities::{
use lemmy_db_schema::{
source::{
actor_language::CommunityLanguage,
post::{Post, PostForm},
local_site::LocalSite,
post::{Post, PostUpdateForm},
},
traits::Crud,
utils::{diesel_option_overwrite, naive_now},
utils::diesel_option_overwrite,
};
use lemmy_utils::{
error::LemmyError,
@ -42,6 +44,7 @@ impl PerformCrud for EditPost {
let data: &EditPost = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let data_url = data.url.as_ref();
@ -50,9 +53,9 @@ impl PerformCrud for EditPost {
let url = Some(data_url.map(clean_url_params).map(Into::into));
let body = diesel_option_overwrite(&data.body);
let slur_regex = &context.settings().slur_regex();
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 {
if !is_valid_post_title(name) {
@ -90,21 +93,17 @@ impl PerformCrud for EditPost {
})
.await??;
let post_form = PostForm {
creator_id: orig_post.creator_id.to_owned(),
community_id: orig_post.community_id,
name: data.name.to_owned().unwrap_or(orig_post.name),
url,
body,
nsfw: data.nsfw,
updated: Some(naive_now()),
embed_title,
embed_description,
embed_video_url,
language_id: data.language_id,
thumbnail_url: Some(thumbnail_url),
..PostForm::default()
};
let post_form = PostUpdateForm::builder()
.name(data.name.to_owned())
.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))
.build();
let post_id = data.post_id;
let res = blocking(context.pool(), move |conn| {

@ -7,6 +7,7 @@ use lemmy_api_common::{
check_person_block,
get_interface_language,
get_local_user_view_from_jwt,
local_site_to_slur_regex,
send_email_to_user,
},
};
@ -19,7 +20,10 @@ use lemmy_apub::{
EndpointType,
};
use lemmy_db_schema::{
source::private_message::{PrivateMessage, PrivateMessageForm},
source::{
local_site::LocalSite,
private_message::{PrivateMessage, PrivateMessageInsertForm, PrivateMessageUpdateForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
@ -39,18 +43,20 @@ impl PerformCrud for CreatePrivateMessage {
let data: &CreatePrivateMessage = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
let content_slurs_removed =
remove_slurs(&data.content.to_owned(), &context.settings().slur_regex());
let content_slurs_removed = remove_slurs(
&data.content.to_owned(),
&local_site_to_slur_regex(&local_site),
);
check_person_block(local_user_view.person.id, data.recipient_id, context.pool()).await?;
let private_message_form = PrivateMessageForm {
content: content_slurs_removed.to_owned(),
creator_id: local_user_view.person.id,
recipient_id: data.recipient_id,
..PrivateMessageForm::default()
};
let private_message_form = PrivateMessageInsertForm::builder()
.content(content_slurs_removed.to_owned())
.creator_id(local_user_view.person.id)
.recipient_id(data.recipient_id)
.build();
let inserted_private_message = match blocking(context.pool(), move |conn| {
PrivateMessage::create(conn, &private_message_form)
@ -76,10 +82,12 @@ impl PerformCrud for CreatePrivateMessage {
&inserted_private_message_id.to_string(),
&protocol_and_hostname,
)?;
Ok(PrivateMessage::update_ap_id(
Ok(PrivateMessage::update(
conn,
inserted_private_message_id,
apub_id,
inserted_private_message.id,
&PrivateMessageUpdateForm::builder()
.ap_id(Some(apub_id))
.build(),
)?)
},
)

@ -5,7 +5,10 @@ use lemmy_api_common::{
utils::{blocking, get_local_user_view_from_jwt},
};
use lemmy_apub::activities::deletion::send_apub_delete_private_message;
use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
use lemmy_db_schema::{
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
traits::Crud,
};
use lemmy_utils::{error::LemmyError, ConnectionId};
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
@ -37,7 +40,13 @@ impl PerformCrud for DeletePrivateMessage {
let private_message_id = data.private_message_id;
let deleted = data.deleted;
let updated_private_message = blocking(context.pool(), move |conn| {
PrivateMessage::update_deleted(conn, private_message_id, deleted)
PrivateMessage::update(
conn,
private_message_id,
&PrivateMessageUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;

@ -2,13 +2,20 @@ use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{
private_message::{EditPrivateMessage, PrivateMessageResponse},
utils::{blocking, get_local_user_view_from_jwt},
utils::{blocking, get_local_user_view_from_jwt, local_site_to_slur_regex},
};
use lemmy_apub::protocol::activities::{
create_or_update::private_message::CreateOrUpdatePrivateMessage,
CreateOrUpdateType,
};
use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
use lemmy_db_schema::{
source::{
local_site::LocalSite,
private_message::{PrivateMessage, PrivateMessageUpdateForm},
},
traits::Crud,
utils::naive_now,
};
use lemmy_utils::{error::LemmyError, utils::remove_slurs, ConnectionId};
use lemmy_websocket::{send::send_pm_ws_message, LemmyContext, UserOperationCrud};
@ -25,6 +32,7 @@ impl PerformCrud for EditPrivateMessage {
let data: &EditPrivateMessage = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
// Checking permissions
let private_message_id = data.private_message_id;
@ -37,10 +45,17 @@ impl PerformCrud for EditPrivateMessage {
}
// Doing the update
let content_slurs_removed = remove_slurs(&data.content, &context.settings().slur_regex());
let content_slurs_removed = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site));
let private_message_id = data.private_message_id;
let updated_private_message = blocking(context.pool(), move |conn| {
PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
PrivateMessage::update(
conn,
private_message_id,
&PrivateMessageUpdateForm::builder()
.content(Some(content_slurs_removed))
.updated(Some(Some(naive_now())))
.build(),
)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_private_message"))?;

@ -3,19 +3,29 @@ use activitypub_federation::core::signatures::generate_actor_keypair;
use actix_web::web::Data;
use lemmy_api_common::{
site::{CreateSite, SiteResponse},
utils::{blocking, get_local_user_view_from_jwt, is_admin, site_description_length_check},
utils::{
blocking,
get_local_user_view_from_jwt,
is_admin,
local_site_to_slur_regex,
site_description_length_check,
},
};
use lemmy_apub::generate_site_inbox_url;
use lemmy_db_schema::{
newtypes::DbUrl,
source::site::{Site, SiteForm},
source::{
local_site::{LocalSite, LocalSiteUpdateForm},
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm},
site::{Site, SiteUpdateForm},
},
traits::Crud,
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url, naive_now},
};
use lemmy_db_views::structs::SiteView;
use lemmy_utils::{
error::LemmyError,
utils::{check_slurs, check_slurs_opt},
utils::{check_application_question, check_slurs, check_slurs_opt},
ConnectionId,
};
use lemmy_websocket::LemmyContext;
@ -33,8 +43,9 @@ impl PerformCrud for CreateSite {
) -> Result<SiteResponse, LemmyError> {
let data: &CreateSite = self;
let read_site = Site::read_local;
if blocking(context.pool(), read_site).await?.is_ok() {
let local_site = blocking(context.pool(), LocalSite::read).await??;
if local_site.site_setup {
return Err(LemmyError::from_message("site_already_exists"));
};
@ -46,8 +57,9 @@ impl PerformCrud for CreateSite {
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
check_slurs(&data.name, &context.settings().slur_regex())?;
check_slurs_opt(&data.description, &context.settings().slur_regex())?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.name, &slur_regex)?;
check_slurs_opt(&data.description, &slur_regex)?;
// Make sure user is an admin
is_admin(&local_user_view)?;
@ -56,35 +68,82 @@ impl PerformCrud for CreateSite {
site_description_length_check(desc)?;
}
let application_question = diesel_option_overwrite(&data.application_question);
check_application_question(&application_question, &data.require_application)?;
let actor_id: DbUrl = Url::parse(&context.settings().get_protocol_and_hostname())?.into();
let inbox_url = Some(generate_site_inbox_url(&actor_id)?);
let keypair = generate_actor_keypair()?;
let site_form = SiteForm {
name: data.name.to_owned(),
sidebar,
description,
icon,
banner,
enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration,
enable_nsfw: data.enable_nsfw,
community_creation_admin_only: data.community_creation_admin_only,
actor_id: Some(actor_id),
last_refreshed_at: Some(naive_now()),
inbox_url,
private_key: Some(Some(keypair.private_key)),
public_key: Some(keypair.public_key),
default_theme: data.default_theme.clone(),
default_post_listing_type: data.default_post_listing_type.clone(),
application_email_admins: data.application_email_admins,
hide_modlog_mod_names: data.hide_modlog_mod_names,
..SiteForm::default()
};
let site_form = SiteUpdateForm::builder()
.name(Some(data.name.to_owned()))
.sidebar(sidebar)
.description(description)
.icon(icon)
.banner(banner)
.actor_id(Some(actor_id))
.last_refreshed_at(Some(naive_now()))
.inbox_url(inbox_url)
.private_key(Some(Some(keypair.private_key)))
.public_key(Some(keypair.public_key))
.build();
let site_id = local_site.site_id;
blocking(context.pool(), move |conn| {
Site::update(conn, site_id, &site_form)
})
.await??;
let local_site_form = LocalSiteUpdateForm::builder()
// Set the site setup to true
.site_setup(Some(true))
.enable_downvotes(data.enable_downvotes)
.open_registration(data.open_registration)
.enable_nsfw(data.enable_nsfw)
.community_creation_admin_only(data.community_creation_admin_only)
.require_email_verification(data.require_email_verification)
.require_application(data.require_application)
.application_question(application_question)
.private_instance(data.private_instance)
.default_theme(data.default_theme.clone())
.default_post_listing_type(data.default_post_listing_type.clone())
.legal_information(diesel_option_overwrite(&data.legal_information))
.application_email_admins(data.application_email_admins)
.hide_modlog_mod_names(data.hide_modlog_mod_names)
.updated(Some(Some(naive_now())))
.slur_filter_regex(diesel_option_overwrite(&data.slur_filter_regex))
.actor_name_max_length(data.actor_name_max_length)
.federation_enabled(data.federation_enabled)
.federation_debug(data.federation_debug)
.federation_strict_allowlist(data.federation_strict_allowlist)
.federation_http_fetch_retry_limit(data.federation_http_fetch_retry_limit)
.federation_worker_count(data.federation_worker_count)
.captcha_enabled(data.captcha_enabled)
.captcha_difficulty(data.captcha_difficulty.to_owned())
.build();
blocking(context.pool(), move |conn| {
LocalSite::update(conn, &local_site_form)
})
.await??;
let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm::builder()
.message(data.rate_limit_message)
.message_per_second(data.rate_limit_message_per_second)
.post(data.rate_limit_post)
.post_per_second(data.rate_limit_post_per_second)
.register(data.rate_limit_register)
.register_per_second(data.rate_limit_register_per_second)
.image(data.rate_limit_image)
.image_per_second(data.rate_limit_image_per_second)
.comment(data.rate_limit_comment)
.comment_per_second(data.rate_limit_comment_per_second)
.search(data.rate_limit_search)
.search_per_second(data.rate_limit_search_per_second)
.build();
let create_site = move |conn: &mut _| Site::create(conn, &site_form);
blocking(context.pool(), create_site)
.await?
.map_err(|e| LemmyError::from_error_message(e, "site_already_exists"))?;
blocking(context.pool(), move |conn| {
LocalSiteRateLimit::update(conn, &local_site_rate_limit_form)
})
.await??;
let site_view = blocking(context.pool(), SiteView::read_local).await??;

@ -1,8 +1,7 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{
person::Register,
site::{CreateSite, GetSite, GetSiteResponse, MyUserInfo},
site::{GetSite, GetSiteResponse, MyUserInfo},
utils::{blocking, build_federated_instances, get_local_user_settings_view_from_jwt_opt},
};
use lemmy_db_schema::source::{actor_language::SiteLanguage, language::Language};
@ -16,56 +15,20 @@ use lemmy_db_views_actor::structs::{
};
use lemmy_utils::{error::LemmyError, version, ConnectionId};
use lemmy_websocket::{messages::GetUsersOnline, LemmyContext};
use tracing::info;
#[async_trait::async_trait(?Send)]
impl PerformCrud for GetSite {
type Response = GetSiteResponse;
#[tracing::instrument(skip(context, websocket_id))]
#[tracing::instrument(skip(context, _websocket_id))]
async fn perform(
&self,
context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>,
_websocket_id: Option<ConnectionId>,
) -> Result<GetSiteResponse, LemmyError> {
let data: &GetSite = self;
let site_view = match blocking(context.pool(), SiteView::read_local).await? {
Ok(site_view) => Some(site_view),
// If the site isn't created yet, check the setup
Err(_) => {
if let Some(setup) = context.settings().setup.as_ref() {
let register = Register {
username: setup.admin_username.to_owned(),
email: setup.admin_email.clone().map(|s| s.into()),
password: setup.admin_password.clone().into(),
password_verify: setup.admin_password.clone().into(),
show_nsfw: true,
captcha_uuid: None,
captcha_answer: None,
honeypot: None,
answer: None,
};
let admin_jwt = register
.perform(context, websocket_id)
.await?
.jwt
.expect("jwt is returned from registration on newly created site");
info!("Admin {} created", setup.admin_username);
let create_site = CreateSite {
name: setup.site_name.to_owned(),
auth: admin_jwt,
..CreateSite::default()
};
create_site.perform(context, websocket_id).await?;
info!("Site {} created", setup.site_name);
Some(blocking(context.pool(), SiteView::read_local).await??)
} else {
None
}
}
};
let site_view = blocking(context.pool(), SiteView::read_local).await??;
let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
@ -130,7 +93,8 @@ impl PerformCrud for GetSite {
None
};
let federated_instances = build_federated_instances(context.pool(), context.settings()).await?;
let federated_instances =
build_federated_instances(&site_view.local_site, context.pool()).await?;
let all_languages = blocking(context.pool(), Language::read_all).await??;
let discussion_languages = blocking(context.pool(), SiteLanguage::read_local).await??;

@ -2,22 +2,36 @@ use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{
site::{EditSite, SiteResponse},
utils::{blocking, get_local_user_view_from_jwt, is_admin, site_description_length_check},
utils::{
blocking,
get_local_user_view_from_jwt,
is_admin,
local_site_to_slur_regex,
site_description_length_check,
},
};
use lemmy_db_schema::{
source::{
actor_language::SiteLanguage,
federation_allowlist::FederationAllowList,
federation_blocklist::FederationBlockList,
local_site::{LocalSite, LocalSiteUpdateForm},
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm},
local_user::LocalUser,
site::{Site, SiteForm},
site::{Site, SiteUpdateForm},
},
traits::Crud,
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url, naive_now},
ListingType,
};
use lemmy_db_views::structs::SiteView;
use lemmy_utils::{error::LemmyError, utils::check_slurs_opt, ConnectionId};
use lemmy_utils::{
error::LemmyError,
utils::{check_application_question, check_slurs_opt},
ConnectionId,
};
use lemmy_websocket::{messages::SendAllMessage, LemmyContext, UserOperationCrud};
use std::{default::Default, str::FromStr};
use std::str::FromStr;
#[async_trait::async_trait(?Send)]
impl PerformCrud for EditSite {
@ -32,32 +46,22 @@ impl PerformCrud for EditSite {
let data: &EditSite = self;
let local_user_view =
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
// Make sure user is an admin
is_admin(&local_user_view)?;
let local_site = blocking(context.pool(), Site::read_local).await??;
let sidebar = diesel_option_overwrite(&data.sidebar);
let description = diesel_option_overwrite(&data.description);
let application_question = diesel_option_overwrite(&data.application_question);
let legal_information = diesel_option_overwrite(&data.legal_information);
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs_opt(&data.name, &context.settings().slur_regex())?;
check_slurs_opt(&data.description, &context.settings().slur_regex())?;
check_slurs_opt(&data.name, &slur_regex)?;
check_slurs_opt(&data.description, &slur_regex)?;
if let Some(Some(desc)) = &description {
if let Some(desc) = &data.description {
site_description_length_check(desc)?;
}
// Make sure if applications are required, that there is an application questionnaire
if data.require_application.unwrap_or(false)
&& application_question.as_ref().unwrap_or(&None).is_none()
{
return Err(LemmyError::from_message("application_question_required"));
}
let application_question = diesel_option_overwrite(&data.application_question);
check_application_question(&application_question, &data.require_application)?;
if let Some(default_post_listing_type) = &data.default_post_listing_type {
// only allow all or local as default listing types
@ -69,7 +73,7 @@ impl PerformCrud for EditSite {
}
}
let site_id = local_site.id;
let site_id = local_site.site_id;
if let Some(discussion_languages) = data.discussion_languages.clone() {
blocking(context.pool(), move |conn| {
SiteLanguage::update(conn, discussion_languages.clone(), site_id)
@ -77,41 +81,104 @@ impl PerformCrud for EditSite {
.await??;
}
let site_form = SiteForm {
name: data.name.to_owned().unwrap_or(local_site.name),
sidebar,
description,
icon,
banner,
updated: Some(naive_now()),
enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration,
enable_nsfw: data.enable_nsfw,
community_creation_admin_only: data.community_creation_admin_only,
require_email_verification: data.require_email_verification,
require_application: data.require_application,
application_question,
private_instance: data.private_instance,
default_theme: data.default_theme.clone(),
default_post_listing_type: data.default_post_listing_type.clone(),
legal_information,
application_email_admins: data.application_email_admins,
hide_modlog_mod_names: data.hide_modlog_mod_names,
..SiteForm::default()
};
let update_site = blocking(context.pool(), move |conn| {
Site::update(conn, local_site.id, &site_form)
let name = data.name.to_owned();
let site_form = SiteUpdateForm::builder()
.name(name)
.sidebar(diesel_option_overwrite(&data.sidebar))
.description(diesel_option_overwrite(&data.description))
.icon(diesel_option_overwrite_to_url(&data.icon)?)
.banner(diesel_option_overwrite_to_url(&data.banner)?)
.updated(Some(Some(naive_now())))
.build();
blocking(context.pool(), move |conn| {
Site::update(conn, site_id, &site_form)
})
.await
// Ignore errors for all these, so as to not throw errors if no update occurs
// Diesel will throw an error for empty update forms
.ok();
let local_site_form = LocalSiteUpdateForm::builder()
.enable_downvotes(data.enable_downvotes)
.open_registration(data.open_registration)
.enable_nsfw(data.enable_nsfw)
.community_creation_admin_only(data.community_creation_admin_only)
.require_email_verification(data.require_email_verification)
.require_application(data.require_application)
.application_question(application_question)
.private_instance(data.private_instance)
.default_theme(data.default_theme.clone())
.default_post_listing_type(data.default_post_listing_type.clone())
.legal_information(diesel_option_overwrite(&data.legal_information))
.application_email_admins(data.application_email_admins)
.hide_modlog_mod_names(data.hide_modlog_mod_names)
.updated(Some(Some(naive_now())))
.slur_filter_regex(diesel_option_overwrite(&data.slur_filter_regex))
.actor_name_max_length(data.actor_name_max_length)
.federation_enabled(data.federation_enabled)
.federation_debug(data.federation_debug)
.federation_strict_allowlist(data.federation_strict_allowlist)
.federation_http_fetch_retry_limit(data.federation_http_fetch_retry_limit)
.federation_worker_count(data.federation_worker_count)
.captcha_enabled(data.captcha_enabled)
.captcha_difficulty(data.captcha_difficulty.to_owned())
.build();
let update_local_site = blocking(context.pool(), move |conn| {
LocalSite::update(conn, &local_site_form)
})
.await?
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_site"))?;
.await
.ok();
let local_site_rate_limit_form = LocalSiteRateLimitUpdateForm::builder()
.message(data.rate_limit_message)
.message_per_second(data.rate_limit_message_per_second)
.post(data.rate_limit_post)
.post_per_second(data.rate_limit_post_per_second)
.register(data.rate_limit_register)
.register_per_second(data.rate_limit_register_per_second)
.image(data.rate_limit_image)
.image_per_second(data.rate_limit_image_per_second)
.comment(data.rate_limit_comment)
.comment_per_second(data.rate_limit_comment_per_second)
.search(data.rate_limit_search)
.search_per_second(data.rate_limit_search_per_second)
.build();
blocking(context.pool(), move |conn| {
LocalSiteRateLimit::update(conn, &local_site_rate_limit_form)
})
.await
.ok();
// Replace the blocked and allowed instances
let allowed = data.allowed_instances.to_owned();
blocking(context.pool(), move |conn| {
FederationAllowList::replace(conn, allowed)
})
.await??;
let blocked = data.blocked_instances.to_owned();
blocking(context.pool(), move |conn| {
FederationBlockList::replace(conn, blocked)
})
.await??;
// TODO can't think of a better way to do this.
// If the server suddenly requires email verification, or required applications, no old users
// will be able to log in. It really only wants this to be a requirement for NEW signups.
// So if it was set from false, to true, you need to update all current users columns to be verified.
if !local_site.require_application && update_site.require_application {
let new_require_application = update_local_site
.as_ref()
.map(|ols| {
ols
.as_ref()
.map(|ls| ls.require_application)
.unwrap_or(false)
})
.unwrap_or(false);
if !local_site.require_application && new_require_application {
blocking(context.pool(), move |conn| {
LocalUser::set_all_users_registration_applications_accepted(conn)
})
@ -119,7 +186,16 @@ impl PerformCrud for EditSite {
.map_err(|e| LemmyError::from_error_message(e, "couldnt_set_all_registrations_accepted"))?;
}
if !local_site.require_email_verification && update_site.require_email_verification {
let new_require_email_verification = update_local_site
.as_ref()
.map(|ols| {
ols
.as_ref()
.map(|ls| ls.require_email_verification)
.unwrap_or(false)
})
.unwrap_or(false);
if !local_site.require_email_verification && new_require_email_verification {
blocking(context.pool(), move |conn| {
LocalUser::set_all_users_email_verified(conn)
})

@ -6,6 +6,7 @@ use lemmy_api_common::{
utils::{
blocking,
honeypot_check,
local_site_to_slur_regex,
password_length_check,
send_new_applicant_email_to_admins,
send_verification_email,
@ -20,14 +21,13 @@ use lemmy_apub::{
use lemmy_db_schema::{
aggregates::structs::PersonAggregates,
source::{
local_user::{LocalUser, LocalUserForm},
person::{Person, PersonForm},
registration_application::{RegistrationApplication, RegistrationApplicationForm},
site::Site,
local_user::{LocalUser, LocalUserInsertForm},
person::{Person, PersonInsertForm},
registration_application::{RegistrationApplication, RegistrationApplicationInsertForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_db_views_actor::structs::PersonViewSafe;
use lemmy_utils::{
claims::Claims,
@ -49,27 +49,21 @@ impl PerformCrud for Register {
) -> Result<LoginResponse, LemmyError> {
let data: &Register = self;
// no email verification, or applications if the site is not setup yet
let (mut email_verification, mut require_application) = (false, false);
let site_view = blocking(context.pool(), SiteView::read_local).await??;
let local_site = site_view.local_site;
// Make sure site has open registration
let site = blocking(context.pool(), Site::read_local).await?;
if let Ok(site) = &site {
if !site.open_registration {
return Err(LemmyError::from_message("registration_closed"));
}
email_verification = site.require_email_verification;
require_application = site.require_application;
if !local_site.open_registration {
return Err(LemmyError::from_message("registration_closed"));
}
password_length_check(&data.password)?;
honeypot_check(&data.honeypot)?;
if email_verification && data.email.is_none() {
if local_site.require_email_verification && data.email.is_none() {
return Err(LemmyError::from_message("email_required"));
}
if require_application && data.answer.is_none() {
if local_site.require_application && data.answer.is_none() {
return Err(LemmyError::from_message(
"registration_application_answer_required",
));
@ -87,7 +81,7 @@ impl PerformCrud for Register {
.await??;
// If its not the admin, check the captcha
if !no_admins && context.settings().captcha.enabled {
if !no_admins && local_site.captcha_enabled {
let check = context
.chat_server()
.send(CheckCaptcha {
@ -106,12 +100,12 @@ impl PerformCrud for Register {
}
}
let slur_regex = &context.settings().slur_regex();
check_slurs(&data.username, slur_regex)?;
check_slurs_opt(&data.answer, slur_regex)?;
let slur_regex = local_site_to_slur_regex(&local_site);
check_slurs(&data.username, &slur_regex)?;
check_slurs_opt(&data.answer, &slur_regex)?;
let actor_keypair = generate_actor_keypair()?;
if !is_valid_actor_name(&data.username, context.settings().actor_name_max_length) {
if !is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize) {
return Err(LemmyError::from_message("invalid_username"));
}
let actor_id = generate_local_apub_endpoint(
@ -123,16 +117,16 @@ impl PerformCrud for Register {
// We have to create both a person, and local_user
// Register the new person
let person_form = PersonForm {
name: data.username.to_owned(),
actor_id: Some(actor_id.clone()),
private_key: Some(Some(actor_keypair.private_key)),
public_key: Some(actor_keypair.public_key),
inbox_url: Some(generate_inbox_url(&actor_id)?),
shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
admin: Some(no_admins),
..PersonForm::default()
};
let person_form = PersonInsertForm::builder()
.name(data.username.to_owned())
.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)?))
.admin(Some(no_admins))
.instance_id(site_view.site.instance_id)
.build();
// insert the person
let inserted_person = blocking(context.pool(), move |conn| {
@ -142,17 +136,15 @@ impl PerformCrud for Register {
.map_err(|e| LemmyError::from_error_message(e, "user_already_exists"))?;
// Create the local user
let local_user_form = LocalUserForm {
person_id: Some(inserted_person.id),
email: Some(data.email.as_deref().map(|s| s.to_lowercase())),
password_encrypted: Some(data.password.to_string()),
show_nsfw: Some(data.show_nsfw),
email_verified: Some(false),
..LocalUserForm::default()
};
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.email(data.email.as_deref().map(|s| s.to_lowercase()))
.password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw))
.build();
let inserted_local_user = match blocking(context.pool(), move |conn| {
LocalUser::register(conn, &local_user_form)
LocalUser::create(conn, &local_user_form)
})
.await?
{
@ -176,13 +168,12 @@ impl PerformCrud for Register {
}
};
if require_application {
if local_site.require_application {
// Create the registration application
let form = RegistrationApplicationForm {
local_user_id: Some(inserted_local_user.id),
let form = RegistrationApplicationInsertForm {
local_user_id: inserted_local_user.id,
// We already made sure answer was not null above
answer: data.answer.to_owned(),
..RegistrationApplicationForm::default()
answer: data.answer.to_owned().expect("must have an answer"),
};
blocking(context.pool(), move |conn| {
@ -192,7 +183,7 @@ impl PerformCrud for Register {
}
// Email the admins
if site.map(|s| s.application_email_admins).unwrap_or(false) {
if local_site.application_email_admins {
send_new_applicant_email_to_admins(&data.username, context.pool(), context.settings())
.await?;
}
@ -204,7 +195,7 @@ impl PerformCrud for Register {
};
// Log the user in directly if email verification and application aren't required
if !require_application && !email_verification {
if !local_site.require_application && !local_site.require_email_verification {
login_response.jwt = Some(
Claims::jwt(
inserted_local_user.id.0,
@ -214,7 +205,7 @@ impl PerformCrud for Register {
.into(),
);
} else {
if email_verification {
if local_site.require_email_verification {
let local_user_view = LocalUserView {
local_user: inserted_local_user,
person: inserted_person,
@ -226,12 +217,13 @@ impl PerformCrud for Register {
.email
.clone()
.expect("email was provided");
send_verification_email(&local_user_view, &email, context.pool(), context.settings())
.await?;
login_response.verify_email_sent = true;
}
if require_application {
if local_site.require_application {
login_response.registration_created = true;
}
}

@ -5,7 +5,10 @@ use lemmy_api_common::{
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
};
use lemmy_apub::{fetcher::resolve_actor_identifier, objects::person::ApubPerson};
use lemmy_db_schema::{source::person::Person, utils::post_to_comment_sort_type};
use lemmy_db_schema::{
source::{local_site::LocalSite, person::Person},
utils::post_to_comment_sort_type,
};
use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery};
use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonViewSafe};
use lemmy_utils::{error::LemmyError, ConnectionId};
@ -31,7 +34,9 @@ impl PerformCrud for GetPersonDetails {
let local_user_view =
get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
.await?;
check_private_instance(&local_user_view, context.pool()).await?;
let local_site = blocking(context.pool(), LocalSite::read).await??;
check_private_instance(&local_user_view, &local_site)?;
let person_details_id = match data.person_id {
Some(id) => id,

@ -9,6 +9,8 @@ use crate::{
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, instance::remote_instance_inboxes, person::ApubPerson},
protocol::activities::block::block_user::BlockUser,
@ -33,7 +35,7 @@ use lemmy_db_schema::{
CommunityPersonBanForm,
},
moderator::{ModBan, ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm},
person::Person,
person::{Person, PersonUpdateForm},
},
traits::{Bannable, Crud, Followable},
};
@ -123,6 +125,10 @@ impl ActivityHandler for BlockUser {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
match self
.target
@ -177,7 +183,14 @@ impl ActivityHandler for BlockUser {
match target {
SiteOrCommunity::Site(_site) => {
let blocked_person = blocking(context.pool(), move |conn| {
Person::ban_person(conn, blocked_person.id, true, expires)
Person::update(
conn,
blocked_person.id,
&PersonUpdateForm::builder()
.banned(Some(true))
.ban_expires(Some(expires))
.build(),
)
})
.await??;
if self.remove_data.unwrap_or(false) {

@ -7,6 +7,8 @@ use crate::{
verify_is_public,
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, instance::remote_instance_inboxes, person::ApubPerson},
protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
@ -24,7 +26,7 @@ use lemmy_db_schema::{
source::{
community::{CommunityPersonBan, CommunityPersonBanForm},
moderator::{ModBan, ModBanForm, ModBanFromCommunity, ModBanFromCommunityForm},
person::Person,
person::{Person, PersonUpdateForm},
},
traits::{Bannable, Crud},
};
@ -90,6 +92,10 @@ impl ActivityHandler for UndoBlockUser {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
verify_domains_match(self.actor.inner(), self.object.actor.inner())?;
self.object.verify(context, request_counter).await?;
@ -121,7 +127,14 @@ impl ActivityHandler for UndoBlockUser {
{
SiteOrCommunity::Site(_site) => {
let blocked_person = blocking(context.pool(), move |conn| {
Person::ban_person(conn, blocked_person.id, false, expires)
Person::update(
conn,
blocked_person.id,
&PersonUpdateForm::builder()
.banned(Some(false))
.ban_expires(Some(expires))
.build(),
)
})
.await??;

@ -12,6 +12,8 @@ use crate::{
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
generate_moderators_url,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
@ -84,6 +86,10 @@ impl ActivityHandler for AddMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;

@ -1,6 +1,8 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_is_public},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
insert_activity,
objects::community::ApubCommunity,
protocol::{
@ -11,6 +13,7 @@ use crate::{
};
use activitypub_federation::{core::object_id::ObjectId, data::Data, traits::ActivityHandler};
use activitystreams_kinds::{activity::AnnounceType, public};
use lemmy_api_common::utils::blocking;
use lemmy_utils::error::LemmyError;
use lemmy_websocket::LemmyContext;
use tracing::debug;
@ -84,9 +87,13 @@ impl ActivityHandler for AnnounceActivity {
#[tracing::instrument(skip_all)]
async fn verify(
&self,
_context: &Data<LemmyContext>,
context: &Data<LemmyContext>,
_request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
Ok(())
}

@ -12,6 +12,8 @@ use crate::{
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
generate_moderators_url,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
@ -84,6 +86,10 @@ impl ActivityHandler for RemoveMod {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;

@ -1,5 +1,7 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person_in_community},
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::report::Report,
@ -74,6 +76,10 @@ impl ActivityHandler for Report {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
let community = self.to[0]
.dereference(context, local_instance(context), request_counter)
.await?;

@ -7,6 +7,8 @@ use crate::{
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::community::update::UpdateCommunity,
@ -19,10 +21,7 @@ use activitypub_federation::{
};
use activitystreams_kinds::{activity::UpdateType, public};
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{
source::community::{Community, CommunityForm},
traits::Crud,
};
use lemmy_db_schema::{source::community::Community, traits::Crud};
use lemmy_utils::error::LemmyError;
use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
use url::Url;
@ -72,6 +71,9 @@ impl ActivityHandler for UpdateCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
@ -101,19 +103,10 @@ impl ActivityHandler for UpdateCommunity {
) -> Result<(), LemmyError> {
let community = self.get_community(context, request_counter).await?;
let updated_community = self.object.into_form();
let cf = CommunityForm {
name: updated_community.name,
title: updated_community.title,
description: updated_community.description,
nsfw: updated_community.nsfw,
// TODO: icon and banner would be hosted on the other instance, ideally we would copy it to ours
icon: updated_community.icon,
banner: updated_community.banner,
..CommunityForm::default()
};
let community_update_form = self.object.into_update_form();
let updated_community = blocking(context.pool(), move |conn| {
Community::update(conn, community.id, &cf)
Community::update(conn, community.id, &community_update_form)
})
.await??;

@ -8,6 +8,8 @@ use crate::{
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
local_instance,
mentions::MentionOrValue,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
@ -115,6 +117,10 @@ impl ActivityHandler for CreateOrUpdateComment {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
let post = self.object.get_parents(context, request_counter).await?.0;
let community = self.get_community(context, request_counter).await?;

@ -8,6 +8,8 @@ use crate::{
verify_person_in_community,
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::{create_or_update::post::CreateOrUpdatePost, CreateOrUpdateType},
ActorType,
@ -93,6 +95,9 @@ impl ActivityHandler for CreateOrUpdatePost {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &self.cc)?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;

@ -1,5 +1,7 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person},
check_apub_id_valid,
fetch_local_site_data,
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
protocol::activities::{
create_or_update::private_message::CreateOrUpdatePrivateMessage,
@ -69,6 +71,10 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_person(&self.actor, context, request_counter).await?;
verify_domains_match(self.actor.inner(), self.object.id.inner())?;
verify_domains_match(self.to[0].inner(), self.object.to[0].inner())?;

@ -4,6 +4,8 @@ use crate::{
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
generate_activity_id,
},
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
@ -14,8 +16,8 @@ use anyhow::anyhow;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{
source::{
comment::Comment,
community::Community,
comment::{Comment, CommentUpdateForm},
community::{Community, CommunityUpdateForm},
moderator::{
ModRemoveComment,
ModRemoveCommentForm,
@ -24,7 +26,7 @@ use lemmy_db_schema::{
ModRemovePost,
ModRemovePostForm,
},
post::Post,
post::{Post, PostUpdateForm},
},
traits::Crud,
};
@ -55,6 +57,9 @@ impl ActivityHandler for Delete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_delete_activity(self, self.summary.is_some(), context, request_counter).await?;
Ok(())
}
@ -150,7 +155,11 @@ pub(in crate::activities) async fn receive_remove_action(
})
.await??;
let deleted_community = blocking(context.pool(), move |conn| {
Community::update_removed(conn, community.id, true)
Community::update(
conn,
community.id,
&CommunityUpdateForm::builder().removed(Some(true)).build(),
)
})
.await??;
@ -168,7 +177,11 @@ pub(in crate::activities) async fn receive_remove_action(
})
.await??;
let removed_post = blocking(context.pool(), move |conn| {
Post::update_removed(conn, post.id, true)
Post::update(
conn,
post.id,
&PostUpdateForm::builder().removed(Some(true)).build(),
)
})
.await??;
@ -186,7 +199,11 @@ pub(in crate::activities) async fn receive_remove_action(
})
.await??;
let removed_comment = blocking(context.pool(), move |conn| {
Comment::update_removed(conn, comment.id, true)
Comment::update(
conn,
comment.id,
&CommentUpdateForm::builder().removed(Some(true)).build(),
)
})
.await??;

@ -1,5 +1,7 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_is_public, verify_person},
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{instance::remote_instance_inboxes, person::ApubPerson},
protocol::activities::deletion::delete_user::DeleteUser,
@ -11,7 +13,7 @@ use activitypub_federation::{
utils::verify_urls_match,
};
use activitystreams_kinds::{activity::DeleteType, public};
use lemmy_api_common::utils::delete_user_account;
use lemmy_api_common::utils::{blocking, delete_user_account};
use lemmy_utils::error::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
@ -36,6 +38,9 @@ impl ActivityHandler for DeleteUser {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_is_public(&self.to, &[])?;
verify_person(&self.actor, context, request_counter).await?;
verify_urls_match(self.actor.inner(), self.object.inner())?;

@ -28,11 +28,11 @@ use activitystreams_kinds::public;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{
source::{
comment::Comment,
community::Community,
comment::{Comment, CommentUpdateForm},
community::{Community, CommunityUpdateForm},
person::Person,
post::Post,
private_message::PrivateMessage,
post::{Post, PostUpdateForm},
private_message::{PrivateMessage, PrivateMessageUpdateForm},
},
traits::Crud,
};
@ -239,7 +239,13 @@ async fn receive_delete_action(
}
let community = blocking(context.pool(), move |conn| {
Community::update_deleted(conn, community.id, deleted)
Community::update(
conn,
community.id,
&CommunityUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
})
.await??;
send_community_ws_message(
@ -254,7 +260,11 @@ async fn receive_delete_action(
DeletableObjects::Post(post) => {
if deleted != post.deleted {
let deleted_post = blocking(context.pool(), move |conn| {
Post::update_deleted(conn, post.id, deleted)
Post::update(
conn,
post.id,
&PostUpdateForm::builder().deleted(Some(deleted)).build(),
)
})
.await??;
send_post_ws_message(
@ -270,7 +280,11 @@ async fn receive_delete_action(
DeletableObjects::Comment(comment) => {
if deleted != comment.deleted {
let deleted_comment = blocking(context.pool(), move |conn| {
Comment::update_deleted(conn, comment.id, deleted)
Comment::update(
conn,
comment.id,
&CommentUpdateForm::builder().deleted(Some(deleted)).build(),
)
})
.await??;
send_comment_ws_message_simple(
@ -283,7 +297,13 @@ async fn receive_delete_action(
}
DeletableObjects::PrivateMessage(pm) => {
let deleted_private_message = blocking(context.pool(), move |conn| {
PrivateMessage::update_deleted(conn, pm.id, deleted)
PrivateMessage::update(
conn,
pm.id,
&PrivateMessageUpdateForm::builder()
.deleted(Some(deleted))
.build(),
)
})
.await??;

@ -4,6 +4,8 @@ use crate::{
deletion::{receive_delete_action, verify_delete_activity, DeletableObjects},
generate_activity_id,
},
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
@ -13,8 +15,8 @@ use activitystreams_kinds::activity::UndoType;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{
source::{
comment::Comment,
community::Community,
comment::{Comment, CommentUpdateForm},
community::{Community, CommunityUpdateForm},
moderator::{
ModRemoveComment,
ModRemoveCommentForm,
@ -23,7 +25,7 @@ use lemmy_db_schema::{
ModRemovePost,
ModRemovePostForm,
},
post::Post,
post::{Post, PostUpdateForm},
},
traits::Crud,
};
@ -54,6 +56,9 @@ impl ActivityHandler for UndoDelete {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
self.object.verify(context, request_counter).await?;
verify_delete_activity(
&self.object,
@ -148,7 +153,11 @@ impl UndoDelete {
})
.await??;
let deleted_community = blocking(context.pool(), move |conn| {
Community::update_removed(conn, community.id, false)
Community::update(
conn,
community.id,
&CommunityUpdateForm::builder().removed(Some(false)).build(),
)
})
.await??;
send_community_ws_message(deleted_community.id, EditCommunity, None, None, context).await?;
@ -165,7 +174,11 @@ impl UndoDelete {
})
.await??;
let removed_post = blocking(context.pool(), move |conn| {
Post::update_removed(conn, post.id, false)
Post::update(
conn,
post.id,
&PostUpdateForm::builder().removed(Some(false)).build(),
)
})
.await??;
send_post_ws_message(removed_post.id, EditPost, None, None, context).await?;
@ -182,7 +195,11 @@ impl UndoDelete {
})
.await??;
let removed_comment = blocking(context.pool(), move |conn| {
Comment::update_removed(conn, comment.id, false)
Comment::update(
conn,
comment.id,
&CommentUpdateForm::builder().removed(Some(false)).build(),
)
})
.await??;
send_comment_ws_message_simple(removed_comment.id, EditComment, context).await?;

@ -1,5 +1,7 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity},
check_apub_id_valid,
fetch_local_site_data,
local_instance,
protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity},
ActorType,
@ -67,6 +69,10 @@ impl ActivityHandler for AcceptFollowCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_urls_match(self.actor.inner(), self.object.object.inner())?;
self.object.verify(context, request_counter).await?;
Ok(())

@ -5,6 +5,8 @@ use crate::{
verify_person,
verify_person_in_community,
},
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{accept::AcceptFollowCommunity, follow::FollowCommunity},
@ -84,6 +86,9 @@ impl ActivityHandler for FollowCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_person(&self.actor, context, request_counter).await?;
let community = self
.object

@ -1,5 +1,7 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person},
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{follow::FollowCommunity, undo_follow::UndoFollowCommunity},
@ -63,6 +65,9 @@ impl ActivityHandler for UndoFollowCommunity {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
verify_urls_match(self.actor.inner(), self.object.actor.inner())?;
verify_person(&self.actor, context, request_counter).await?;
self.object.verify(context, request_counter).await?;

@ -14,7 +14,10 @@ use activitypub_federation::{
use activitystreams_kinds::public;
use anyhow::anyhow;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{newtypes::CommunityId, source::community::Community};
use lemmy_db_schema::{
newtypes::CommunityId,
source::{community::Community, local_site::LocalSite},
};
use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView};
use lemmy_utils::error::LemmyError;
use lemmy_websocket::LemmyContext;
@ -167,9 +170,14 @@ where
ActorT: Actor + ActorType,
Activity: ActivityHandler<Error = LemmyError>,
{
if !context.settings().federation.enabled {
let federation_enabled = blocking(context.pool(), &LocalSite::read)
.await?
.map(|l| l.federation_enabled)
.unwrap_or(false);
if !federation_enabled {
return Ok(());
}
info!("Sending activity {}", activity.id().to_string());
let activity = WithContext::new(activity, CONTEXT.deref().clone());

@ -6,6 +6,8 @@ use crate::{
voting::{undo_vote_comment, undo_vote_post},
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::voting::{
@ -82,6 +84,9 @@ impl ActivityHandler for UndoVote {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
verify_urls_match(self.actor.inner(), self.object.actor.inner())?;

@ -6,6 +6,8 @@ use crate::{
voting::{vote_comment, vote_post},
},
activity_lists::AnnouncableActivities,
check_apub_id_valid,
fetch_local_site_data,
local_instance,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::voting::vote::{Vote, VoteType},
@ -17,7 +19,7 @@ use anyhow::anyhow;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{
newtypes::CommunityId,
source::{community::Community, post::Post, site::Site},
source::{community::Community, local_site::LocalSite, post::Post},
traits::Crud,
};
use lemmy_utils::error::LemmyError;
@ -81,10 +83,16 @@ impl ActivityHandler for Vote {
context: &Data<LemmyContext>,
request_counter: &mut i32,
) -> Result<(), LemmyError> {
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid(self.id(), &local_site_data, context.settings())
.map_err(LemmyError::from_message)?;
let community = self.get_community(context, request_counter).await?;
verify_person_in_community(&self.actor, &community, context, request_counter).await?;
let site = blocking(context.pool(), Site::read_local).await??;
if self.kind == VoteType::Dislike && !site.enable_downvotes {
let enable_downvotes = blocking(context.pool(), LocalSite::read)
.await?
.map(|l| l.enable_downvotes)
.unwrap_or(true);
if self.kind == VoteType::Dislike && !enable_downvotes {
return Err(anyhow!("Downvotes disabled").into());
}
Ok(())

@ -152,7 +152,8 @@ mod tests {
use lemmy_db_schema::{
source::{
community::Community,
person::{Person, PersonForm},
instance::Instance,
person::{Person, PersonInsertForm},
site::Site,
},
traits::Crud,
@ -168,11 +169,14 @@ mod tests {
let community = parse_lemmy_community(&context).await;
let community_id = community.id;
let old_mod = PersonForm {
name: "holly".into(),
public_key: Some("pubkey".to_string()),
..PersonForm::default()
};
let inserted_instance = Instance::create(conn, "my_domain.tld").unwrap();
let old_mod = PersonInsertForm::builder()
.name("holly".into())
.public_key("pubkey".to_string())
.instance_id(inserted_instance.id)
.build();
let old_mod = Person::create(conn, &old_mod).unwrap();
let community_moderator_form = CommunityModeratorForm {
community_id: community.id,
@ -210,5 +214,6 @@ mod tests {
Person::delete(conn, new_mod.id).unwrap();
Community::delete(conn, community_context.0.id).unwrap();
Site::delete(conn, site.id).unwrap();
Instance::delete(conn, inserted_instance.id).unwrap();
}
}

@ -2,7 +2,8 @@ use crate::{local_instance, ActorType};
use activitypub_federation::{core::object_id::ObjectId, traits::ApubObject};
use anyhow::anyhow;
use itertools::Itertools;
use lemmy_db_schema::newtypes::DbUrl;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{newtypes::DbUrl, source::local_site::LocalSite};
use lemmy_utils::error::LemmyError;
use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize};
@ -47,8 +48,14 @@ where
);
debug!("Fetching webfinger url: {}", &fetch_url);
let local_site = blocking(context.pool(), LocalSite::read).await?;
let http_fetch_retry_limit = local_site
.as_ref()
.map(|l| l.federation_http_fetch_retry_limit)
.unwrap_or(25);
*request_counter += 1;
if *request_counter > context.settings().federation.http_fetch_retry_limit {
if *request_counter > http_fetch_retry_limit {
return Err(LemmyError::from_message("Request retry limit reached"));
}

@ -19,51 +19,46 @@ use actix_web::{
web,
};
use http_signature_normalization_actix::digest::middleware::VerifyDigest;
use lemmy_utils::settings::structs::Settings;
use sha2::{Digest, Sha256};
pub fn config(cfg: &mut web::ServiceConfig, settings: &Settings) {
if settings.federation.enabled {
println!("federation enabled, host is {}", settings.hostname);
pub fn config(cfg: &mut web::ServiceConfig) {
cfg
.route("/", web::get().to(get_apub_site_http))
.route("/site_outbox", web::get().to(get_apub_site_outbox))
.route(
"/c/{community_name}",
web::get().to(get_apub_community_http),
)
.route(
"/c/{community_name}/followers",
web::get().to(get_apub_community_followers),
)
.route(
"/c/{community_name}/outbox",
web::get().to(get_apub_community_outbox),
)
.route(
"/c/{community_name}/moderators",
web::get().to(get_apub_community_moderators),
)
.route("/u/{user_name}", web::get().to(get_apub_person_http))
.route(
"/u/{user_name}/outbox",
web::get().to(get_apub_person_outbox),
)
.route("/post/{post_id}", web::get().to(get_apub_post))
.route("/comment/{comment_id}", web::get().to(get_apub_comment))
.route("/activities/{type_}/{id}", web::get().to(get_activity));
cfg
.route("/", web::get().to(get_apub_site_http))
.route("/site_outbox", web::get().to(get_apub_site_outbox))
.route(
"/c/{community_name}",
web::get().to(get_apub_community_http),
)
.route(
"/c/{community_name}/followers",
web::get().to(get_apub_community_followers),
)
.route(
"/c/{community_name}/outbox",
web::get().to(get_apub_community_outbox),
)
.route(
"/c/{community_name}/moderators",
web::get().to(get_apub_community_moderators),
)
.route("/u/{user_name}", web::get().to(get_apub_person_http))
.route(
"/u/{user_name}/outbox",
web::get().to(get_apub_person_outbox),
)
.route("/post/{post_id}", web::get().to(get_apub_post))
.route("/comment/{comment_id}", web::get().to(get_apub_comment))
.route("/activities/{type_}/{id}", web::get().to(get_activity));
cfg.service(
web::scope("")
.wrap(VerifyDigest::new(Sha256::new()))
.guard(InboxRequestGuard)
.route("/c/{community_name}/inbox", web::post().to(community_inbox))
.route("/u/{user_name}/inbox", web::post().to(person_inbox))
.route("/inbox", web::post().to(shared_inbox))
.route("/site_inbox", web::post().to(get_apub_site_inbox)),
);
}
cfg.service(
web::scope("")
.wrap(VerifyDigest::new(Sha256::new()))
.guard(InboxRequestGuard)
.route("/c/{community_name}/inbox", web::post().to(community_inbox))
.route("/u/{user_name}/inbox", web::post().to(person_inbox))
.route("/inbox", web::post().to(shared_inbox))
.route("/site_inbox", web::post().to(get_apub_site_inbox)),
);
}
/// Without this, things like webfinger or RSS feeds stop working, as all requests seem to get

@ -7,7 +7,7 @@ use crate::{
use activitypub_federation::{deser::context::WithContext, traits::ApubObject};
use actix_web::{web, HttpRequest, HttpResponse};
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::source::site::Site;
use lemmy_db_views::structs::SiteView;
use lemmy_utils::error::LemmyError;
use lemmy_websocket::LemmyContext;
use url::Url;
@ -15,7 +15,10 @@ use url::Url;
pub(crate) async fn get_apub_site_http(
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
let site: ApubSite = blocking(context.pool(), Site::read_local).await??.into();
let site: ApubSite = blocking(context.pool(), SiteView::read_local)
.await??
.site
.into();
let apub = site.into_apub(&context).await?;
Ok(create_apub_response(&apub))

@ -6,13 +6,14 @@ use activitypub_federation::{
LocalInstance,
};
use anyhow::Context;
use diesel::PgConnection;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{newtypes::DbUrl, source::activity::Activity, utils::DbPool};
use lemmy_utils::{
error::LemmyError,
location_info,
settings::{structs::Settings, SETTINGS},
use lemmy_db_schema::{
newtypes::DbUrl,
source::{activity::Activity, instance::Instance, local_site::LocalSite},
utils::DbPool,
};
use lemmy_utils::{error::LemmyError, location_info, settings::structs::Settings};
use lemmy_websocket::LemmyContext;
use once_cell::sync::{Lazy, OnceCell};
use url::{ParseError, Url};
@ -31,16 +32,35 @@ static CONTEXT: Lazy<Vec<serde_json::Value>> = Lazy::new(|| {
});
// TODO: store this in context? but its only used in this crate, no need to expose it elsewhere
// TODO this singleton needs to be redone to account for live data.
fn local_instance(context: &LemmyContext) -> &'static LocalInstance {
static LOCAL_INSTANCE: OnceCell<LocalInstance> = OnceCell::new();
LOCAL_INSTANCE.get_or_init(|| {
let conn = &mut context
.pool()
.get()
.expect("getting connection for LOCAL_INSTANCE init");
// Local site may be missing
let local_site = &LocalSite::read(conn);
let worker_count = local_site
.as_ref()
.map(|l| l.federation_worker_count)
.unwrap_or(64) as u64;
let http_fetch_retry_limit = local_site
.as_ref()
.map(|l| l.federation_http_fetch_retry_limit)
.unwrap_or(25);
let federation_debug = local_site
.as_ref()
.map(|l| l.federation_debug)
.unwrap_or(true);
let settings = InstanceSettings::builder()
.http_fetch_retry_limit(context.settings().federation.http_fetch_retry_limit)
.worker_count(context.settings().federation.worker_count)
.debug(context.settings().federation.debug)
.http_fetch_retry_limit(http_fetch_retry_limit)
.worker_count(worker_count)
.debug(federation_debug)
// TODO No idea why, but you can't pass context.settings() to the verify_url_function closure
// without the value getting captured.
.verify_url_function(|url| check_apub_id_valid(url, &SETTINGS))
.http_signature_compat(true)
.build()
.expect("configure federation");
@ -62,8 +82,13 @@ fn local_instance(context: &LemmyContext) -> &'static LocalInstance {
///
/// `use_strict_allowlist` should be true only when parsing a remote community, or when parsing a
/// post/comment in a local community.
#[tracing::instrument(skip(settings))]
fn check_apub_id_valid(apub_id: &Url, settings: &Settings) -> Result<(), &'static str> {
#[tracing::instrument(skip(settings, local_site_data))]
// TODO This function needs to be called by incoming activities
fn check_apub_id_valid(
apub_id: &Url,
local_site_data: &LocalSiteData,
settings: &Settings,
) -> Result<(), &'static str> {
let domain = apub_id.domain().expect("apud id has domain").to_string();
let local_instance = settings
.get_hostname_without_port()
@ -72,7 +97,12 @@ fn check_apub_id_valid(apub_id: &Url, settings: &Settings) -> Result<(), &'stati
return Ok(());
}
if !settings.federation.enabled {
if !local_site_data
.local_site
.as_ref()
.map(|l| l.federation_enabled)
.unwrap_or(true)
{
return Err("Federation disabled");
}
@ -80,13 +110,13 @@ fn check_apub_id_valid(apub_id: &Url, settings: &Settings) -> Result<(), &'stati
return Err("Invalid protocol scheme");
}
if let Some(blocked) = settings.to_owned().federation.blocked_instances {
if let Some(blocked) = local_site_data.blocked_instances.as_ref() {
if blocked.contains(&domain) {
return Err("Domain is blocked");
}
}
if let Some(allowed) = settings.to_owned().federation.allowed_instances {
if let Some(allowed) = local_site_data.allowed_instances.as_ref() {
if !allowed.contains(&domain) {
return Err("Domain is not in allowlist");
}
@ -95,13 +125,40 @@ fn check_apub_id_valid(apub_id: &Url, settings: &Settings) -> Result<(), &'stati
Ok(())
}
#[tracing::instrument(skip(settings))]
#[derive(Clone)]
pub(crate) struct LocalSiteData {
local_site: Option<LocalSite>,
allowed_instances: Option<Vec<String>>,
blocked_instances: Option<Vec<String>>,
}
pub(crate) fn fetch_local_site_data(
conn: &mut PgConnection,
) -> Result<LocalSiteData, diesel::result::Error> {
// LocalSite may be missing
let local_site = LocalSite::read(conn).ok();
let allowed = Instance::allowlist(conn)?;
let blocked = Instance::blocklist(conn)?;
// These can return empty vectors, so convert them to options
let allowed_instances = (!allowed.is_empty()).then(|| allowed);
let blocked_instances = (!blocked.is_empty()).then(|| blocked);
Ok(LocalSiteData {
local_site,
allowed_instances,
blocked_instances,
})
}
#[tracing::instrument(skip(settings, local_site_data))]
pub(crate) fn check_apub_id_valid_with_strictness(
apub_id: &Url,
is_strict: bool,
local_site_data: &LocalSiteData,
settings: &Settings,
) -> Result<(), LemmyError> {
check_apub_id_valid(apub_id, settings).map_err(LemmyError::from_message)?;
check_apub_id_valid(apub_id, local_site_data, settings).map_err(LemmyError::from_message)?;
let domain = apub_id.domain().expect("apud id has domain").to_string();
let local_instance = settings
.get_hostname_without_port()
@ -110,15 +167,20 @@ pub(crate) fn check_apub_id_valid_with_strictness(
return Ok(());
}
if let Some(mut allowed) = settings.to_owned().federation.allowed_instances {
if let Some(allowed) = local_site_data.allowed_instances.as_ref() {
// Only check allowlist if this is a community, or strict allowlist is enabled.
let strict_allowlist = settings.to_owned().federation.strict_allowlist;
let strict_allowlist = local_site_data
.local_site
.as_ref()
.map(|l| l.federation_strict_allowlist)
.unwrap_or(true);
if is_strict || strict_allowlist {
// need to allow this explicitly because apub receive might contain objects from our local
// instance.
allowed.push(local_instance);
let mut allowed_and_local = allowed.to_owned();
allowed_and_local.push(local_instance);
if !allowed.contains(&domain) {
if !allowed_and_local.contains(&domain) {
return Err(LemmyError::from_message(
"Federation forbidden by strict allowlist",
));
@ -203,7 +265,7 @@ async fn insert_activity(
let ap_id = ap_id.to_owned().into();
Ok(
blocking(pool, move |conn| {
Activity::insert(conn, ap_id, activity, local, sensitive)
Activity::insert(conn, ap_id, activity, local, Some(sensitive))
})
.await??,
)

@ -1,6 +1,7 @@
use crate::{
activities::{verify_is_public, verify_person_in_community},
check_apub_id_valid_with_strictness,
fetch_local_site_data,
local_instance,
mentions::collect_non_local_mentions,
objects::{read_from_string_or_source, verify_is_remote_object},
@ -18,11 +19,12 @@ use activitypub_federation::{
};
use activitystreams_kinds::{object::NoteType, public};
use chrono::NaiveDateTime;
use lemmy_api_common::utils::blocking;
use lemmy_api_common::utils::{blocking, local_site_opt_to_slur_regex};
use lemmy_db_schema::{
source::{
comment::{Comment, CommentForm},
comment::{Comment, CommentInsertForm, CommentUpdateForm},
community::Community,
local_site::LocalSite,
person::Person,
post::Post,
},
@ -81,7 +83,8 @@ impl ApubObject for ApubComment {
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
if !self.deleted {
blocking(context.pool(), move |conn| {
Comment::update_deleted(conn, self.id, true)
let form = CommentUpdateForm::builder().deleted(Some(true)).build();
Comment::update(conn, self.id, &form)
})
.await??;
}
@ -148,7 +151,14 @@ impl ApubObject for ApubComment {
Community::read(conn, community_id)
})
.await??;
check_apub_id_valid_with_strictness(note.id.inner(), community.local, context.settings())?;
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid_with_strictness(
note.id.inner(),
community.local,
&local_site_data,
context.settings(),
)?;
verify_is_remote_object(note.id.inner(), context.settings())?;
verify_person_in_community(
&note.attributed_to,
@ -179,10 +189,13 @@ impl ApubObject for ApubComment {
let (post, parent_comment) = note.get_parents(context, request_counter).await?;
let content = read_from_string_or_source(&note.content, &note.media_type, &note.source);
let content_slurs_removed = remove_slurs(&content, &context.settings().slur_regex());
let local_site = blocking(context.pool(), LocalSite::read).await?.ok();
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
let content_slurs_removed = remove_slurs(&content, slur_regex);
let language_id = LanguageTag::to_language_id_single(note.language, context.pool()).await?;
let form = CommentForm {
let form = CommentInsertForm {
creator_id: creator.id,
post_id: post.id,
content: content_slurs_removed,
@ -244,6 +257,7 @@ pub(crate) mod tests {
Community::delete(conn, data.1.id).unwrap();
Person::delete(conn, data.0.id).unwrap();
Site::delete(conn, data.3.id).unwrap();
LocalSite::delete(conn).unwrap();
}
#[actix_rt::test]

@ -1,6 +1,7 @@
use crate::{
check_apub_id_valid_with_strictness,
collections::{community_moderators::ApubCommunityModerators, CommunityContext},
fetch_local_site_data,
generate_moderators_url,
generate_outbox_url,
local_instance,
@ -21,8 +22,12 @@ use chrono::NaiveDateTime;
use itertools::Itertools;
use lemmy_api_common::utils::blocking;
use lemmy_db_schema::{
source::{actor_language::CommunityLanguage, community::Community},
traits::ApubActor,
source::{
actor_language::CommunityLanguage,
community::{Community, CommunityUpdateForm},
instance::Instance,
},
traits::{ApubActor, Crud},
};
use lemmy_db_views_actor::structs::CommunityFollowerView;
use lemmy_utils::{
@ -78,7 +83,8 @@ impl ApubObject for ApubCommunity {
#[tracing::instrument(skip_all)]
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
blocking(context.pool(), move |conn| {
Community::update_deleted(conn, self.id, true)
let form = CommunityUpdateForm::builder().deleted(Some(true)).build();
Community::update(conn, self.id, &form)
})
.await??;
Ok(())
@ -138,11 +144,17 @@ impl ApubObject for ApubCommunity {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubCommunity, LemmyError> {
let form = Group::into_form(group.clone());
let apub_id = group.id.inner().to_owned();
let instance = blocking(context.pool(), move |conn| {
Instance::create_from_actor_id(conn, &apub_id)
})
.await??;
let form = Group::into_insert_form(group.clone(), instance.id);
let languages = LanguageTag::to_language_id_multiple(group.language, context.pool()).await?;
let community: ApubCommunity = blocking(context.pool(), move |conn| {
let community = Community::upsert(conn, &form)?;
let community = Community::create(conn, &form)?;
CommunityLanguage::update(conn, languages, community.id)?;
Ok::<Community, diesel::result::Error>(community)
})
@ -205,6 +217,7 @@ impl ApubCommunity {
) -> Result<Vec<Url>, LemmyError> {
let id = self.id;
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
let follows = blocking(context.pool(), move |conn| {
CommunityFollowerView::for_community(conn, id)
})
@ -221,7 +234,10 @@ impl ApubCommunity {
.unique()
.filter(|inbox: &Url| inbox.host_str() != Some(&context.settings().hostname))
// Don't send to blocked instances
.filter(|inbox| check_apub_id_valid_with_strictness(inbox, false, context.settings()).is_ok())
.filter(|inbox| {
check_apub_id_valid_with_strictness(inbox, false, &local_site_data, context.settings())
.is_ok()
})
.collect();
Ok(inboxes)

@ -1,5 +1,6 @@
use crate::{
check_apub_id_valid_with_strictness,
fetch_local_site_data,
local_instance,
objects::read_from_string_or_source_opt,
protocol::{
@ -19,12 +20,14 @@ use activitypub_federation::{
utils::verify_domains_match,
};
use chrono::NaiveDateTime;
use lemmy_api_common::utils::blocking;
use lemmy_api_common::utils::{blocking, local_site_opt_to_slur_regex};
use lemmy_db_schema::{
source::{
actor_language::SiteLanguage,
site::{Site, SiteForm},
instance::Instance as DbInstance,
site::{Site, SiteInsertForm},
},
traits::Crud,
utils::{naive_now, DbPool},
};
use lemmy_utils::{
@ -114,10 +117,13 @@ impl ApubObject for ApubSite {
data: &Self::DataType,
_request_counter: &mut i32,
) -> Result<(), LemmyError> {
check_apub_id_valid_with_strictness(apub.id.inner(), true, data.settings())?;
let local_site_data = blocking(data.pool(), fetch_local_site_data).await??;
check_apub_id_valid_with_strictness(apub.id.inner(), true, &local_site_data, data.settings())?;
verify_domains_match(expected_domain, apub.id.inner())?;
let slur_regex = &data.settings().slur_regex();
let slur_regex = &local_site_opt_to_slur_regex(&local_site_data.local_site);
check_slurs(&apub.name, slur_regex)?;
check_slurs_opt(&apub.summary, slur_regex)?;
Ok(())
@ -129,27 +135,30 @@ impl ApubObject for ApubSite {
data: &Self::DataType,
_request_counter: &mut i32,
) -> Result<Self, LemmyError> {
let site_form = SiteForm {
let apub_id = apub.id.inner().to_owned();
let instance = blocking(data.pool(), move |conn| {
DbInstance::create_from_actor_id(conn, &apub_id)
})
.await??;
let site_form = SiteInsertForm {
name: apub.name.clone(),
sidebar: Some(read_from_string_or_source_opt(
&apub.content,
&None,
&apub.source,
)),
sidebar: read_from_string_or_source_opt(&apub.content, &None, &apub.source),
updated: apub.updated.map(|u| u.clone().naive_local()),
icon: Some(apub.icon.clone().map(|i| i.url.into())),
banner: Some(apub.image.clone().map(|i| i.url.into())),
description: Some(apub.summary.clone()),
icon: apub.icon.clone().map(|i| i.url.into()),
banner: apub.image.clone().map(|i| i.url.into()),
description: apub.summary.clone(),
actor_id: Some(apub.id.clone().into()),
last_refreshed_at: Some(naive_now()),
inbox_url: Some(apub.inbox.clone().into()),
public_key: Some(apub.public_key.public_key_pem.clone()),
..SiteForm::default()
private_key: None,
instance_id: instance.id,
};
let languages = LanguageTag::to_language_id_multiple(apub.language, data.pool()).await?;
let site = blocking(data.pool(), move |conn| {
let site = Site::upsert(conn, &site_form)?;
let site = Site::create(conn, &site_form)?;
SiteLanguage::update(conn, languages, site.id)?;
Ok::<Site, diesel::result::Error>(site)
})

@ -67,7 +67,7 @@ pub(crate) mod tests {
};
use lemmy_utils::{
error::LemmyError,
rate_limit::{rate_limiter::RateLimiter, RateLimit},
rate_limit::{rate_limiter::RateLimiter, RateLimit, RateLimitConfig},
settings::SETTINGS,
};
use lemmy_websocket::{chat_server::ChatServer, LemmyContext};
@ -96,10 +96,6 @@ pub(crate) mod tests {
// call this to run migrations
establish_unpooled_connection();
let settings = SETTINGS.to_owned();
let rate_limiter = RateLimit {
rate_limiter: Arc::new(Mutex::new(RateLimiter::default())),
rate_limit_config: settings.rate_limit.to_owned().unwrap_or_default(),
};
let client = Client::builder()
.user_agent(build_user_agent(&settings))
.build()
@ -122,6 +118,14 @@ pub(crate) mod tests {
async fn x() -> Result<String, LemmyError> {
Ok("".to_string())
}
let rate_limit_config = RateLimitConfig::builder().build();
let rate_limiter = RateLimit {
rate_limiter: Arc::new(Mutex::new(RateLimiter::default())),
rate_limit_config,
};
let chat_server = ChatServer::startup(
pool.clone(),
rate_limiter,

@ -1,5 +1,6 @@
use crate::{
check_apub_id_valid_with_strictness,
fetch_local_site_data,
generate_outbox_url,
objects::{instance::fetch_instance_actor_for_object, read_from_string_or_source_opt},
protocol::{
@ -18,10 +19,13 @@ use activitypub_federation::{
utils::verify_domains_match,
};
use chrono::NaiveDateTime;
use lemmy_api_common::utils::blocking;
use lemmy_api_common::utils::{blocking, local_site_opt_to_slur_regex};
use lemmy_db_schema::{
source::person::{Person as DbPerson, PersonForm},
traits::ApubActor,
source::{
instance::Instance,
person::{Person as DbPerson, PersonInsertForm, PersonUpdateForm},
},
traits::{ApubActor, Crud},
utils::naive_now,
};
use lemmy_utils::{
@ -76,7 +80,8 @@ impl ApubObject for ApubPerson {
#[tracing::instrument(skip_all)]
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
blocking(context.pool(), move |conn| {
DbPerson::update_deleted(conn, self.id, true)
let form = PersonUpdateForm::builder().deleted(Some(true)).build();
DbPerson::update(conn, self.id, &form)
})
.await??;
Ok(())
@ -119,12 +124,20 @@ impl ApubObject for ApubPerson {
context: &LemmyContext,
_request_counter: &mut i32,
) -> Result<(), LemmyError> {
verify_domains_match(person.id.inner(), expected_domain)?;
check_apub_id_valid_with_strictness(person.id.inner(), false, context.settings())?;
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
let slur_regex = &local_site_opt_to_slur_regex(&local_site_data.local_site);
let slur_regex = &context.settings().slur_regex();
check_slurs(&person.preferred_username, slur_regex)?;
check_slurs_opt(&person.name, slur_regex)?;
verify_domains_match(person.id.inner(), expected_domain)?;
check_apub_id_valid_with_strictness(
person.id.inner(),
false,
&local_site_data,
context.settings(),
)?;
let bio = read_from_string_or_source_opt(&person.summary, &None, &person.source);
check_slurs_opt(&bio, slur_regex)?;
Ok(())
@ -136,34 +149,37 @@ impl ApubObject for ApubPerson {
context: &LemmyContext,
request_counter: &mut i32,
) -> Result<ApubPerson, LemmyError> {
let person_form = PersonForm {
let apub_id = person.id.inner().to_owned();
let instance = blocking(context.pool(), move |conn| {
Instance::create_from_actor_id(conn, &apub_id)
})
.await??;
let person_form = PersonInsertForm {
name: person.preferred_username,
display_name: Some(person.name),
display_name: person.name,
banned: None,
ban_expires: None,
deleted: None,
avatar: Some(person.icon.map(|i| i.url.into())),
banner: Some(person.image.map(|i| i.url.into())),
avatar: person.icon.map(|i| i.url.into()),
banner: person.image.map(|i| i.url.into()),
published: person.published.map(|u| u.naive_local()),
updated: person.updated.map(|u| u.naive_local()),
actor_id: Some(person.id.into()),
bio: Some(read_from_string_or_source_opt(
&person.summary,
&None,
&person.source,
)),
bio: read_from_string_or_source_opt(&person.summary, &None, &person.source),
local: Some(false),
admin: Some(false),
bot_account: Some(person.kind == UserTypes::Service),
private_key: None,
public_key: Some(person.public_key.public_key_pem),
public_key: person.public_key.public_key_pem,
last_refreshed_at: Some(naive_now()),
inbox_url: Some(person.inbox.into()),
shared_inbox_url: Some(person.endpoints.map(|e| e.shared_inbox.into())),
matrix_user_id: Some(person.matrix_user_id),
shared_inbox_url: person.endpoints.map(|e| e.shared_inbox.into()),
matrix_user_id: person.matrix_user_id,
instance_id: instance.id,
};
let person = blocking(context.pool(), move |conn| {
DbPerson::upsert(conn, &person_form)
DbPerson::create(conn, &person_form)
})
.await??;
@ -230,22 +246,19 @@ pub(crate) mod tests {
#[serial]
async fn test_parse_lemmy_person() {
let context = init_context();
let conn = &mut context.pool().get().unwrap();
let (person, site) = parse_lemmy_person(&context).await;
assert_eq!(person.display_name, Some("Jean-Luc Picard".to_string()));
assert!(!person.local);
assert_eq!(person.bio.as_ref().unwrap().len(), 39);
DbPerson::delete(conn, person.id).unwrap();
Site::delete(conn, site.id).unwrap();
cleanup((person, site), &context);
}
#[actix_rt::test]
#[serial]
async fn test_parse_pleroma_person() {
let context = init_context();
let conn = &mut context.pool().get().unwrap();
// create and parse a fake pleroma instance actor, to avoid network request during test
let mut json: Instance = file_to_json_object("assets/lemmy/objects/instance.json").unwrap();
@ -272,7 +285,12 @@ pub(crate) mod tests {
assert_eq!(request_counter, 0);
assert_eq!(person.bio.as_ref().unwrap().len(), 873);
DbPerson::delete(conn, person.id).unwrap();
Site::delete(conn, site.id).unwrap();
cleanup((person, site), &context);
}
fn cleanup(data: (ApubPerson, ApubSite), context: &LemmyContext) {
let conn = &mut context.pool().get().unwrap();
DbPerson::delete(conn, data.0.id).unwrap();
Site::delete(conn, data.1.id).unwrap();
}
}

@ -1,6 +1,7 @@
use crate::{
activities::{verify_is_public, verify_person_in_community},
check_apub_id_valid_with_strictness,
fetch_local_site_data,
local_instance,
objects::{read_from_string_or_source_opt, verify_is_remote_object},
protocol::{
@ -20,14 +21,18 @@ use activitypub_federation::{
};
use activitystreams_kinds::public;
use chrono::NaiveDateTime;
use lemmy_api_common::{request::fetch_site_data, utils::blocking};
use lemmy_api_common::{
request::fetch_site_data,
utils::{blocking, local_site_opt_to_slur_regex},
};
use lemmy_db_schema::{
self,
source::{
community::Community,
local_site::LocalSite,
moderator::{ModLockPost, ModLockPostForm, ModStickyPost, ModStickyPostForm},
person::Person,
post::{Post, PostForm},
post::{Post, PostInsertForm, PostUpdateForm},
},
traits::Crud,
};
@ -84,7 +89,8 @@ impl ApubObject for ApubPost {
async fn delete(self, context: &LemmyContext) -> Result<(), LemmyError> {
if !self.deleted {
blocking(context.pool(), move |conn| {
Post::update_deleted(conn, self.id, true)
let form = PostUpdateForm::builder().deleted(Some(true)).build();
Post::update(conn, self.id, &form)
})
.await??;
}
@ -140,10 +146,20 @@ impl ApubObject for ApubPost {
verify_is_remote_object(page.id.inner(), context.settings())?;
};
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
let community = page.extract_community(context, request_counter).await?;
check_apub_id_valid_with_strictness(page.id.inner(), community.local, context.settings())?;
check_apub_id_valid_with_strictness(
page.id.inner(),
community.local,
&local_site_data,
context.settings(),
)?;
verify_person_in_community(&page.creator()?, &community, context, request_counter).await?;
check_slurs(&page.name, &context.settings().slur_regex())?;
let slur_regex = &local_site_opt_to_slur_regex(&local_site_data.local_site);
check_slurs(&page.name, slur_regex)?;
verify_domains_match(page.creator()?.inner(), page.id.inner())?;
verify_is_public(&page.to, &page.cc)?;
Ok(())
@ -181,16 +197,19 @@ impl ApubObject for ApubPost {
(None, page.image.map(|i| i.url.into()))
};
let (embed_title, embed_description, embed_video_url) = metadata_res
.map(|u| (Some(u.title), Some(u.description), Some(u.embed_video_url)))
.map(|u| (u.title, u.description, u.embed_video_url))
.unwrap_or_default();
let local_site = blocking(context.pool(), LocalSite::read).await?.ok();
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
let body_slurs_removed =
read_from_string_or_source_opt(&page.content, &page.media_type, &page.source)
.map(|s| Some(remove_slurs(&s, &context.settings().slur_regex())));
.map(|s| remove_slurs(&s, slur_regex));
let language_id = LanguageTag::to_language_id_single(page.language, context.pool()).await?;
PostForm {
PostInsertForm {
name: page.name.clone(),
url: Some(url.map(Into::into)),
url: url.map(Into::into),
body: body_slurs_removed,
creator_id: creator.id,
community_id: community.id,
@ -204,23 +223,22 @@ impl ApubObject for ApubPost {
embed_title,
embed_description,
embed_video_url,
thumbnail_url: Some(thumbnail_url),
thumbnail_url,
ap_id: Some(page.id.clone().into()),
local: Some(false),
language_id,
}
} else {
// if is mod action, only update locked/stickied fields, nothing else
PostForm {
name: page.name.clone(),
creator_id: creator.id,
community_id: community.id,
locked: page.comments_enabled.map(|e| !e),
stickied: page.stickied,
updated: page.updated.map(|u| u.naive_local()),
ap_id: Some(page.id.clone().into()),
..Default::default()
}
PostInsertForm::builder()
.name(page.name.clone())
.creator_id(creator.id)
.community_id(community.id)
.ap_id(Some(page.id.clone().into()))
.locked(page.comments_enabled.map(|e| !e))
.stickied(page.stickied)
.updated(page.updated.map(|u| u.naive_local()))
.build()
};
// read existing, local post if any (for generating mod log)
@ -228,7 +246,7 @@ impl ApubObject for ApubPost {
.dereference_local(context)
.await;
let post = blocking(context.pool(), move |conn| Post::upsert(conn, &form)).await??;
let post = blocking(context.pool(), move |conn| Post::create(conn, &form)).await??;
// write mod log entries for sticky/lock
if Page::is_stickied_changed(&old_post, &page.stickied) {

@ -1,5 +1,6 @@
use crate::{
check_apub_id_valid_with_strictness,
fetch_local_site_data,
local_instance,
objects::read_from_string_or_source,
protocol::{
@ -18,7 +19,7 @@ use lemmy_api_common::utils::{blocking, check_person_block};
use lemmy_db_schema::{
source::{
person::Person,
private_message::{PrivateMessage, PrivateMessageForm},
private_message::{PrivateMessage, PrivateMessageInsertForm},
},
traits::Crud,
};
@ -108,7 +109,15 @@ impl ApubObject for ApubPrivateMessage {
) -> Result<(), LemmyError> {
verify_domains_match(note.id.inner(), expected_domain)?;
verify_domains_match(note.attributed_to.inner(), note.id.inner())?;
check_apub_id_valid_with_strictness(note.id.inner(), false, context.settings())?;
let local_site_data = blocking(context.pool(), fetch_local_site_data).await??;
check_apub_id_valid_with_strictness(
note.id.inner(),
false,
&local_site_data,
context.settings(),
)?;
let person = note
.attributed_to
.dereference(context, local_instance(context), request_counter)
@ -134,7 +143,7 @@ impl ApubObject for ApubPrivateMessage {
.await?;
check_person_block(creator.id, recipient.id, context.pool()).await?;
let form = PrivateMessageForm {
let form = PrivateMessageInsertForm {
creator_id: creator.id,
recipient_id: recipient.id,
content: read_from_string_or_source(&note.content, &None, &note.source),
@ -146,7 +155,7 @@ impl ApubObject for ApubPrivateMessage {
local: Some(false),
};
let pm = blocking(context.pool(), move |conn| {
PrivateMessage::upsert(conn, &form)
PrivateMessage::create(conn, &form)
})
.await??;
Ok(pm.into())

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

Loading…
Cancel
Save