mirror of
https://github.com/LemmyNet/lemmy
synced 2025-02-20 16:08:59 +00:00
Adding combined inbox (#5257)
* Renaming person_mention to person_comment_mention. * Finishing up post body mentions. * Combined tables try 2 * Finishing up combined report table. * Fix ts optionals. * Adding tests, triggers, and history updates for report_combined. * Adding profile. * Add cursor pagination to report_combined view (#5244) * add pagination cursor * store timestamp instead of id in cursor (partial) * Revert "store timestamp instead of id in cursor (partial)" This reverts commit89359dde4b
. * use paginated query builder * Fixing migration and paged API. * Using dullbananas trigger procedure * Removing pointless list routes, reorganizing tests. * Fixing column XOR check. * Forgot to remove list report actions. * Cleanup. * Use internal tagging. * Fixing api tests. * Adding a few indexes. * Fixing migration name. * Fixing unique constraints. * Addressing PR comments. * Start working on profile combined * Adding views and replaceable schema. * A few changes to profile view. - Separating the profile fetch from its combined content fetch. - Starting to separate saved_only into its own combined view. * Finishing up combined person_saved and person_content. * Fixing api tests. * Moving to api-v4 routes. * Fixing imports. * Update crates/db_views/src/report_combined_view.rs Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Update crates/db_views/src/report_combined_view.rs Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Update crates/db_views/src/report_combined_view.rs Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Update migrations/2024-12-02-181601_add_report_combined_table/up.sql Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Update migrations/2024-12-02-181601_add_report_combined_table/up.sql Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Fixing import and fmt. * Fixing null types in postgres. * Comment out err. * Fixing TS issues. * Adding types, fixing allow and blocklist crud. * Starting to work on combined views. * Using dullbananas trigger procedure * Adding the full combined view queries. * Adding tests. * taplo fmt. * Upgrading package.json deps. * Updating pnpm * Most of the bulk work done, need to add tests yet. * Finishing up inbox. * Using assert_length * Fixing sql_format. * Running fmt. * Fixing cargo shear. * Fixing clippy. * Addressing PR comments. * Removing serialization * Removing serialization * Fixing duped trigger. * Remove saved_only test. * Remove pointless post_tags types. * Remove pointless index. * Changing published to saved for person_saved_combined. * Removing comment. * Renaming modlog when_ columns to published. - Fixes #5312 * Adding strum and simplifying imports. * Avoiding clone in map_to_enum * Changing modded_person to other_person. * Update crates/db_views_moderator/src/modlog_combined_view.rs Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Update crates/db_views_moderator/src/modlog_combined_view.rs Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Update crates/db_views_moderator/src/modlog_combined_view.rs Co-authored-by: dullbananas <dull.bananas0@gmail.com> * Addressing PR comments. * Fixing split. * Revert "Adding strum and simplifying imports." This reverts commit15f1671107
. * Running fmt. * Using assert + matches instead of filter_map. * Adding listPersonContent check. * Updating lemmy-js-client * Fixing mark all as read route, changing mark read to SuccessResponse. * Adding post body mention api test, fixing api tests. * Fixing route locations, and api tests. --------- Co-authored-by: dullbananas <dull.bananas0@gmail.com>
This commit is contained in:
parent
5bc3f0c4d9
commit
3f06317878
68 changed files with 2193 additions and 1507 deletions
Cargo.lock
api_tests
crates
api/src
comment
local_user/notifications
list_inbox.rslist_mentions.rslist_replies.rsmark_all_read.rsmark_comment_mention_read.rsmark_mention_read.rsmark_post_mention_read.rsmark_reply_read.rsmod.rsunread_count.rs
post
private_message
api_common/src
api_crud/src
comment
post
private_message
apub/src/activities/create_or_update
db_schema
replaceable_schema
src
db_views/src
db_views_actor
Cargo.toml
src
routes/src
utils/src
migrations
2024-12-05-233704_add_person_content_combined_table
2024-12-10-193418_add_inbox_combined_table
src
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2752,8 +2752,8 @@ dependencies = [
|
|||
"chrono",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"i-love-jesus",
|
||||
"lemmy_db_schema",
|
||||
"lemmy_db_views",
|
||||
"lemmy_utils",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"eslint": "^9.18.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"jest": "^29.5.0",
|
||||
"lemmy-js-client": "0.20.0-no-delete-token.2",
|
||||
"lemmy-js-client": "0.20.0-inbox-combined.1",
|
||||
"prettier": "^3.4.2",
|
||||
"ts-jest": "^29.1.0",
|
||||
"typescript": "^5.7.3",
|
||||
|
|
10
api_tests/pnpm-lock.yaml
generated
10
api_tests/pnpm-lock.yaml
generated
|
@ -30,8 +30,8 @@ importers:
|
|||
specifier: ^29.5.0
|
||||
version: 29.7.0(@types/node@22.10.6)
|
||||
lemmy-js-client:
|
||||
specifier: 0.20.0-no-delete-token.2
|
||||
version: 0.20.0-no-delete-token.2
|
||||
specifier: 0.20.0-inbox-combined.1
|
||||
version: 0.20.0-inbox-combined.1
|
||||
prettier:
|
||||
specifier: ^3.4.2
|
||||
version: 3.4.2
|
||||
|
@ -1157,8 +1157,8 @@ packages:
|
|||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
lemmy-js-client@0.20.0-no-delete-token.2:
|
||||
resolution: {integrity: sha512-3ra3DpD8XR6RRwCeUDLI/ztFgVuF1IoUoft+xKVDALyupwRWUsA3JcHXRIcFd1a2Qt+pHJtWbc5Iwvybakxwdg==}
|
||||
lemmy-js-client@0.20.0-inbox-combined.1:
|
||||
resolution: {integrity: sha512-sFJJePXdMHIVQwCa3fN+nIcIvfD7ZbBEZn08fmITXEA6/qbJLvZGWG/rEcRNkZM+lRKnhfrZihWKx1AHZE9wqA==}
|
||||
|
||||
leven@3.1.0:
|
||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||
|
@ -3060,7 +3060,7 @@ snapshots:
|
|||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
lemmy-js-client@0.20.0-no-delete-token.2: {}
|
||||
lemmy-js-client@0.20.0-inbox-combined.1: {}
|
||||
|
||||
leven@3.1.0: {}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
editComment,
|
||||
deleteComment,
|
||||
removeComment,
|
||||
getMentions,
|
||||
resolvePost,
|
||||
unfollowRemotes,
|
||||
createCommunity,
|
||||
|
@ -27,7 +26,6 @@ import {
|
|||
getComments,
|
||||
getCommentParentId,
|
||||
resolveCommunity,
|
||||
getReplies,
|
||||
getUnreadCount,
|
||||
waitUntil,
|
||||
waitForPost,
|
||||
|
@ -38,11 +36,14 @@ import {
|
|||
saveUserSettings,
|
||||
listReports,
|
||||
listPersonContent,
|
||||
listInbox,
|
||||
} from "./shared";
|
||||
import {
|
||||
CommentReplyView,
|
||||
CommentReportView,
|
||||
CommentView,
|
||||
CommunityView,
|
||||
PersonCommentMentionView,
|
||||
ReportCombinedView,
|
||||
SaveUserSettings,
|
||||
} from "lemmy-js-client";
|
||||
|
@ -356,7 +357,7 @@ test("Federated comment like", async () => {
|
|||
});
|
||||
|
||||
test("Reply to a comment from another instance, get notification", async () => {
|
||||
await alpha.markAllAsRead();
|
||||
await alpha.markAllNotificationsAsRead();
|
||||
|
||||
let betaCommunity = (
|
||||
await waitUntil(
|
||||
|
@ -423,18 +424,18 @@ test("Reply to a comment from another instance, get notification", async () => {
|
|||
// Did alpha get notified of the reply from beta?
|
||||
let alphaUnreadCountRes = await waitUntil(
|
||||
() => getUnreadCount(alpha),
|
||||
e => e.replies >= 1,
|
||||
e => e.count >= 1,
|
||||
);
|
||||
expect(alphaUnreadCountRes.replies).toBeGreaterThanOrEqual(1);
|
||||
expect(alphaUnreadCountRes.count).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// check inbox of replies on alpha, fetching read/unread both
|
||||
let alphaRepliesRes = await waitUntil(
|
||||
() => getReplies(alpha),
|
||||
r => r.replies.length > 0,
|
||||
);
|
||||
const alphaReply = alphaRepliesRes.replies.find(
|
||||
r => r.comment.id === alphaComment.comment.id,
|
||||
() => listInbox(alpha, "CommentReply"),
|
||||
r => r.inbox.length > 0,
|
||||
);
|
||||
const alphaReply = alphaRepliesRes.inbox.find(
|
||||
r => r.type_ == "CommentReply" && r.comment.id === alphaComment.comment.id,
|
||||
) as CommentReplyView | undefined;
|
||||
expect(alphaReply).toBeDefined();
|
||||
if (!alphaReply) throw Error();
|
||||
expect(alphaReply.comment.content).toBeDefined();
|
||||
|
@ -463,7 +464,7 @@ test("Bot reply notifications are filtered when bots are hidden", async () => {
|
|||
throw "Missing alpha community";
|
||||
}
|
||||
|
||||
await alpha.markAllAsRead();
|
||||
await alpha.markAllNotificationsAsRead();
|
||||
form = {
|
||||
show_bot_accounts: false,
|
||||
};
|
||||
|
@ -478,10 +479,7 @@ test("Bot reply notifications are filtered when bots are hidden", async () => {
|
|||
expect(commentRes).toBeDefined();
|
||||
|
||||
let alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||
expect(alphaUnreadCountRes.replies).toBe(0);
|
||||
|
||||
let alphaUnreadRepliesRes = await getReplies(alpha, true);
|
||||
expect(alphaUnreadRepliesRes.replies.length).toBe(0);
|
||||
expect(alphaUnreadCountRes.count).toBe(0);
|
||||
|
||||
// This both restores the original state that may be expected by other tests
|
||||
// implicitly and is used by the next steps to ensure replies are still
|
||||
|
@ -492,16 +490,16 @@ test("Bot reply notifications are filtered when bots are hidden", async () => {
|
|||
await saveUserSettings(alpha, form);
|
||||
|
||||
alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||
expect(alphaUnreadCountRes.replies).toBe(1);
|
||||
expect(alphaUnreadCountRes.count).toBe(1);
|
||||
|
||||
alphaUnreadRepliesRes = await getReplies(alpha, true);
|
||||
expect(alphaUnreadRepliesRes.replies.length).toBe(1);
|
||||
expect(alphaUnreadRepliesRes.replies[0].comment.id).toBe(
|
||||
let alphaUnreadRepliesRes = await listInbox(alpha, "CommentReply", true);
|
||||
expect(alphaUnreadRepliesRes.inbox.length).toBe(1);
|
||||
expect((alphaUnreadRepliesRes.inbox[0] as CommentReplyView).comment.id).toBe(
|
||||
commentRes.comment_view.comment.id,
|
||||
);
|
||||
});
|
||||
|
||||
test("Mention beta from alpha", async () => {
|
||||
test("Mention beta from alpha comment", async () => {
|
||||
if (!betaCommunity) throw Error("no community");
|
||||
const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
|
||||
// Create a new branch, trunk-level comment branch, from alpha instance
|
||||
|
@ -548,15 +546,17 @@ test("Mention beta from alpha", async () => {
|
|||
assertCommentFederation(betaRootComment, commentRes.comment_view);
|
||||
|
||||
let mentionsRes = await waitUntil(
|
||||
() => getMentions(beta),
|
||||
m => !!m.mentions[0],
|
||||
() => listInbox(beta, "CommentMention"),
|
||||
m => !!m.inbox[0],
|
||||
);
|
||||
expect(mentionsRes.mentions[0].comment.content).toBeDefined();
|
||||
expect(mentionsRes.mentions[0].community.local).toBe(true);
|
||||
expect(mentionsRes.mentions[0].creator.local).toBe(false);
|
||||
expect(mentionsRes.mentions[0].counts.score).toBe(1);
|
||||
|
||||
const firstMention = mentionsRes.inbox[0] as PersonCommentMentionView;
|
||||
expect(firstMention.comment.content).toBeDefined();
|
||||
expect(firstMention.community.local).toBe(true);
|
||||
expect(firstMention.creator.local).toBe(false);
|
||||
expect(firstMention.counts.score).toBe(1);
|
||||
// the reply comment with mention should be the most fresh, newest, index 0
|
||||
expect(mentionsRes.mentions[0].person_mention.comment_id).toBe(
|
||||
expect(firstMention.person_comment_mention.comment_id).toBe(
|
||||
betaPostComments.comments[0].comment.id,
|
||||
);
|
||||
});
|
||||
|
@ -623,15 +623,17 @@ test("A and G subscribe to B (center) A posts, G mentions B, it gets announced t
|
|||
);
|
||||
|
||||
// Make sure beta has mentions
|
||||
let relevantMention = await waitUntil(
|
||||
let relevantMention = (await waitUntil(
|
||||
() =>
|
||||
getMentions(beta).then(m =>
|
||||
m.mentions.find(
|
||||
m => m.comment.ap_id === commentRes.comment_view.comment.ap_id,
|
||||
listInbox(beta, "CommentMention").then(m =>
|
||||
m.inbox.find(
|
||||
m =>
|
||||
m.type_ == "CommentMention" &&
|
||||
m.comment.ap_id === commentRes.comment_view.comment.ap_id,
|
||||
),
|
||||
),
|
||||
e => !!e,
|
||||
);
|
||||
)) as PersonCommentMentionView | undefined;
|
||||
if (!relevantMention) throw Error("could not find mention");
|
||||
expect(relevantMention.comment.content).toBe(commentContent);
|
||||
expect(relevantMention.community.local).toBe(false);
|
||||
|
@ -824,6 +826,7 @@ test("Report a comment", async () => {
|
|||
});
|
||||
|
||||
test("Dont send a comment reply to a blocked community", async () => {
|
||||
await beta.markAllNotificationsAsRead();
|
||||
let newCommunity = await createCommunity(beta);
|
||||
let newCommunityId = newCommunity.community_view.community.id;
|
||||
|
||||
|
@ -837,7 +840,7 @@ test("Dont send a comment reply to a blocked community", async () => {
|
|||
|
||||
// Check beta's inbox count
|
||||
let unreadCount = await getUnreadCount(beta);
|
||||
expect(unreadCount.replies).toBe(1);
|
||||
expect(unreadCount.count).toBe(0);
|
||||
|
||||
// Beta blocks the new beta community
|
||||
let blockRes = await blockCommunity(beta, newCommunityId, true);
|
||||
|
@ -857,10 +860,10 @@ test("Dont send a comment reply to a blocked community", async () => {
|
|||
|
||||
// Check beta's inbox count, make sure it stays the same
|
||||
unreadCount = await getUnreadCount(beta);
|
||||
expect(unreadCount.replies).toBe(1);
|
||||
expect(unreadCount.count).toBe(0);
|
||||
|
||||
let replies = await getReplies(beta);
|
||||
expect(replies.replies.length).toBe(1);
|
||||
let replies = await listInbox(beta, "CommentReply", true);
|
||||
expect(replies.inbox.length).toBe(0);
|
||||
|
||||
// Unblock the community
|
||||
blockRes = await blockCommunity(beta, newCommunityId, false);
|
||||
|
|
|
@ -38,11 +38,13 @@ import {
|
|||
createCommunity,
|
||||
listReports,
|
||||
getMyUser,
|
||||
listInbox,
|
||||
} from "./shared";
|
||||
import { PostView } from "lemmy-js-client/dist/types/PostView";
|
||||
import { AdminBlockInstanceParams } from "lemmy-js-client/dist/types/AdminBlockInstanceParams";
|
||||
import {
|
||||
EditSite,
|
||||
PersonPostMentionView,
|
||||
PostReport,
|
||||
PostReportView,
|
||||
ReportCombinedView,
|
||||
|
@ -799,6 +801,44 @@ test("Fetch post with redirect", async () => {
|
|||
expect(gammaPost2.post).toBeDefined();
|
||||
});
|
||||
|
||||
test("Mention beta from alpha post body", async () => {
|
||||
if (!betaCommunity) throw Error("no community");
|
||||
let mentionContent = "A test mention of @lemmy_beta@lemmy-beta:8551";
|
||||
|
||||
const postOnAlphaRes = await createPost(
|
||||
alpha,
|
||||
betaCommunity.community.id,
|
||||
undefined,
|
||||
mentionContent,
|
||||
);
|
||||
|
||||
expect(postOnAlphaRes.post_view.post.body).toBeDefined();
|
||||
expect(postOnAlphaRes.post_view.community.local).toBe(false);
|
||||
expect(postOnAlphaRes.post_view.creator.local).toBe(true);
|
||||
expect(postOnAlphaRes.post_view.counts.score).toBe(1);
|
||||
|
||||
// get beta's localized copy of the alpha post
|
||||
let betaPost = await waitForPost(beta, postOnAlphaRes.post_view.post);
|
||||
if (!betaPost) {
|
||||
throw "unable to locate post on beta";
|
||||
}
|
||||
expect(betaPost.post.ap_id).toBe(postOnAlphaRes.post_view.post.ap_id);
|
||||
expect(betaPost.post.name).toBe(postOnAlphaRes.post_view.post.name);
|
||||
await assertPostFederation(betaPost, postOnAlphaRes.post_view);
|
||||
|
||||
let mentionsRes = await waitUntil(
|
||||
() => listInbox(beta, "PostMention"),
|
||||
m => !!m.inbox[0],
|
||||
);
|
||||
|
||||
const firstMention = mentionsRes.inbox[0] as PersonPostMentionView;
|
||||
expect(firstMention.post.body).toBeDefined();
|
||||
expect(firstMention.community.local).toBe(true);
|
||||
expect(firstMention.creator.local).toBe(false);
|
||||
expect(firstMention.counts.score).toBe(1);
|
||||
expect(firstMention.person_post_mention.post_id).toBe(betaPost.post.id);
|
||||
});
|
||||
|
||||
test("Rewrite markdown links", async () => {
|
||||
const community = (await resolveBetaCommunity(beta)).community!;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
jest.setTimeout(120000);
|
||||
import { PrivateMessageView } from "lemmy-js-client";
|
||||
import {
|
||||
alpha,
|
||||
beta,
|
||||
|
@ -6,11 +7,11 @@ import {
|
|||
followBeta,
|
||||
createPrivateMessage,
|
||||
editPrivateMessage,
|
||||
listPrivateMessages,
|
||||
deletePrivateMessage,
|
||||
waitUntil,
|
||||
reportPrivateMessage,
|
||||
unfollows,
|
||||
listInbox,
|
||||
} from "./shared";
|
||||
|
||||
let recipient_id: number;
|
||||
|
@ -31,13 +32,14 @@ test("Create a private message", async () => {
|
|||
expect(pmRes.private_message_view.recipient.local).toBe(false);
|
||||
|
||||
let betaPms = await waitUntil(
|
||||
() => listPrivateMessages(beta),
|
||||
e => !!e.private_messages[0],
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
e => !!e.inbox[0],
|
||||
);
|
||||
expect(betaPms.private_messages[0].private_message.content).toBeDefined();
|
||||
expect(betaPms.private_messages[0].private_message.local).toBe(false);
|
||||
expect(betaPms.private_messages[0].creator.local).toBe(false);
|
||||
expect(betaPms.private_messages[0].recipient.local).toBe(true);
|
||||
const firstPm = betaPms.inbox[0] as PrivateMessageView;
|
||||
expect(firstPm.private_message.content).toBeDefined();
|
||||
expect(firstPm.private_message.local).toBe(false);
|
||||
expect(firstPm.creator.local).toBe(false);
|
||||
expect(firstPm.recipient.local).toBe(true);
|
||||
});
|
||||
|
||||
test("Update a private message", async () => {
|
||||
|
@ -53,10 +55,12 @@ test("Update a private message", async () => {
|
|||
);
|
||||
|
||||
let betaPms = await waitUntil(
|
||||
() => listPrivateMessages(beta),
|
||||
p => p.private_messages[0].private_message.content === updatedContent,
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
p =>
|
||||
p.inbox[0].type_ == "PrivateMessage" &&
|
||||
p.inbox[0].private_message.content === updatedContent,
|
||||
);
|
||||
expect(betaPms.private_messages[0].private_message.content).toBe(
|
||||
expect((betaPms.inbox[0] as PrivateMessageView).private_message.content).toBe(
|
||||
updatedContent,
|
||||
);
|
||||
});
|
||||
|
@ -64,12 +68,13 @@ test("Update a private message", async () => {
|
|||
test("Delete a private message", async () => {
|
||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||
let betaPms1 = await waitUntil(
|
||||
() => listPrivateMessages(beta),
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
m =>
|
||||
!!m.private_messages.find(
|
||||
!!m.inbox.find(
|
||||
e =>
|
||||
e.type_ == "PrivateMessage" &&
|
||||
e.private_message.ap_id ===
|
||||
pmRes.private_message_view.private_message.ap_id,
|
||||
pmRes.private_message_view.private_message.ap_id,
|
||||
),
|
||||
);
|
||||
let deletedPmRes = await deletePrivateMessage(
|
||||
|
@ -83,12 +88,10 @@ test("Delete a private message", async () => {
|
|||
// even though they are in the actual database.
|
||||
// no reason to show them
|
||||
let betaPms2 = await waitUntil(
|
||||
() => listPrivateMessages(beta),
|
||||
p => p.private_messages.length === betaPms1.private_messages.length - 1,
|
||||
);
|
||||
expect(betaPms2.private_messages.length).toBe(
|
||||
betaPms1.private_messages.length - 1,
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
p => p.inbox.length === betaPms1.inbox.length - 1,
|
||||
);
|
||||
expect(betaPms2.inbox.length).toBe(betaPms1.inbox.length - 1);
|
||||
|
||||
// Undelete
|
||||
let undeletedPmRes = await deletePrivateMessage(
|
||||
|
@ -101,26 +104,25 @@ test("Delete a private message", async () => {
|
|||
);
|
||||
|
||||
let betaPms3 = await waitUntil(
|
||||
() => listPrivateMessages(beta),
|
||||
p => p.private_messages.length === betaPms1.private_messages.length,
|
||||
);
|
||||
expect(betaPms3.private_messages.length).toBe(
|
||||
betaPms1.private_messages.length,
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
p => p.inbox.length === betaPms1.inbox.length,
|
||||
);
|
||||
expect(betaPms3.inbox.length).toBe(betaPms1.inbox.length);
|
||||
});
|
||||
|
||||
test("Create a private message report", async () => {
|
||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||
let betaPms1 = await waitUntil(
|
||||
() => listPrivateMessages(beta),
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
m =>
|
||||
!!m.private_messages.find(
|
||||
!!m.inbox.find(
|
||||
e =>
|
||||
e.type_ == "PrivateMessage" &&
|
||||
e.private_message.ap_id ===
|
||||
pmRes.private_message_view.private_message.ap_id,
|
||||
pmRes.private_message_view.private_message.ap_id,
|
||||
),
|
||||
);
|
||||
let betaPm = betaPms1.private_messages[0];
|
||||
let betaPm = betaPms1.inbox[0] as PrivateMessageView;
|
||||
expect(betaPm).toBeDefined();
|
||||
|
||||
// Make sure that only the recipient can report it, so this should fail
|
||||
|
|
|
@ -7,8 +7,6 @@ import {
|
|||
CreatePrivateMessageReport,
|
||||
EditCommunity,
|
||||
GetCommunityPendingFollowsCountResponse,
|
||||
GetReplies,
|
||||
GetRepliesResponse,
|
||||
GetUnreadCountResponse,
|
||||
InstanceId,
|
||||
LemmyHttp,
|
||||
|
@ -26,6 +24,9 @@ import {
|
|||
ListPersonContentResponse,
|
||||
ListPersonContent,
|
||||
PersonContentType,
|
||||
ListInboxResponse,
|
||||
ListInbox,
|
||||
InboxDataType,
|
||||
} from "lemmy-js-client";
|
||||
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
||||
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
|
||||
|
@ -59,8 +60,6 @@ import { CreateComment } from "lemmy-js-client/dist/types/CreateComment";
|
|||
import { EditComment } from "lemmy-js-client/dist/types/EditComment";
|
||||
import { DeleteComment } from "lemmy-js-client/dist/types/DeleteComment";
|
||||
import { RemoveComment } from "lemmy-js-client/dist/types/RemoveComment";
|
||||
import { GetPersonMentionsResponse } from "lemmy-js-client/dist/types/GetPersonMentionsResponse";
|
||||
import { GetPersonMentions } from "lemmy-js-client/dist/types/GetPersonMentions";
|
||||
import { CreateCommentLike } from "lemmy-js-client/dist/types/CreateCommentLike";
|
||||
import { CreateCommunity } from "lemmy-js-client/dist/types/CreateCommunity";
|
||||
import { GetCommunity } from "lemmy-js-client/dist/types/GetCommunity";
|
||||
|
@ -75,8 +74,6 @@ import { Register } from "lemmy-js-client/dist/types/Register";
|
|||
import { SaveUserSettings } from "lemmy-js-client/dist/types/SaveUserSettings";
|
||||
import { DeleteAccount } from "lemmy-js-client/dist/types/DeleteAccount";
|
||||
import { GetSiteResponse } from "lemmy-js-client/dist/types/GetSiteResponse";
|
||||
import { PrivateMessagesResponse } from "lemmy-js-client/dist/types/PrivateMessagesResponse";
|
||||
import { GetPrivateMessages } from "lemmy-js-client/dist/types/GetPrivateMessages";
|
||||
import { PostReportResponse } from "lemmy-js-client/dist/types/PostReportResponse";
|
||||
import { CreatePostReport } from "lemmy-js-client/dist/types/CreatePostReport";
|
||||
import { CommentReportResponse } from "lemmy-js-client/dist/types/CommentReportResponse";
|
||||
|
@ -377,15 +374,16 @@ export async function getUnreadCount(
|
|||
return api.getUnreadCount();
|
||||
}
|
||||
|
||||
export async function getReplies(
|
||||
export async function listInbox(
|
||||
api: LemmyHttp,
|
||||
type_?: InboxDataType,
|
||||
unread_only: boolean = false,
|
||||
): Promise<GetRepliesResponse> {
|
||||
let form: GetReplies = {
|
||||
sort: "New",
|
||||
): Promise<ListInboxResponse> {
|
||||
let form: ListInbox = {
|
||||
unread_only,
|
||||
type_,
|
||||
};
|
||||
return api.getReplies(form);
|
||||
return api.listInbox(form);
|
||||
}
|
||||
|
||||
export async function resolveComment(
|
||||
|
@ -542,16 +540,6 @@ export async function removeComment(
|
|||
return api.removeComment(form);
|
||||
}
|
||||
|
||||
export async function getMentions(
|
||||
api: LemmyHttp,
|
||||
): Promise<GetPersonMentionsResponse> {
|
||||
let form: GetPersonMentions = {
|
||||
sort: "New",
|
||||
unread_only: false,
|
||||
};
|
||||
return api.getPersonMentions(form);
|
||||
}
|
||||
|
||||
export async function likeComment(
|
||||
api: LemmyHttp,
|
||||
score: number,
|
||||
|
@ -777,15 +765,6 @@ export async function getMyUser(api: LemmyHttp): Promise<MyUserInfo> {
|
|||
return api.getMyUser();
|
||||
}
|
||||
|
||||
export async function listPrivateMessages(
|
||||
api: LemmyHttp,
|
||||
): Promise<PrivateMessagesResponse> {
|
||||
let form: GetPrivateMessages = {
|
||||
unread_only: false,
|
||||
};
|
||||
return api.getPrivateMessages(form);
|
||||
}
|
||||
|
||||
export async function unfollowRemotes(api: LemmyHttp): Promise<MyUserInfo> {
|
||||
// Unfollow all remote communities
|
||||
let my_user = await getMyUser(api);
|
||||
|
|
|
@ -5,10 +5,10 @@ use lemmy_api_common::{
|
|||
comment::{CommentResponse, CreateCommentLike},
|
||||
context::LemmyContext,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem},
|
||||
utils::{check_bot_account, check_community_user_action, check_local_vote_mode},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::LocalUserId,
|
||||
newtypes::{LocalUserId, PostOrCommentId},
|
||||
source::{
|
||||
comment::{CommentLike, CommentLikeForm},
|
||||
comment_reply::CommentReply,
|
||||
|
@ -33,7 +33,7 @@ pub async fn like_comment(
|
|||
|
||||
check_local_vote_mode(
|
||||
data.score,
|
||||
VoteItem::Comment(comment_id),
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
&local_site,
|
||||
local_user_view.person.id,
|
||||
&mut context.pool(),
|
||||
|
|
40
crates/api/src/local_user/notifications/list_inbox.rs
Normal file
40
crates/api/src/local_user/notifications/list_inbox.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::{ListInbox, ListInboxResponse},
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::inbox_combined_view::InboxCombinedQuery;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_inbox(
|
||||
data: Query<ListInbox>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<ListInboxResponse>> {
|
||||
let unread_only = data.unread_only;
|
||||
let type_ = data.type_;
|
||||
let person_id = local_user_view.person.id;
|
||||
let show_bot_accounts = Some(local_user_view.local_user.show_bot_accounts);
|
||||
|
||||
// parse pagination token
|
||||
let page_after = if let Some(pa) = &data.page_cursor {
|
||||
Some(pa.read(&mut context.pool()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let page_back = data.page_back;
|
||||
|
||||
let inbox = InboxCombinedQuery {
|
||||
type_,
|
||||
unread_only,
|
||||
show_bot_accounts,
|
||||
page_after,
|
||||
page_back,
|
||||
}
|
||||
.list(&mut context.pool(), person_id)
|
||||
.await?;
|
||||
|
||||
Ok(Json(ListInboxResponse { inbox }))
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::{GetPersonMentions, GetPersonMentionsResponse},
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::person_mention_view::PersonMentionQuery;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_mentions(
|
||||
data: Query<GetPersonMentions>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<GetPersonMentionsResponse>> {
|
||||
let sort = data.sort;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only.unwrap_or_default();
|
||||
let person_id = Some(local_user_view.person.id);
|
||||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
|
||||
let mentions = PersonMentionQuery {
|
||||
recipient_id: person_id,
|
||||
my_person_id: person_id,
|
||||
sort,
|
||||
unread_only,
|
||||
show_bot_accounts,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
Ok(Json(GetPersonMentionsResponse { mentions }))
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::{GetReplies, GetRepliesResponse},
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::comment_reply_view::CommentReplyQuery;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn list_replies(
|
||||
data: Query<GetReplies>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<GetRepliesResponse>> {
|
||||
let sort = data.sort;
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only.unwrap_or_default();
|
||||
let person_id = Some(local_user_view.person.id);
|
||||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
|
||||
let replies = CommentReplyQuery {
|
||||
recipient_id: person_id,
|
||||
my_person_id: person_id,
|
||||
sort,
|
||||
unread_only,
|
||||
show_bot_accounts,
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
Ok(Json(GetRepliesResponse { replies }))
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{context::LemmyContext, person::GetRepliesResponse};
|
||||
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
|
||||
use lemmy_db_schema::source::{
|
||||
comment_reply::CommentReply,
|
||||
person_mention::PersonMention,
|
||||
person_comment_mention::PersonCommentMention,
|
||||
person_post_mention::PersonPostMention,
|
||||
private_message::PrivateMessage,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
|
@ -12,7 +13,7 @@ use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
|||
pub async fn mark_all_notifications_read(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<GetRepliesResponse>> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
// Mark all comment_replies as read
|
||||
|
@ -20,15 +21,20 @@ pub async fn mark_all_notifications_read(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
|
||||
|
||||
// Mark all user mentions as read
|
||||
PersonMention::mark_all_as_read(&mut context.pool(), person_id)
|
||||
// Mark all comment mentions as read
|
||||
PersonCommentMention::mark_all_as_read(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
|
||||
|
||||
// Mark all post mentions as read
|
||||
PersonPostMention::mark_all_as_read(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
|
||||
|
||||
// Mark all private_messages as read
|
||||
PrivateMessage::mark_all_as_read(&mut context.pool(), person_id)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
|
||||
|
||||
Ok(Json(GetRepliesResponse { replies: vec![] }))
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::MarkPersonCommentMentionAsRead,
|
||||
SuccessResponse,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::person_comment_mention::{PersonCommentMention, PersonCommentMentionUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_comment_mention_as_read(
|
||||
data: Json<MarkPersonCommentMentionAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let person_comment_mention_id = data.person_comment_mention_id;
|
||||
let read_person_comment_mention =
|
||||
PersonCommentMention::read(&mut context.pool(), person_comment_mention_id).await?;
|
||||
|
||||
if local_user_view.person.id != read_person_comment_mention.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||
}
|
||||
|
||||
let person_comment_mention_id = read_person_comment_mention.id;
|
||||
let read = Some(data.read);
|
||||
PersonCommentMention::update(
|
||||
&mut context.pool(),
|
||||
person_comment_mention_id,
|
||||
&PersonCommentMentionUpdateForm { read },
|
||||
)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::{MarkPersonMentionAsRead, PersonMentionResponse},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::person_mention::{PersonMention, PersonMentionUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PersonMentionView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_person_mention_as_read(
|
||||
data: Json<MarkPersonMentionAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<PersonMentionResponse>> {
|
||||
let person_mention_id = data.person_mention_id;
|
||||
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;
|
||||
|
||||
if local_user_view.person.id != read_person_mention.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||
}
|
||||
|
||||
let person_mention_id = read_person_mention.id;
|
||||
let read = Some(data.read);
|
||||
PersonMention::update(
|
||||
&mut context.pool(),
|
||||
person_mention_id,
|
||||
&PersonMentionUpdateForm { read },
|
||||
)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
|
||||
|
||||
let person_mention_id = read_person_mention.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let person_mention_view =
|
||||
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)).await?;
|
||||
|
||||
Ok(Json(PersonMentionResponse {
|
||||
person_mention_view,
|
||||
}))
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::MarkPersonPostMentionAsRead,
|
||||
SuccessResponse,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::person_post_mention::{PersonPostMention, PersonPostMentionUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn mark_post_mention_as_read(
|
||||
data: Json<MarkPersonPostMentionAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let person_post_mention_id = data.person_post_mention_id;
|
||||
let read_person_post_mention =
|
||||
PersonPostMention::read(&mut context.pool(), person_post_mention_id).await?;
|
||||
|
||||
if local_user_view.person.id != read_person_post_mention.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdatePost)?
|
||||
}
|
||||
|
||||
let person_post_mention_id = read_person_post_mention.id;
|
||||
let read = Some(data.read);
|
||||
PersonPostMention::update(
|
||||
&mut context.pool(),
|
||||
person_post_mention_id,
|
||||
&PersonPostMentionUpdateForm { read },
|
||||
)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::{CommentReplyResponse, MarkCommentReplyAsRead},
|
||||
};
|
||||
use lemmy_api_common::{context::LemmyContext, person::MarkCommentReplyAsRead, SuccessResponse};
|
||||
use lemmy_db_schema::{
|
||||
source::comment_reply::{CommentReply, CommentReplyUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::CommentReplyView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -16,7 +12,7 @@ pub async fn mark_reply_as_read(
|
|||
data: Json<MarkCommentReplyAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<CommentReplyResponse>> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let comment_reply_id = data.comment_reply_id;
|
||||
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;
|
||||
|
||||
|
@ -35,10 +31,5 @@ pub async fn mark_reply_as_read(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
|
||||
|
||||
let comment_reply_id = read_comment_reply.id;
|
||||
let person_id = local_user_view.person.id;
|
||||
let comment_reply_view =
|
||||
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?;
|
||||
|
||||
Ok(Json(CommentReplyResponse { comment_reply_view }))
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub mod list_mentions;
|
||||
pub mod list_replies;
|
||||
pub mod list_inbox;
|
||||
pub mod mark_all_read;
|
||||
pub mod mark_mention_read;
|
||||
pub mod mark_comment_mention_read;
|
||||
pub mod mark_post_mention_read;
|
||||
pub mod mark_reply_read;
|
||||
pub mod unread_count;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::InboxCombinedViewInternal;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -10,20 +10,10 @@ pub async fn unread_count(
|
|||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<GetUnreadCountResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
let replies =
|
||||
CommentReplyView::get_unread_replies(&mut context.pool(), &local_user_view.local_user).await?;
|
||||
|
||||
let mentions =
|
||||
PersonMentionView::get_unread_mentions(&mut context.pool(), &local_user_view.local_user)
|
||||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
let count =
|
||||
InboxCombinedViewInternal::get_unread_count(&mut context.pool(), person_id, show_bot_accounts)
|
||||
.await?;
|
||||
|
||||
let private_messages =
|
||||
PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?;
|
||||
|
||||
Ok(Json(GetUnreadCountResponse {
|
||||
replies,
|
||||
mentions,
|
||||
private_messages,
|
||||
}))
|
||||
Ok(Json(GetUnreadCountResponse { count }))
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@ use lemmy_api_common::{
|
|||
context::LemmyContext,
|
||||
post::{CreatePostLike, PostResponse},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem},
|
||||
utils::{check_bot_account, check_community_user_action, check_local_vote_mode},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
local_site::LocalSite,
|
||||
post::{PostLike, PostLikeForm, PostRead, PostReadForm},
|
||||
|
@ -29,7 +30,7 @@ pub async fn like_post(
|
|||
|
||||
check_local_vote_mode(
|
||||
data.score,
|
||||
VoteItem::Post(post_id),
|
||||
PostOrCommentId::Post(post_id),
|
||||
&local_site,
|
||||
local_user_view.person.id,
|
||||
&mut context.pool(),
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
private_message::{MarkPrivateMessageAsRead, PrivateMessageResponse},
|
||||
private_message::MarkPrivateMessageAsRead,
|
||||
SuccessResponse,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -15,7 +16,7 @@ pub async fn mark_pm_as_read(
|
|||
data: Json<MarkPrivateMessageAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<PrivateMessageResponse>> {
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
// Checking permissions
|
||||
let private_message_id = data.private_message_id;
|
||||
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
|
||||
|
@ -37,8 +38,5 @@ pub async fn mark_pm_as_read(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
|
||||
|
||||
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
|
||||
Ok(Json(PrivateMessageResponse {
|
||||
private_message_view: view,
|
||||
}))
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
||||
|
|
|
@ -12,14 +12,15 @@ use crate::{
|
|||
};
|
||||
use actix_web::web::Json;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommentId, CommunityId, LocalUserId, PostId},
|
||||
newtypes::{CommentId, CommunityId, LocalUserId, PostId, PostOrCommentId},
|
||||
source::{
|
||||
actor_language::CommunityLanguage,
|
||||
comment::Comment,
|
||||
comment_reply::{CommentReply, CommentReplyInsertForm},
|
||||
community::Community,
|
||||
person::Person,
|
||||
person_mention::{PersonMention, PersonMentionInsertForm},
|
||||
person_comment_mention::{PersonCommentMention, PersonCommentMentionInsertForm},
|
||||
person_post_mention::{PersonPostMention, PersonPostMentionInsertForm},
|
||||
post::Post,
|
||||
},
|
||||
traits::Crud,
|
||||
|
@ -94,7 +95,7 @@ pub async fn build_post_response(
|
|||
#[tracing::instrument(skip_all)]
|
||||
pub async fn send_local_notifs(
|
||||
mentions: Vec<MentionData>,
|
||||
comment_id: CommentId,
|
||||
post_or_comment_id: PostOrCommentId,
|
||||
person: &Person,
|
||||
do_send_email: bool,
|
||||
context: &LemmyContext,
|
||||
|
@ -103,27 +104,42 @@ pub async fn send_local_notifs(
|
|||
let mut recipient_ids = Vec::new();
|
||||
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||
|
||||
// When called from api code, we have local user view and can read with CommentView
|
||||
// to reduce db queries. But when receiving a federated comment the user view is None,
|
||||
// which means that comments inside private communities cant be read. As a workaround
|
||||
// we need to read the items manually to bypass this check.
|
||||
let (comment, post, community) = if let Some(local_user_view) = local_user_view {
|
||||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
comment_view.comment,
|
||||
comment_view.post,
|
||||
comment_view.community,
|
||||
)
|
||||
} else {
|
||||
let comment = Comment::read(&mut context.pool(), comment_id).await?;
|
||||
let post = Post::read(&mut context.pool(), comment.post_id).await?;
|
||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||
(comment, post, community)
|
||||
let (comment_opt, post, community) = match post_or_comment_id {
|
||||
PostOrCommentId::Post(post_id) => {
|
||||
let post_view = PostView::read(
|
||||
&mut context.pool(),
|
||||
post_id,
|
||||
local_user_view.map(|view| &view.local_user),
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
(None, post_view.post, post_view.community)
|
||||
}
|
||||
PostOrCommentId::Comment(comment_id) => {
|
||||
// When called from api code, we have local user view and can read with CommentView
|
||||
// to reduce db queries. But when receiving a federated comment the user view is None,
|
||||
// which means that comments inside private communities cant be read. As a workaround
|
||||
// we need to read the items manually to bypass this check.
|
||||
if let Some(local_user_view) = local_user_view {
|
||||
// Read the comment view to get extra info
|
||||
let comment_view = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
Some(&local_user_view.local_user),
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
Some(comment_view.comment),
|
||||
comment_view.post,
|
||||
comment_view.community,
|
||||
)
|
||||
} else {
|
||||
let comment = Comment::read(&mut context.pool(), comment_id).await?;
|
||||
let post = Post::read(&mut context.pool(), comment.post_id).await?;
|
||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||
(Some(comment), post, community)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Send the local mentions
|
||||
|
@ -140,22 +156,38 @@ pub async fn send_local_notifs(
|
|||
// below by checking recipient ids
|
||||
recipient_ids.push(mention_user_view.local_user.id);
|
||||
|
||||
let user_mention_form = PersonMentionInsertForm {
|
||||
recipient_id: mention_user_view.person.id,
|
||||
comment_id,
|
||||
read: None,
|
||||
};
|
||||
// Make the correct reply form depending on whether its a post or comment mention
|
||||
let comment_content_or_post_body = if let Some(comment) = &comment_opt {
|
||||
let person_comment_mention_form = PersonCommentMentionInsertForm {
|
||||
recipient_id: mention_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
PersonMention::create(&mut context.pool(), &user_mention_form)
|
||||
.await
|
||||
.ok();
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
PersonCommentMention::create(&mut context.pool(), &person_comment_mention_form)
|
||||
.await
|
||||
.ok();
|
||||
comment.content.clone()
|
||||
} else {
|
||||
let person_post_mention_form = PersonPostMentionInsertForm {
|
||||
recipient_id: mention_user_view.person.id,
|
||||
post_id: post.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since edits might re-update or replace it
|
||||
PersonPostMention::create(&mut context.pool(), &person_post_mention_form)
|
||||
.await
|
||||
.ok();
|
||||
post.body.clone().unwrap_or_default()
|
||||
};
|
||||
|
||||
// Send an email to those local users that have notifications on
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&mention_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
let content = markdown_to_html(&comment_content_or_post_body);
|
||||
send_email_to_user(
|
||||
&mention_user_view,
|
||||
&lang.notification_mentioned_by_subject(&person.name),
|
||||
|
@ -168,99 +200,101 @@ pub async fn send_local_notifs(
|
|||
}
|
||||
|
||||
// Send comment_reply to the parent commenter / poster
|
||||
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
|
||||
if let Some(comment) = &comment_opt {
|
||||
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
|
||||
|
||||
// Get the parent commenter local_user
|
||||
let parent_creator_id = parent_comment.creator_id;
|
||||
// Get the parent commenter local_user
|
||||
let parent_creator_id = parent_comment.creator_id;
|
||||
|
||||
let check_blocks = check_person_instance_community_block(
|
||||
person.id,
|
||||
parent_creator_id,
|
||||
// Only block from the community's instance_id
|
||||
community.instance_id,
|
||||
community.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await
|
||||
.is_err();
|
||||
let check_blocks = check_person_instance_community_block(
|
||||
person.id,
|
||||
parent_creator_id,
|
||||
// Only block from the community's instance_id
|
||||
community.instance_id,
|
||||
community.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await
|
||||
.is_err();
|
||||
|
||||
// Don't send a notif to yourself
|
||||
if parent_comment.creator_id != person.id && !check_blocks {
|
||||
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
|
||||
if let Ok(parent_user_view) = user_view {
|
||||
// Don't duplicate notif if already mentioned by checking recipient ids
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
// Don't send a notif to yourself
|
||||
if parent_comment.creator_id != person.id && !check_blocks {
|
||||
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
|
||||
if let Ok(parent_user_view) = user_view {
|
||||
// Don't duplicate notif if already mentioned by checking recipient ids
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_comment_reply_subject(&person.name),
|
||||
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_comment_reply_subject(&person.name),
|
||||
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use the post creator to check blocks
|
||||
let check_blocks = check_person_instance_community_block(
|
||||
person.id,
|
||||
post.creator_id,
|
||||
// Only block from the community's instance_id
|
||||
community.instance_id,
|
||||
community.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await
|
||||
.is_err();
|
||||
} else {
|
||||
// Use the post creator to check blocks
|
||||
let check_blocks = check_person_instance_community_block(
|
||||
person.id,
|
||||
post.creator_id,
|
||||
// Only block from the community's instance_id
|
||||
community.instance_id,
|
||||
community.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await
|
||||
.is_err();
|
||||
|
||||
if post.creator_id != person.id && !check_blocks {
|
||||
let creator_id = post.creator_id;
|
||||
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
|
||||
if let Ok(parent_user_view) = parent_user {
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
if post.creator_id != person.id && !check_blocks {
|
||||
let creator_id = post.creator_id;
|
||||
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
|
||||
if let Ok(parent_user_view) = parent_user {
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
use lemmy_db_schema::{
|
||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||
newtypes::{
|
||||
CommentReplyId,
|
||||
CommunityId,
|
||||
LanguageId,
|
||||
PersonCommentMentionId,
|
||||
PersonId,
|
||||
PersonPostMentionId,
|
||||
},
|
||||
sensitive::SensitiveString,
|
||||
source::{login_token::LoginToken, site::Site},
|
||||
CommentSortType,
|
||||
InboxDataType,
|
||||
ListingType,
|
||||
PersonContentType,
|
||||
PostListingMode,
|
||||
|
@ -15,9 +23,9 @@ use lemmy_db_views::structs::{
|
|||
PersonSavedCombinedPaginationCursor,
|
||||
};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommentReplyView,
|
||||
CommunityModeratorView,
|
||||
PersonMentionView,
|
||||
InboxCombinedPaginationCursor,
|
||||
InboxCombinedView,
|
||||
PersonView,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -364,69 +372,45 @@ pub struct BlockPersonResponse {
|
|||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Get comment replies.
|
||||
pub struct GetReplies {
|
||||
/// Get your inbox (replies, comment mentions, post mentions, and messages)
|
||||
pub struct ListInbox {
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub sort: Option<CommentSortType>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page: Option<i64>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub limit: Option<i64>,
|
||||
pub type_: Option<InboxDataType>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub unread_only: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Fetches your replies.
|
||||
// TODO, replies and mentions below should be redone as tagged enums.
|
||||
pub struct GetRepliesResponse {
|
||||
pub replies: Vec<CommentReplyView>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Get mentions for your user.
|
||||
pub struct GetPersonMentions {
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub sort: Option<CommentSortType>,
|
||||
pub page_cursor: Option<InboxCombinedPaginationCursor>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page: Option<i64>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub limit: Option<i64>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub unread_only: Option<bool>,
|
||||
pub page_back: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The response of mentions for your user.
|
||||
pub struct GetPersonMentionsResponse {
|
||||
pub mentions: Vec<PersonMentionView>,
|
||||
/// Get your inbox (replies, comment mentions, post mentions, and messages)
|
||||
pub struct ListInboxResponse {
|
||||
pub inbox: Vec<InboxCombinedView>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Mark a person mention as read.
|
||||
pub struct MarkPersonMentionAsRead {
|
||||
pub person_mention_id: PersonMentionId,
|
||||
pub struct MarkPersonCommentMentionAsRead {
|
||||
pub person_comment_mention_id: PersonCommentMentionId,
|
||||
pub read: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The response for a person mention action.
|
||||
pub struct PersonMentionResponse {
|
||||
pub person_mention_view: PersonMentionView,
|
||||
/// Mark a person mention as read.
|
||||
pub struct MarkPersonPostMentionAsRead {
|
||||
pub person_post_mention_id: PersonPostMentionId,
|
||||
pub read: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
|
@ -438,14 +422,6 @@ pub struct MarkCommentReplyAsRead {
|
|||
pub read: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The response for a comment reply action.
|
||||
pub struct CommentReplyResponse {
|
||||
pub comment_reply_view: CommentReplyView,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
@ -495,11 +471,9 @@ pub struct GetReportCountResponse {
|
|||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A response containing counts for your notifications.
|
||||
/// A response containing a count of unread notifications.
|
||||
pub struct GetUnreadCountResponse {
|
||||
pub replies: i64,
|
||||
pub mentions: i64,
|
||||
pub private_messages: i64,
|
||||
pub count: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use lemmy_db_schema::newtypes::{PersonId, PrivateMessageId};
|
||||
use lemmy_db_views::structs::PrivateMessageView;
|
||||
use lemmy_db_views_actor::structs::PrivateMessageView;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
use ts_rs::TS;
|
||||
|
||||
|
@ -41,30 +40,6 @@ pub struct MarkPrivateMessageAsRead {
|
|||
pub read: bool,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// Get your private messages.
|
||||
pub struct GetPrivateMessages {
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub unread_only: Option<bool>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub page: Option<i64>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub limit: Option<i64>,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub creator_id: Option<PersonId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The private messages response.
|
||||
pub struct PrivateMessagesResponse {
|
||||
pub private_messages: Vec<PrivateMessageView>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
|
|
@ -11,7 +11,7 @@ use lemmy_db_schema::{
|
|||
private_message::PrivateMessage,
|
||||
},
|
||||
};
|
||||
use lemmy_db_views::structs::PrivateMessageView;
|
||||
use lemmy_db_views_actor::structs::PrivateMessageView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
use tokio::{
|
||||
|
|
|
@ -11,7 +11,7 @@ use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
|||
use enum_map::{enum_map, EnumMap};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||
newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId},
|
||||
newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId, PostOrCommentId},
|
||||
source::{
|
||||
comment::{Comment, CommentLike, CommentUpdateForm},
|
||||
community::{Community, CommunityModerator, CommunityUpdateForm},
|
||||
|
@ -291,23 +291,17 @@ pub async fn check_person_instance_community_block(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// A vote item type used to check the vote mode.
|
||||
pub enum VoteItem {
|
||||
Post(PostId),
|
||||
Comment(CommentId),
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn check_local_vote_mode(
|
||||
score: i16,
|
||||
vote_item: VoteItem,
|
||||
post_or_comment_id: PostOrCommentId,
|
||||
local_site: &LocalSite,
|
||||
person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let (downvote_setting, upvote_setting) = match vote_item {
|
||||
VoteItem::Post(_) => (local_site.post_downvotes, local_site.post_upvotes),
|
||||
VoteItem::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
|
||||
let (downvote_setting, upvote_setting) = match post_or_comment_id {
|
||||
PostOrCommentId::Post(_) => (local_site.post_downvotes, local_site.post_upvotes),
|
||||
PostOrCommentId::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
|
||||
};
|
||||
|
||||
let downvote_fail = score == -1 && downvote_setting == FederationMode::Disable;
|
||||
|
@ -315,9 +309,11 @@ pub async fn check_local_vote_mode(
|
|||
|
||||
// Undo previous vote for item if new vote fails
|
||||
if downvote_fail || upvote_fail {
|
||||
match vote_item {
|
||||
VoteItem::Post(post_id) => PostLike::remove(pool, person_id, post_id).await?,
|
||||
VoteItem::Comment(comment_id) => CommentLike::remove(pool, person_id, comment_id).await?,
|
||||
match post_or_comment_id {
|
||||
PostOrCommentId::Post(post_id) => PostLike::remove(pool, person_id, post_id).await?,
|
||||
PostOrCommentId::Comment(comment_id) => {
|
||||
CommentLike::remove(pool, person_id, comment_id).await?
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -17,11 +17,12 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
|
||||
comment_reply::{CommentReply, CommentReplyUpdateForm},
|
||||
local_site::LocalSite,
|
||||
person_mention::{PersonMention, PersonMentionUpdateForm},
|
||||
person_comment_mention::{PersonCommentMention, PersonCommentMentionUpdateForm},
|
||||
},
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
|
@ -117,7 +118,7 @@ pub async fn create_comment(
|
|||
let mentions = scrape_text_for_mentions(&content);
|
||||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
inserted_comment_id,
|
||||
PostOrCommentId::Comment(inserted_comment_id),
|
||||
&local_user_view.person,
|
||||
true,
|
||||
&context,
|
||||
|
@ -169,17 +170,18 @@ pub async fn create_comment(
|
|||
.with_lemmy_type(LemmyErrorType::CouldntUpdateReplies)?;
|
||||
}
|
||||
|
||||
// If the parent has PersonMentions mark them as read too
|
||||
let person_mention =
|
||||
PersonMention::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await;
|
||||
if let Ok(Some(mention)) = person_mention {
|
||||
PersonMention::update(
|
||||
// If the parent has PersonCommentMentions mark them as read too
|
||||
let person_comment_mention =
|
||||
PersonCommentMention::read_by_comment_and_person(&mut context.pool(), parent_id, person_id)
|
||||
.await;
|
||||
if let Ok(Some(mention)) = person_comment_mention {
|
||||
PersonCommentMention::update(
|
||||
&mut context.pool(),
|
||||
mention.id,
|
||||
&PersonMentionUpdateForm { read: Some(true) },
|
||||
&PersonCommentMentionUpdateForm { read: Some(true) },
|
||||
)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePersonMentions)?;
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePersonCommentMentions)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use lemmy_api_common::{
|
|||
utils::check_community_user_action,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PostOrCommentId,
|
||||
source::comment::{Comment, CommentUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
|
@ -60,7 +61,7 @@ pub async fn delete_comment(
|
|||
|
||||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
comment_id,
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
|
|
|
@ -8,6 +8,7 @@ use lemmy_api_common::{
|
|||
utils::check_community_mod_action,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
comment_report::CommentReport,
|
||||
|
@ -82,7 +83,7 @@ pub async fn remove_comment(
|
|||
|
||||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
comment_id,
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
|
|
|
@ -15,6 +15,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
local_site::LocalSite,
|
||||
|
@ -86,7 +87,7 @@ pub async fn update_comment(
|
|||
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
||||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
comment_id,
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::convert_published_time;
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
build_response::build_post_response,
|
||||
build_response::{build_post_response, send_local_notifs},
|
||||
context::LemmyContext,
|
||||
post::{CreatePost, PostResponse},
|
||||
request::generate_post_link_metadata,
|
||||
|
@ -17,6 +17,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
|
@ -32,6 +33,7 @@ use lemmy_utils::{
|
|||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
spawn_try_task,
|
||||
utils::{
|
||||
mention::scrape_text_for_mentions,
|
||||
slurs::check_slurs,
|
||||
validation::{
|
||||
is_url_blocked,
|
||||
|
@ -151,6 +153,18 @@ pub async fn create_post(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
||||
|
||||
// Scan the post body for user mentions, add those rows
|
||||
let mentions = scrape_text_for_mentions(&inserted_post.body.clone().unwrap_or_default());
|
||||
send_local_notifs(
|
||||
mentions,
|
||||
PostOrCommentId::Post(inserted_post.id),
|
||||
&local_user_view.person,
|
||||
true,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let read_form = PostReadForm::new(post_id, person_id);
|
||||
PostRead::mark_as_read(&mut context.pool(), &read_form).await?;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use activitypub_federation::config::Data;
|
|||
use actix_web::web::Json;
|
||||
use chrono::Utc;
|
||||
use lemmy_api_common::{
|
||||
build_response::build_post_response,
|
||||
build_response::{build_post_response, send_local_notifs},
|
||||
context::LemmyContext,
|
||||
post::{EditPost, PostResponse},
|
||||
request::generate_post_link_metadata,
|
||||
|
@ -17,6 +17,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
community::Community,
|
||||
local_site::LocalSite,
|
||||
|
@ -29,6 +30,7 @@ use lemmy_db_views::structs::{LocalUserView, PostView};
|
|||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::{
|
||||
mention::scrape_text_for_mentions,
|
||||
slurs::check_slurs,
|
||||
validation::{
|
||||
is_url_blocked,
|
||||
|
@ -142,6 +144,18 @@ pub async fn update_post(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
|
||||
|
||||
// Scan the post body for user mentions, add those rows
|
||||
let mentions = scrape_text_for_mentions(&updated_post.body.clone().unwrap_or_default());
|
||||
send_local_notifs(
|
||||
mentions,
|
||||
PostOrCommentId::Post(updated_post.id),
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// send out federation/webmention if necessary
|
||||
match (
|
||||
orig_post.post.scheduled_publish_time,
|
||||
|
|
|
@ -21,7 +21,8 @@ use lemmy_db_schema::{
|
|||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PrivateMessageView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::{markdown::markdown_to_html, validation::is_valid_body_field},
|
||||
|
|
|
@ -9,7 +9,8 @@ use lemmy_db_schema::{
|
|||
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PrivateMessageView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod read;
|
||||
pub mod update;
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
private_message::{GetPrivateMessages, PrivateMessagesResponse},
|
||||
};
|
||||
use lemmy_db_views::{private_message_view::PrivateMessageQuery, structs::LocalUserView};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn get_private_message(
|
||||
data: Query<GetPrivateMessages>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<PrivateMessagesResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let unread_only = data.unread_only.unwrap_or_default();
|
||||
let creator_id = data.creator_id;
|
||||
let messages = PrivateMessageQuery {
|
||||
page,
|
||||
limit,
|
||||
unread_only,
|
||||
creator_id,
|
||||
}
|
||||
.list(&mut context.pool(), person_id)
|
||||
.await?;
|
||||
|
||||
Ok(Json(PrivateMessagesResponse {
|
||||
private_messages: messages,
|
||||
}))
|
||||
}
|
|
@ -14,7 +14,8 @@ use lemmy_db_schema::{
|
|||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_actor::structs::PrivateMessageView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
utils::validation::is_valid_body_field,
|
||||
|
|
|
@ -29,7 +29,7 @@ use lemmy_api_common::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::CommentAggregates,
|
||||
newtypes::PersonId,
|
||||
newtypes::{PersonId, PostOrCommentId},
|
||||
source::{
|
||||
activity::ActivitySendTargets,
|
||||
comment::{Comment, CommentLike, CommentLikeForm},
|
||||
|
@ -175,10 +175,17 @@ impl ActivityHandler for CreateOrUpdateNote {
|
|||
// TODO: for compatibility with other projects, it would be much better to read this from cc or
|
||||
// tags
|
||||
let mentions = scrape_text_for_mentions(&comment.content);
|
||||
|
||||
// TODO: this fails in local community comment as CommentView::read() returns nothing
|
||||
// without passing LocalUser
|
||||
send_local_notifs(mentions, comment.id, &actor, do_send_email, context, None).await?;
|
||||
send_local_notifs(
|
||||
mentions,
|
||||
PostOrCommentId::Comment(comment.id),
|
||||
&actor,
|
||||
do_send_email,
|
||||
context,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,10 +20,10 @@ use activitypub_federation::{
|
|||
protocol::verification::{verify_domains_match, verify_urls_match},
|
||||
traits::{ActivityHandler, Actor, Object},
|
||||
};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_api_common::{build_response::send_local_notifs, context::LemmyContext};
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::PostAggregates,
|
||||
newtypes::PersonId,
|
||||
newtypes::{PersonId, PostOrCommentId},
|
||||
source::{
|
||||
activity::ActivitySendTargets,
|
||||
community::Community,
|
||||
|
@ -32,7 +32,10 @@ use lemmy_db_schema::{
|
|||
},
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyResult},
|
||||
utils::mention::scrape_text_for_mentions,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
impl CreateOrUpdatePage {
|
||||
|
@ -123,6 +126,21 @@ impl ActivityHandler for CreateOrUpdatePage {
|
|||
// Calculate initial hot_rank for post
|
||||
PostAggregates::update_ranks(&mut context.pool(), post.id).await?;
|
||||
|
||||
let do_send_email = self.kind == CreateOrUpdateType::Create;
|
||||
let actor = self.actor.dereference(context).await?;
|
||||
|
||||
// Send the post body mentions
|
||||
let mentions = scrape_text_for_mentions(&post.body.clone().unwrap_or_default());
|
||||
send_local_notifs(
|
||||
mentions,
|
||||
PostOrCommentId::Post(post.id),
|
||||
&actor,
|
||||
do_send_email,
|
||||
context,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use activitypub_federation::{
|
|||
};
|
||||
use lemmy_api_common::context::LemmyContext;
|
||||
use lemmy_db_schema::source::activity::ActivitySendTargets;
|
||||
use lemmy_db_views::structs::PrivateMessageView;
|
||||
use lemmy_db_views_actor::structs::PrivateMessageView;
|
||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||
use url::Url;
|
||||
|
||||
|
|
|
@ -857,3 +857,35 @@ CALL r.create_modlog_combined_trigger ('mod_remove_post');
|
|||
|
||||
CALL r.create_modlog_combined_trigger ('mod_transfer_community');
|
||||
|
||||
-- Inbox: (replies, comment mentions, post mentions, and private_messages)
|
||||
CREATE PROCEDURE r.create_inbox_combined_trigger (table_name text)
|
||||
LANGUAGE plpgsql
|
||||
AS $a$
|
||||
BEGIN
|
||||
EXECUTE replace($b$ CREATE FUNCTION r.inbox_combined_thing_insert ( )
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO inbox_combined (published, thing_id)
|
||||
VALUES (NEW.published, NEW.id);
|
||||
RETURN NEW;
|
||||
END $$;
|
||||
CREATE TRIGGER inbox_combined
|
||||
AFTER INSERT ON thing
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION r.inbox_combined_thing_insert ( );
|
||||
$b$,
|
||||
'thing',
|
||||
table_name);
|
||||
END;
|
||||
$a$;
|
||||
|
||||
CALL r.create_inbox_combined_trigger ('comment_reply');
|
||||
|
||||
CALL r.create_inbox_combined_trigger ('person_comment_mention');
|
||||
|
||||
CALL r.create_inbox_combined_trigger ('person_post_mention');
|
||||
|
||||
CALL r.create_inbox_combined_trigger ('private_message');
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ pub mod oauth_provider;
|
|||
pub mod password_reset_request;
|
||||
pub mod person;
|
||||
pub mod person_block;
|
||||
pub mod person_mention;
|
||||
pub mod person_comment_mention;
|
||||
pub mod person_post_mention;
|
||||
pub mod post;
|
||||
pub mod post_report;
|
||||
pub mod private_message;
|
||||
|
|
83
crates/db_schema/src/impls/person_comment_mention.rs
Normal file
83
crates/db_schema/src/impls/person_comment_mention.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{CommentId, PersonCommentMentionId, PersonId},
|
||||
schema::person_comment_mention,
|
||||
source::person_comment_mention::{
|
||||
PersonCommentMention,
|
||||
PersonCommentMentionInsertForm,
|
||||
PersonCommentMentionUpdateForm,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
#[async_trait]
|
||||
impl Crud for PersonCommentMention {
|
||||
type InsertForm = PersonCommentMentionInsertForm;
|
||||
type UpdateForm = PersonCommentMentionUpdateForm;
|
||||
type IdType = PersonCommentMentionId;
|
||||
|
||||
async fn create(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_comment_mention_form: &Self::InsertForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
// since the return here isnt utilized, we dont need to do an update
|
||||
// but get_result doesn't return the existing row here
|
||||
insert_into(person_comment_mention::table)
|
||||
.values(person_comment_mention_form)
|
||||
.on_conflict((
|
||||
person_comment_mention::recipient_id,
|
||||
person_comment_mention::comment_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(person_comment_mention_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_comment_mention_id: PersonCommentMentionId,
|
||||
person_comment_mention_form: &Self::UpdateForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(person_comment_mention::table.find(person_comment_mention_id))
|
||||
.set(person_comment_mention_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonCommentMention {
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<Vec<PersonCommentMention>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(
|
||||
person_comment_mention::table
|
||||
.filter(person_comment_mention::recipient_id.eq(for_recipient_id))
|
||||
.filter(person_comment_mention::read.eq(false)),
|
||||
)
|
||||
.set(person_comment_mention::read.eq(true))
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read_by_comment_and_person(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_comment_id: CommentId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
person_comment_mention::table
|
||||
.filter(person_comment_mention::comment_id.eq(for_comment_id))
|
||||
.filter(person_comment_mention::recipient_id.eq(for_recipient_id))
|
||||
.first(conn)
|
||||
.await
|
||||
.optional()
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{CommentId, PersonId, PersonMentionId},
|
||||
schema::person_mention,
|
||||
source::person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
#[async_trait]
|
||||
impl Crud for PersonMention {
|
||||
type InsertForm = PersonMentionInsertForm;
|
||||
type UpdateForm = PersonMentionUpdateForm;
|
||||
type IdType = PersonMentionId;
|
||||
|
||||
async fn create(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_mention_form: &Self::InsertForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
// since the return here isnt utilized, we dont need to do an update
|
||||
// but get_result doesn't return the existing row here
|
||||
insert_into(person_mention::table)
|
||||
.values(person_mention_form)
|
||||
.on_conflict((person_mention::recipient_id, person_mention::comment_id))
|
||||
.do_update()
|
||||
.set(person_mention_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_mention_id: PersonMentionId,
|
||||
person_mention_form: &Self::UpdateForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(person_mention::table.find(person_mention_id))
|
||||
.set(person_mention_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonMention {
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<Vec<PersonMention>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(
|
||||
person_mention::table
|
||||
.filter(person_mention::recipient_id.eq(for_recipient_id))
|
||||
.filter(person_mention::read.eq(false)),
|
||||
)
|
||||
.set(person_mention::read.eq(true))
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read_by_comment_and_person(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_comment_id: CommentId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
person_mention::table
|
||||
.filter(person_mention::comment_id.eq(for_comment_id))
|
||||
.filter(person_mention::recipient_id.eq(for_recipient_id))
|
||||
.first(conn)
|
||||
.await
|
||||
.optional()
|
||||
}
|
||||
}
|
83
crates/db_schema/src/impls/person_post_mention.rs
Normal file
83
crates/db_schema/src/impls/person_post_mention.rs
Normal file
|
@ -0,0 +1,83 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{PersonId, PersonPostMentionId, PostId},
|
||||
schema::person_post_mention,
|
||||
source::person_post_mention::{
|
||||
PersonPostMention,
|
||||
PersonPostMentionInsertForm,
|
||||
PersonPostMentionUpdateForm,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
#[async_trait]
|
||||
impl Crud for PersonPostMention {
|
||||
type InsertForm = PersonPostMentionInsertForm;
|
||||
type UpdateForm = PersonPostMentionUpdateForm;
|
||||
type IdType = PersonPostMentionId;
|
||||
|
||||
async fn create(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_post_mention_form: &Self::InsertForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
// since the return here isnt utilized, we dont need to do an update
|
||||
// but get_result doesn't return the existing row here
|
||||
insert_into(person_post_mention::table)
|
||||
.values(person_post_mention_form)
|
||||
.on_conflict((
|
||||
person_post_mention::recipient_id,
|
||||
person_post_mention::post_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(person_post_mention_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_post_mention_id: PersonPostMentionId,
|
||||
person_post_mention_form: &Self::UpdateForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(person_post_mention::table.find(person_post_mention_id))
|
||||
.set(person_post_mention_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonPostMention {
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<Vec<PersonPostMention>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(
|
||||
person_post_mention::table
|
||||
.filter(person_post_mention::recipient_id.eq(for_recipient_id))
|
||||
.filter(person_post_mention::read.eq(false)),
|
||||
)
|
||||
.set(person_post_mention::read.eq(true))
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read_by_post_and_person(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_post_id: PostId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> Result<Option<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
person_post_mention::table
|
||||
.filter(person_post_mention::post_id.eq(for_post_id))
|
||||
.filter(person_post_mention::recipient_id.eq(for_recipient_id))
|
||||
.first(conn)
|
||||
.await
|
||||
.optional()
|
||||
}
|
||||
}
|
|
@ -214,6 +214,18 @@ pub enum ModlogActionType {
|
|||
AdminAllowInstance,
|
||||
}
|
||||
|
||||
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A list of possible types for the inbox.
|
||||
pub enum InboxDataType {
|
||||
All,
|
||||
CommentReply,
|
||||
CommentMention,
|
||||
PostMention,
|
||||
PrivateMessage,
|
||||
}
|
||||
|
||||
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
|
|
@ -55,6 +55,11 @@ impl fmt::Display for CommentId {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum PostOrCommentId {
|
||||
Post(PostId),
|
||||
Comment(CommentId),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
@ -71,7 +76,7 @@ pub struct LocalUserId(pub i32);
|
|||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The private message id.
|
||||
pub struct PrivateMessageId(i32);
|
||||
pub struct PrivateMessageId(pub i32);
|
||||
|
||||
impl fmt::Display for PrivateMessageId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -82,8 +87,14 @@ impl fmt::Display for PrivateMessageId {
|
|||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The person mention id.
|
||||
pub struct PersonMentionId(i32);
|
||||
/// The person comment mention id.
|
||||
pub struct PersonCommentMentionId(pub i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The person post mention id.
|
||||
pub struct PersonPostMentionId(pub i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
|
@ -125,7 +136,7 @@ pub struct LanguageId(pub i32);
|
|||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The comment reply id.
|
||||
pub struct CommentReplyId(i32);
|
||||
pub struct CommentReplyId(pub i32);
|
||||
|
||||
#[derive(
|
||||
Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default, Ord, PartialOrd,
|
||||
|
@ -204,6 +215,11 @@ pub struct PersonSavedCombinedId(i32);
|
|||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
pub struct ModlogCombinedId(i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
/// The inbox combined id
|
||||
pub struct InboxCombinedId(i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
|
|
|
@ -351,6 +351,17 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
inbox_combined (id) {
|
||||
id -> Int4,
|
||||
published -> Timestamptz,
|
||||
comment_reply_id -> Nullable<Int4>,
|
||||
person_comment_mention_id -> Nullable<Int4>,
|
||||
person_post_mention_id -> Nullable<Int4>,
|
||||
private_message_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
instance (id) {
|
||||
id -> Int4,
|
||||
|
@ -773,6 +784,16 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_comment_mention (id) {
|
||||
id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
comment_id -> Int4,
|
||||
read -> Bool,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_content_combined (id) {
|
||||
id -> Int4,
|
||||
|
@ -783,10 +804,10 @@ diesel::table! {
|
|||
}
|
||||
|
||||
diesel::table! {
|
||||
person_mention (id) {
|
||||
person_post_mention (id) {
|
||||
id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
comment_id -> Int4,
|
||||
post_id -> Int4,
|
||||
read -> Bool,
|
||||
published -> Timestamptz,
|
||||
}
|
||||
|
@ -1091,6 +1112,10 @@ diesel::joinable!(email_verification -> local_user (local_user_id));
|
|||
diesel::joinable!(federation_allowlist -> instance (instance_id));
|
||||
diesel::joinable!(federation_blocklist -> instance (instance_id));
|
||||
diesel::joinable!(federation_queue_state -> instance (instance_id));
|
||||
diesel::joinable!(inbox_combined -> comment_reply (comment_reply_id));
|
||||
diesel::joinable!(inbox_combined -> person_comment_mention (person_comment_mention_id));
|
||||
diesel::joinable!(inbox_combined -> person_post_mention (person_post_mention_id));
|
||||
diesel::joinable!(inbox_combined -> private_message (private_message_id));
|
||||
diesel::joinable!(instance_actions -> instance (instance_id));
|
||||
diesel::joinable!(instance_actions -> person (person_id));
|
||||
diesel::joinable!(local_image -> local_user (local_user_id));
|
||||
|
@ -1139,10 +1164,12 @@ diesel::joinable!(password_reset_request -> local_user (local_user_id));
|
|||
diesel::joinable!(person -> instance (instance_id));
|
||||
diesel::joinable!(person_aggregates -> person (person_id));
|
||||
diesel::joinable!(person_ban -> person (person_id));
|
||||
diesel::joinable!(person_comment_mention -> comment (comment_id));
|
||||
diesel::joinable!(person_comment_mention -> person (recipient_id));
|
||||
diesel::joinable!(person_content_combined -> comment (comment_id));
|
||||
diesel::joinable!(person_content_combined -> post (post_id));
|
||||
diesel::joinable!(person_mention -> comment (comment_id));
|
||||
diesel::joinable!(person_mention -> person (recipient_id));
|
||||
diesel::joinable!(person_post_mention -> person (recipient_id));
|
||||
diesel::joinable!(person_post_mention -> post (post_id));
|
||||
diesel::joinable!(person_saved_combined -> comment (comment_id));
|
||||
diesel::joinable!(person_saved_combined -> person (person_id));
|
||||
diesel::joinable!(person_saved_combined -> post (post_id));
|
||||
|
@ -1196,6 +1223,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
federation_blocklist,
|
||||
federation_queue_state,
|
||||
image_details,
|
||||
inbox_combined,
|
||||
instance,
|
||||
instance_actions,
|
||||
language,
|
||||
|
@ -1226,8 +1254,9 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
person_actions,
|
||||
person_aggregates,
|
||||
person_ban,
|
||||
person_comment_mention,
|
||||
person_content_combined,
|
||||
person_mention,
|
||||
person_post_mention,
|
||||
person_saved_combined,
|
||||
post,
|
||||
post_actions,
|
||||
|
|
33
crates/db_schema/src/source/combined/inbox.rs
Normal file
33
crates/db_schema/src/source/combined/inbox.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use crate::newtypes::{
|
||||
CommentReplyId,
|
||||
InboxCombinedId,
|
||||
PersonCommentMentionId,
|
||||
PersonPostMentionId,
|
||||
PrivateMessageId,
|
||||
};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::inbox_combined;
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use i_love_jesus::CursorKeysModule;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(PartialEq, Eq, Serialize, Deserialize, Debug, Clone)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Identifiable, Queryable, Selectable, CursorKeysModule)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = inbox_combined))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", cursor_keys_module(name = inbox_combined_keys))]
|
||||
/// A combined inbox table.
|
||||
pub struct InboxCombined {
|
||||
pub id: InboxCombinedId,
|
||||
pub published: DateTime<Utc>,
|
||||
pub comment_reply_id: Option<CommentReplyId>,
|
||||
pub person_comment_mention_id: Option<PersonCommentMentionId>,
|
||||
pub person_post_mention_id: Option<PersonPostMentionId>,
|
||||
pub private_message_id: Option<PrivateMessageId>,
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod inbox;
|
||||
pub mod modlog;
|
||||
pub mod person_content;
|
||||
pub mod person_saved;
|
||||
|
|
|
@ -34,7 +34,8 @@ pub mod oauth_provider;
|
|||
pub mod password_reset_request;
|
||||
pub mod person;
|
||||
pub mod person_block;
|
||||
pub mod person_mention;
|
||||
pub mod person_comment_mention;
|
||||
pub mod person_post_mention;
|
||||
pub mod post;
|
||||
pub mod post_report;
|
||||
pub mod private_message;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::newtypes::{CommentId, PersonId, PersonMentionId};
|
||||
use crate::newtypes::{CommentId, PersonCommentMentionId, PersonId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::person_mention;
|
||||
use crate::schema::person_comment_mention;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -12,12 +12,12 @@ use ts_rs::TS;
|
|||
derive(Queryable, Selectable, Associations, Identifiable, TS)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_mention))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_comment_mention))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A person mention.
|
||||
pub struct PersonMention {
|
||||
pub id: PersonMentionId,
|
||||
pub struct PersonCommentMention {
|
||||
pub id: PersonCommentMentionId,
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
pub read: bool,
|
||||
|
@ -25,15 +25,15 @@ pub struct PersonMention {
|
|||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_mention))]
|
||||
pub struct PersonMentionInsertForm {
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_comment_mention))]
|
||||
pub struct PersonCommentMentionInsertForm {
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
pub read: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_mention))]
|
||||
pub struct PersonMentionUpdateForm {
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_comment_mention))]
|
||||
pub struct PersonCommentMentionUpdateForm {
|
||||
pub read: Option<bool>,
|
||||
}
|
39
crates/db_schema/src/source/person_post_mention.rs
Normal file
39
crates/db_schema/src/source/person_post_mention.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use crate::newtypes::{PersonId, PersonPostMentionId, PostId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::person_post_mention;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(feature = "full")]
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Queryable, Selectable, Associations, Identifiable, TS)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_post_mention))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A person mention.
|
||||
pub struct PersonPostMention {
|
||||
pub id: PersonPostMentionId,
|
||||
pub recipient_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub read: bool,
|
||||
pub published: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_post_mention))]
|
||||
pub struct PersonPostMentionInsertForm {
|
||||
pub recipient_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub read: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_post_mention))]
|
||||
pub struct PersonPostMentionUpdateForm {
|
||||
pub read: Option<bool>,
|
||||
}
|
|
@ -24,8 +24,6 @@ pub mod post_view;
|
|||
#[cfg(feature = "full")]
|
||||
pub mod private_message_report_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod private_message_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod registration_application_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod report_combined_view;
|
||||
|
|
|
@ -198,17 +198,6 @@ pub struct PostView {
|
|||
pub tags: PostTags,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A private message view.
|
||||
pub struct PrivateMessageView {
|
||||
pub private_message: PrivateMessage,
|
||||
pub creator: Person,
|
||||
pub recipient: Person,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
|
|
|
@ -18,6 +18,7 @@ workspace = true
|
|||
full = [
|
||||
"lemmy_db_schema/full",
|
||||
"lemmy_utils/full",
|
||||
"i-love-jesus",
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"ts-rs",
|
||||
|
@ -40,10 +41,10 @@ ts-rs = { workspace = true, optional = true }
|
|||
chrono.workspace = true
|
||||
strum = { workspace = true }
|
||||
lemmy_utils = { workspace = true, optional = true }
|
||||
i-love-jesus = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
url.workspace = true
|
||||
lemmy_db_views = { workspace = true, features = ["full"] }
|
||||
|
|
|
@ -1,379 +0,0 @@
|
|||
use crate::structs::CommentReplyView;
|
||||
use diesel::{
|
||||
dsl::{exists, not},
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases::{self, creator_community_actions},
|
||||
newtypes::{CommentReplyId, PersonId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
comment_reply,
|
||||
community,
|
||||
community_actions,
|
||||
local_user,
|
||||
person,
|
||||
person_actions,
|
||||
post,
|
||||
},
|
||||
source::{community::CommunityFollower, local_user::LocalUser},
|
||||
utils::{
|
||||
actions,
|
||||
actions_alias,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
CommentSortType,
|
||||
};
|
||||
|
||||
fn queries<'a>() -> Queries<
|
||||
impl ReadFn<'a, CommentReplyView, (CommentReplyId, Option<PersonId>)>,
|
||||
impl ListFn<'a, CommentReplyView, CommentReplyQuery>,
|
||||
> {
|
||||
let creator_is_admin = exists(
|
||||
local_user::table.filter(
|
||||
comment::creator_id
|
||||
.eq(local_user::person_id)
|
||||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
);
|
||||
|
||||
let all_joins = move |query: comment_reply::BoxedQuery<'a, Pg>,
|
||||
my_person_id: Option<PersonId>| {
|
||||
query
|
||||
.inner_join(comment::table)
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.inner_join(post::table.on(comment::post_id.eq(post::id)))
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(aliases::person1)
|
||||
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
|
||||
.left_join(actions(comment_actions::table, my_person_id, comment::id))
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
my_person_id,
|
||||
comment::creator_id,
|
||||
))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
comment::creator_id,
|
||||
post::community_id,
|
||||
))
|
||||
.select((
|
||||
comment_reply::all_columns,
|
||||
comment::all_columns,
|
||||
person::all_columns,
|
||||
post::all_columns,
|
||||
community::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
comment_aggregates::all_columns,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_is_admin,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
))
|
||||
};
|
||||
|
||||
let read =
|
||||
move |mut conn: DbConn<'a>,
|
||||
(comment_reply_id, my_person_id): (CommentReplyId, Option<PersonId>)| async move {
|
||||
all_joins(
|
||||
comment_reply::table.find(comment_reply_id).into_boxed(),
|
||||
my_person_id,
|
||||
)
|
||||
.first(&mut conn)
|
||||
.await
|
||||
};
|
||||
|
||||
let list = move |mut conn: DbConn<'a>, o: CommentReplyQuery| async move {
|
||||
// These filters need to be kept in sync with the filters in
|
||||
// CommentReplyView::get_unread_replies()
|
||||
let mut query = all_joins(comment_reply::table.into_boxed(), o.my_person_id);
|
||||
|
||||
if let Some(recipient_id) = o.recipient_id {
|
||||
query = query.filter(comment_reply::recipient_id.eq(recipient_id));
|
||||
}
|
||||
|
||||
if o.unread_only {
|
||||
query = query.filter(comment_reply::read.eq(false));
|
||||
}
|
||||
|
||||
if !o.show_bot_accounts {
|
||||
query = query.filter(not(person::bot_account));
|
||||
};
|
||||
|
||||
query = match o.sort.unwrap_or(CommentSortType::New) {
|
||||
CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()),
|
||||
CommentSortType::Controversial => {
|
||||
query.then_order_by(comment_aggregates::controversy_rank.desc())
|
||||
}
|
||||
CommentSortType::New => query.then_order_by(comment_reply::published.desc()),
|
||||
CommentSortType::Old => query.then_order_by(comment_reply::published.asc()),
|
||||
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
|
||||
};
|
||||
|
||||
// Don't show replies from blocked persons
|
||||
query = query.filter(person_actions::blocked.is_null());
|
||||
|
||||
let (limit, offset) = limit_and_offset(o.page, o.limit)?;
|
||||
|
||||
query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load::<CommentReplyView>(&mut conn)
|
||||
.await
|
||||
};
|
||||
|
||||
Queries::new(read, list)
|
||||
}
|
||||
|
||||
impl CommentReplyView {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
comment_reply_id: CommentReplyId,
|
||||
my_person_id: Option<PersonId>,
|
||||
) -> Result<Self, Error> {
|
||||
queries().read(pool, (comment_reply_id, my_person_id)).await
|
||||
}
|
||||
|
||||
/// Gets the number of unread replies
|
||||
pub async fn get_unread_replies(
|
||||
pool: &mut DbPool<'_>,
|
||||
local_user: &LocalUser,
|
||||
) -> Result<i64, Error> {
|
||||
use diesel::dsl::count;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let mut query = comment_reply::table
|
||||
.inner_join(comment::table)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(local_user.person_id),
|
||||
comment::creator_id,
|
||||
))
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.into_boxed();
|
||||
|
||||
// These filters need to be kept in sync with the filters in queries().list()
|
||||
if !local_user.show_bot_accounts {
|
||||
query = query.filter(not(person::bot_account));
|
||||
}
|
||||
|
||||
query
|
||||
// Don't count replies from blocked users
|
||||
.filter(person_actions::blocked.is_null())
|
||||
.filter(comment_reply::recipient_id.eq(local_user.person_id))
|
||||
.filter(comment_reply::read.eq(false))
|
||||
.filter(comment::deleted.eq(false))
|
||||
.filter(comment::removed.eq(false))
|
||||
.select(count(comment_reply::id))
|
||||
.first::<i64>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CommentReplyQuery {
|
||||
pub my_person_id: Option<PersonId>,
|
||||
pub recipient_id: Option<PersonId>,
|
||||
pub sort: Option<CommentSortType>,
|
||||
pub unread_only: bool,
|
||||
pub show_bot_accounts: bool,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
impl CommentReplyQuery {
|
||||
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<CommentReplyView>, Error> {
|
||||
queries().list(pool, self).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::{comment_reply_view::CommentReplyQuery, structs::CommentReplyView};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm},
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::Instance,
|
||||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||
person::{Person, PersonInsertForm, PersonUpdateForm},
|
||||
person_block::{PersonBlock, PersonBlockForm},
|
||||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::{Blockable, Crud},
|
||||
utils::build_db_pool_for_tests,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_crud() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
|
||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
|
||||
let terry_form = PersonInsertForm::test_form(inserted_instance.id, "terrylake");
|
||||
let inserted_terry = Person::create(pool, &terry_form).await?;
|
||||
|
||||
let recipient_form = PersonInsertForm {
|
||||
local: Some(true),
|
||||
..PersonInsertForm::test_form(inserted_instance.id, "terrylakes recipient")
|
||||
};
|
||||
|
||||
let inserted_recipient = Person::create(pool, &recipient_form).await?;
|
||||
let recipient_id = inserted_recipient.id;
|
||||
|
||||
let recipient_local_user =
|
||||
LocalUser::create(pool, &LocalUserInsertForm::test_form(recipient_id), vec![]).await?;
|
||||
|
||||
let new_community = CommunityInsertForm::new(
|
||||
inserted_instance.id,
|
||||
"test community lake".to_string(),
|
||||
"nada".to_owned(),
|
||||
"pubkey".to_string(),
|
||||
);
|
||||
let inserted_community = Community::create(pool, &new_community).await?;
|
||||
|
||||
let new_post = PostInsertForm::new(
|
||||
"A test post".into(),
|
||||
inserted_terry.id,
|
||||
inserted_community.id,
|
||||
);
|
||||
let inserted_post = Post::create(pool, &new_post).await?;
|
||||
|
||||
let comment_form =
|
||||
CommentInsertForm::new(inserted_terry.id, inserted_post.id, "A test comment".into());
|
||||
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
|
||||
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: inserted_recipient.id,
|
||||
comment_id: inserted_comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
let inserted_reply = CommentReply::create(pool, &comment_reply_form).await?;
|
||||
|
||||
let expected_reply = CommentReply {
|
||||
id: inserted_reply.id,
|
||||
recipient_id: inserted_reply.recipient_id,
|
||||
comment_id: inserted_reply.comment_id,
|
||||
read: false,
|
||||
published: inserted_reply.published,
|
||||
};
|
||||
|
||||
let read_reply = CommentReply::read(pool, inserted_reply.id).await?;
|
||||
|
||||
let comment_reply_update_form = CommentReplyUpdateForm { read: Some(false) };
|
||||
let updated_reply =
|
||||
CommentReply::update(pool, inserted_reply.id, &comment_reply_update_form).await?;
|
||||
|
||||
// Test to make sure counts and blocks work correctly
|
||||
let unread_replies = CommentReplyView::get_unread_replies(pool, &recipient_local_user).await?;
|
||||
|
||||
let query = CommentReplyQuery {
|
||||
recipient_id: Some(recipient_id),
|
||||
my_person_id: Some(recipient_id),
|
||||
sort: None,
|
||||
unread_only: false,
|
||||
show_bot_accounts: true,
|
||||
page: None,
|
||||
limit: None,
|
||||
};
|
||||
let replies = query.clone().list(pool).await?;
|
||||
assert_eq!(1, unread_replies);
|
||||
assert_eq!(1, replies.len());
|
||||
|
||||
// Block the person, and make sure these counts are now empty
|
||||
let block_form = PersonBlockForm {
|
||||
person_id: recipient_id,
|
||||
target_id: inserted_terry.id,
|
||||
};
|
||||
PersonBlock::block(pool, &block_form).await?;
|
||||
|
||||
let unread_replies_after_block =
|
||||
CommentReplyView::get_unread_replies(pool, &recipient_local_user).await?;
|
||||
let replies_after_block = query.clone().list(pool).await?;
|
||||
assert_eq!(0, unread_replies_after_block);
|
||||
assert_eq!(0, replies_after_block.len());
|
||||
|
||||
// Unblock user so we can reuse the same person
|
||||
PersonBlock::unblock(pool, &block_form).await?;
|
||||
|
||||
// Turn Terry into a bot account
|
||||
let person_update_form = PersonUpdateForm {
|
||||
bot_account: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
Person::update(pool, inserted_terry.id, &person_update_form).await?;
|
||||
|
||||
let recipient_local_user_update_form = LocalUserUpdateForm {
|
||||
show_bot_accounts: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
LocalUser::update(
|
||||
pool,
|
||||
recipient_local_user.id,
|
||||
&recipient_local_user_update_form,
|
||||
)
|
||||
.await?;
|
||||
let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?;
|
||||
|
||||
let unread_replies_after_hide_bots =
|
||||
CommentReplyView::get_unread_replies(pool, &recipient_local_user_view.local_user).await?;
|
||||
|
||||
let mut query_without_bots = query.clone();
|
||||
query_without_bots.show_bot_accounts = false;
|
||||
let replies_after_hide_bots = query_without_bots.list(pool).await?;
|
||||
assert_eq!(0, unread_replies_after_hide_bots);
|
||||
assert_eq!(0, replies_after_hide_bots.len());
|
||||
|
||||
Comment::delete(pool, inserted_comment.id).await?;
|
||||
Post::delete(pool, inserted_post.id).await?;
|
||||
Community::delete(pool, inserted_community.id).await?;
|
||||
Person::delete(pool, inserted_terry.id).await?;
|
||||
Person::delete(pool, inserted_recipient.id).await?;
|
||||
Instance::delete(pool, inserted_instance.id).await?;
|
||||
|
||||
assert_eq!(expected_reply, read_reply);
|
||||
assert_eq!(expected_reply, inserted_reply);
|
||||
assert_eq!(expected_reply, updated_reply);
|
||||
Ok(())
|
||||
}
|
||||
}
|
991
crates/db_views_actor/src/inbox_combined_view.rs
Normal file
991
crates/db_views_actor/src/inbox_combined_view.rs
Normal file
|
@ -0,0 +1,991 @@
|
|||
use crate::structs::{
|
||||
CommentReplyView,
|
||||
InboxCombinedPaginationCursor,
|
||||
InboxCombinedView,
|
||||
InboxCombinedViewInternal,
|
||||
PersonCommentMentionView,
|
||||
PersonPostMentionView,
|
||||
PrivateMessageView,
|
||||
};
|
||||
use diesel::{
|
||||
dsl::not,
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
SelectableHelper,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use i_love_jesus::PaginatedQueryBuilder;
|
||||
use lemmy_db_schema::{
|
||||
aliases::{self, creator_community_actions},
|
||||
newtypes::PersonId,
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
comment_reply,
|
||||
community,
|
||||
community_actions,
|
||||
image_details,
|
||||
inbox_combined,
|
||||
instance_actions,
|
||||
local_user,
|
||||
person,
|
||||
person_actions,
|
||||
person_comment_mention,
|
||||
person_post_mention,
|
||||
post,
|
||||
post_actions,
|
||||
post_aggregates,
|
||||
private_message,
|
||||
},
|
||||
source::{
|
||||
combined::inbox::{inbox_combined_keys as key, InboxCombined},
|
||||
community::CommunityFollower,
|
||||
},
|
||||
utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool},
|
||||
InboxDataType,
|
||||
InternalToCombinedView,
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
impl InboxCombinedViewInternal {
|
||||
/// Gets the number of unread mentions
|
||||
pub async fn get_unread_count(
|
||||
pool: &mut DbPool<'_>,
|
||||
my_person_id: PersonId,
|
||||
show_bot_accounts: bool,
|
||||
) -> Result<i64, Error> {
|
||||
use diesel::dsl::count;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let item_creator = person::id;
|
||||
let recipient_person = aliases::person1.field(person::id);
|
||||
|
||||
let unread_filter = comment_reply::read
|
||||
.eq(false)
|
||||
.or(person_comment_mention::read.eq(false))
|
||||
.or(person_post_mention::read.eq(false))
|
||||
// If its unread, I only want the messages to me
|
||||
.or(
|
||||
private_message::read
|
||||
.eq(false)
|
||||
.and(private_message::recipient_id.eq(my_person_id)),
|
||||
);
|
||||
|
||||
let item_creator_join = comment::creator_id
|
||||
.eq(item_creator)
|
||||
.or(
|
||||
inbox_combined::person_post_mention_id
|
||||
.is_not_null()
|
||||
.and(post::creator_id.eq(item_creator)),
|
||||
)
|
||||
.or(private_message::creator_id.eq(item_creator));
|
||||
|
||||
let recipient_join = comment_reply::recipient_id
|
||||
.eq(recipient_person)
|
||||
.or(person_comment_mention::recipient_id.eq(recipient_person))
|
||||
.or(person_post_mention::recipient_id.eq(recipient_person))
|
||||
.or(private_message::recipient_id.eq(recipient_person));
|
||||
|
||||
let comment_join = comment_reply::comment_id
|
||||
.eq(comment::id)
|
||||
.or(person_comment_mention::comment_id.eq(comment::id))
|
||||
// Filter out the deleted / removed
|
||||
.and(not(comment::deleted))
|
||||
.and(not(comment::removed));
|
||||
|
||||
let post_join = person_post_mention::post_id
|
||||
.eq(post::id)
|
||||
.or(comment::post_id.eq(post::id))
|
||||
// Filter out the deleted / removed
|
||||
.and(not(post::deleted))
|
||||
.and(not(post::removed));
|
||||
|
||||
// This could be a simple join, but you need to check for deleted here
|
||||
let private_message_join = inbox_combined::private_message_id
|
||||
.eq(private_message::id.nullable())
|
||||
.and(not(private_message::deleted));
|
||||
|
||||
let mut query = inbox_combined::table
|
||||
.left_join(comment_reply::table)
|
||||
.left_join(person_comment_mention::table)
|
||||
.left_join(person_post_mention::table)
|
||||
.left_join(private_message::table.on(private_message_join))
|
||||
.left_join(comment::table.on(comment_join))
|
||||
.left_join(post::table.on(post_join))
|
||||
// The item creator
|
||||
.inner_join(person::table.on(item_creator_join))
|
||||
// The recipient
|
||||
.inner_join(aliases::person1.on(recipient_join))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
Some(my_person_id),
|
||||
person::instance_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(my_person_id),
|
||||
item_creator,
|
||||
))
|
||||
// Filter for your user
|
||||
.filter(recipient_person.eq(my_person_id))
|
||||
// Filter unreads
|
||||
.filter(unread_filter)
|
||||
// Don't count replies from blocked users
|
||||
.filter(person_actions::blocked.is_null())
|
||||
.filter(instance_actions::blocked.is_null())
|
||||
.into_boxed();
|
||||
|
||||
// These filters need to be kept in sync with the filters in queries().list()
|
||||
if !show_bot_accounts {
|
||||
query = query.filter(not(person::bot_account));
|
||||
}
|
||||
|
||||
query
|
||||
.select(count(inbox_combined::id))
|
||||
.first::<i64>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl InboxCombinedPaginationCursor {
|
||||
// get cursor for page that starts immediately after the given post
|
||||
pub fn after_post(view: &InboxCombinedView) -> InboxCombinedPaginationCursor {
|
||||
let (prefix, id) = match view {
|
||||
InboxCombinedView::CommentReply(v) => ('R', v.comment_reply.id.0),
|
||||
InboxCombinedView::CommentMention(v) => ('C', v.person_comment_mention.id.0),
|
||||
InboxCombinedView::PostMention(v) => ('P', v.person_post_mention.id.0),
|
||||
InboxCombinedView::PrivateMessage(v) => ('M', v.private_message.id.0),
|
||||
};
|
||||
// hex encoding to prevent ossification
|
||||
InboxCombinedPaginationCursor(format!("{prefix}{id:x}"))
|
||||
}
|
||||
|
||||
pub async fn read(&self, pool: &mut DbPool<'_>) -> Result<PaginationCursorData, Error> {
|
||||
let err_msg = || Error::QueryBuilderError("Could not parse pagination token".into());
|
||||
let mut query = inbox_combined::table
|
||||
.select(InboxCombined::as_select())
|
||||
.into_boxed();
|
||||
let (prefix, id_str) = self.0.split_at_checked(1).ok_or_else(err_msg)?;
|
||||
let id = i32::from_str_radix(id_str, 16).map_err(|_err| err_msg())?;
|
||||
query = match prefix {
|
||||
"R" => query.filter(inbox_combined::comment_reply_id.eq(id)),
|
||||
"C" => query.filter(inbox_combined::person_comment_mention_id.eq(id)),
|
||||
"P" => query.filter(inbox_combined::person_post_mention_id.eq(id)),
|
||||
"M" => query.filter(inbox_combined::private_message_id.eq(id)),
|
||||
_ => return Err(err_msg()),
|
||||
};
|
||||
let token = query.first(&mut get_conn(pool).await?).await?;
|
||||
|
||||
Ok(PaginationCursorData(token))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PaginationCursorData(InboxCombined);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InboxCombinedQuery {
|
||||
pub type_: Option<InboxDataType>,
|
||||
pub unread_only: Option<bool>,
|
||||
pub show_bot_accounts: Option<bool>,
|
||||
pub page_after: Option<PaginationCursorData>,
|
||||
pub page_back: Option<bool>,
|
||||
}
|
||||
|
||||
impl InboxCombinedQuery {
|
||||
pub async fn list(
|
||||
self,
|
||||
pool: &mut DbPool<'_>,
|
||||
my_person_id: PersonId,
|
||||
) -> LemmyResult<Vec<InboxCombinedView>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let item_creator = person::id;
|
||||
let recipient_person = aliases::person1.field(person::id);
|
||||
|
||||
let item_creator_join = comment::creator_id
|
||||
.eq(item_creator)
|
||||
.or(
|
||||
inbox_combined::person_post_mention_id
|
||||
.is_not_null()
|
||||
.and(post::creator_id.eq(item_creator)),
|
||||
)
|
||||
.or(private_message::creator_id.eq(item_creator));
|
||||
|
||||
let recipient_join = comment_reply::recipient_id
|
||||
.eq(recipient_person)
|
||||
.or(person_comment_mention::recipient_id.eq(recipient_person))
|
||||
.or(person_post_mention::recipient_id.eq(recipient_person))
|
||||
.or(private_message::recipient_id.eq(recipient_person));
|
||||
|
||||
let comment_join = comment_reply::comment_id
|
||||
.eq(comment::id)
|
||||
.or(person_comment_mention::comment_id.eq(comment::id))
|
||||
// Filter out the deleted / removed
|
||||
.and(not(comment::deleted))
|
||||
.and(not(comment::removed));
|
||||
|
||||
let post_join = person_post_mention::post_id
|
||||
.eq(post::id)
|
||||
.or(comment::post_id.eq(post::id))
|
||||
// Filter out the deleted / removed
|
||||
.and(not(post::deleted))
|
||||
.and(not(post::removed));
|
||||
|
||||
// This could be a simple join, but you need to check for deleted here
|
||||
let private_message_join = inbox_combined::private_message_id
|
||||
.eq(private_message::id.nullable())
|
||||
.and(not(private_message::deleted));
|
||||
|
||||
let community_join = post::community_id.eq(community::id);
|
||||
|
||||
let mut query = inbox_combined::table
|
||||
.left_join(comment_reply::table)
|
||||
.left_join(person_comment_mention::table)
|
||||
.left_join(person_post_mention::table)
|
||||
.left_join(private_message::table.on(private_message_join))
|
||||
.left_join(comment::table.on(comment_join))
|
||||
.left_join(post::table.on(post_join))
|
||||
.left_join(community::table.on(community_join))
|
||||
// The item creator
|
||||
.inner_join(person::table.on(item_creator_join))
|
||||
// The recipient
|
||||
.inner_join(aliases::person1.on(recipient_join))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
item_creator,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(
|
||||
local_user::table.on(
|
||||
item_creator
|
||||
.eq(local_user::person_id)
|
||||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
)
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
Some(my_person_id),
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
Some(my_person_id),
|
||||
person::instance_id,
|
||||
))
|
||||
.left_join(actions(post_actions::table, Some(my_person_id), post::id))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(my_person_id),
|
||||
item_creator,
|
||||
))
|
||||
.left_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id)))
|
||||
.left_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
|
||||
.left_join(actions(
|
||||
comment_actions::table,
|
||||
Some(my_person_id),
|
||||
comment::id,
|
||||
))
|
||||
.left_join(image_details::table.on(post::thumbnail_url.eq(image_details::link.nullable())))
|
||||
.select((
|
||||
// Specific
|
||||
comment_reply::all_columns.nullable(),
|
||||
person_comment_mention::all_columns.nullable(),
|
||||
person_post_mention::all_columns.nullable(),
|
||||
post_aggregates::all_columns.nullable(),
|
||||
coalesce(
|
||||
post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(),
|
||||
post_aggregates::comments,
|
||||
)
|
||||
.nullable(),
|
||||
post_actions::saved.nullable().is_not_null(),
|
||||
post_actions::read.nullable().is_not_null(),
|
||||
post_actions::hidden.nullable().is_not_null(),
|
||||
post_actions::like_score.nullable(),
|
||||
image_details::all_columns.nullable(),
|
||||
private_message::all_columns.nullable(),
|
||||
// Shared
|
||||
post::all_columns.nullable(),
|
||||
community::all_columns.nullable(),
|
||||
comment::all_columns.nullable(),
|
||||
comment_aggregates::all_columns.nullable(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
person::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
local_user::admin.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
// Filters
|
||||
if self.unread_only.unwrap_or_default() {
|
||||
query = query
|
||||
// The recipient filter (IE only show replies to you)
|
||||
.filter(recipient_person.eq(my_person_id))
|
||||
.filter(
|
||||
comment_reply::read
|
||||
.eq(false)
|
||||
.or(person_comment_mention::read.eq(false))
|
||||
.or(person_post_mention::read.eq(false))
|
||||
// If its unread, I only want the messages to me
|
||||
.or(private_message::read.eq(false)),
|
||||
);
|
||||
} else {
|
||||
// A special case for private messages: show messages FROM you also.
|
||||
// Use a not-null checks to catch the others
|
||||
query = query.filter(
|
||||
inbox_combined::comment_reply_id
|
||||
.is_not_null()
|
||||
.and(recipient_person.eq(my_person_id))
|
||||
.or(
|
||||
inbox_combined::person_comment_mention_id
|
||||
.is_not_null()
|
||||
.and(recipient_person.eq(my_person_id)),
|
||||
)
|
||||
.or(
|
||||
inbox_combined::person_post_mention_id
|
||||
.is_not_null()
|
||||
.and(recipient_person.eq(my_person_id)),
|
||||
)
|
||||
.or(
|
||||
inbox_combined::private_message_id.is_not_null().and(
|
||||
recipient_person
|
||||
.eq(my_person_id)
|
||||
.or(item_creator.eq(my_person_id)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if !(self.show_bot_accounts.unwrap_or_default()) {
|
||||
query = query.filter(not(person::bot_account));
|
||||
};
|
||||
|
||||
// Dont show replies from blocked users or instances
|
||||
query = query
|
||||
.filter(person_actions::blocked.is_null())
|
||||
.filter(instance_actions::blocked.is_null());
|
||||
|
||||
if let Some(type_) = self.type_ {
|
||||
query = match type_ {
|
||||
InboxDataType::All => query,
|
||||
InboxDataType::CommentReply => query.filter(inbox_combined::comment_reply_id.is_not_null()),
|
||||
InboxDataType::CommentMention => {
|
||||
query.filter(inbox_combined::person_comment_mention_id.is_not_null())
|
||||
}
|
||||
InboxDataType::PostMention => {
|
||||
query.filter(inbox_combined::person_post_mention_id.is_not_null())
|
||||
}
|
||||
InboxDataType::PrivateMessage => {
|
||||
query.filter(inbox_combined::private_message_id.is_not_null())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut query = PaginatedQueryBuilder::new(query);
|
||||
|
||||
let page_after = self.page_after.map(|c| c.0);
|
||||
|
||||
if self.page_back.unwrap_or_default() {
|
||||
query = query.before(page_after).limit_and_offset_from_end();
|
||||
} else {
|
||||
query = query.after(page_after);
|
||||
}
|
||||
|
||||
// Sorting by published
|
||||
query = query
|
||||
.then_desc(key::published)
|
||||
// Tie breaker
|
||||
.then_desc(key::id);
|
||||
|
||||
let res = query.load::<InboxCombinedViewInternal>(conn).await?;
|
||||
|
||||
// Map the query results to the enum
|
||||
let out = res
|
||||
.into_iter()
|
||||
.filter_map(InternalToCombinedView::map_to_enum)
|
||||
.collect();
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl InternalToCombinedView for InboxCombinedViewInternal {
|
||||
type CombinedView = InboxCombinedView;
|
||||
|
||||
fn map_to_enum(self) -> Option<Self::CombinedView> {
|
||||
// Use for a short alias
|
||||
let v = self;
|
||||
|
||||
if let (Some(comment_reply), Some(comment), Some(counts), Some(post), Some(community)) = (
|
||||
v.comment_reply,
|
||||
v.comment.clone(),
|
||||
v.comment_counts.clone(),
|
||||
v.post.clone(),
|
||||
v.community.clone(),
|
||||
) {
|
||||
Some(InboxCombinedView::CommentReply(CommentReplyView {
|
||||
comment_reply,
|
||||
comment,
|
||||
counts,
|
||||
recipient: v.item_recipient,
|
||||
post,
|
||||
community,
|
||||
creator: v.item_creator,
|
||||
creator_banned_from_community: v.item_creator_banned_from_community,
|
||||
creator_is_moderator: v.item_creator_is_moderator,
|
||||
creator_is_admin: v.item_creator_is_admin,
|
||||
creator_blocked: v.item_creator_blocked,
|
||||
subscribed: v.subscribed,
|
||||
saved: v.comment_saved,
|
||||
my_vote: v.my_comment_vote,
|
||||
banned_from_community: v.banned_from_community,
|
||||
}))
|
||||
} else if let (
|
||||
Some(person_comment_mention),
|
||||
Some(comment),
|
||||
Some(counts),
|
||||
Some(post),
|
||||
Some(community),
|
||||
) = (
|
||||
v.person_comment_mention,
|
||||
v.comment,
|
||||
v.comment_counts,
|
||||
v.post.clone(),
|
||||
v.community.clone(),
|
||||
) {
|
||||
Some(InboxCombinedView::CommentMention(
|
||||
PersonCommentMentionView {
|
||||
person_comment_mention,
|
||||
comment,
|
||||
counts,
|
||||
recipient: v.item_recipient,
|
||||
post,
|
||||
community,
|
||||
creator: v.item_creator,
|
||||
creator_banned_from_community: v.item_creator_banned_from_community,
|
||||
creator_is_moderator: v.item_creator_is_moderator,
|
||||
creator_is_admin: v.item_creator_is_admin,
|
||||
creator_blocked: v.item_creator_blocked,
|
||||
subscribed: v.subscribed,
|
||||
saved: v.comment_saved,
|
||||
my_vote: v.my_comment_vote,
|
||||
banned_from_community: v.banned_from_community,
|
||||
},
|
||||
))
|
||||
} else if let (
|
||||
Some(person_post_mention),
|
||||
Some(post),
|
||||
Some(counts),
|
||||
Some(unread_comments),
|
||||
Some(community),
|
||||
) = (
|
||||
v.person_post_mention,
|
||||
v.post,
|
||||
v.post_counts,
|
||||
v.post_unread_comments,
|
||||
v.community,
|
||||
) {
|
||||
Some(InboxCombinedView::PostMention(PersonPostMentionView {
|
||||
person_post_mention,
|
||||
counts,
|
||||
post,
|
||||
community,
|
||||
recipient: v.item_recipient,
|
||||
unread_comments,
|
||||
creator: v.item_creator,
|
||||
creator_banned_from_community: v.item_creator_banned_from_community,
|
||||
creator_is_moderator: v.item_creator_is_moderator,
|
||||
creator_is_admin: v.item_creator_is_admin,
|
||||
creator_blocked: v.item_creator_blocked,
|
||||
subscribed: v.subscribed,
|
||||
saved: v.post_saved,
|
||||
read: v.post_read,
|
||||
hidden: v.post_hidden,
|
||||
my_vote: v.my_post_vote,
|
||||
image_details: v.image_details,
|
||||
banned_from_community: v.banned_from_community,
|
||||
}))
|
||||
} else if let Some(private_message) = v.private_message {
|
||||
Some(InboxCombinedView::PrivateMessage(PrivateMessageView {
|
||||
private_message,
|
||||
creator: v.item_creator,
|
||||
recipient: v.item_recipient,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
inbox_combined_view::InboxCombinedQuery,
|
||||
structs::{InboxCombinedView, InboxCombinedViewInternal, PrivateMessageView},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
assert_length,
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm},
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::Instance,
|
||||
instance_block::{InstanceBlock, InstanceBlockForm},
|
||||
person::{Person, PersonInsertForm, PersonUpdateForm},
|
||||
person_block::{PersonBlock, PersonBlockForm},
|
||||
person_comment_mention::{PersonCommentMention, PersonCommentMentionInsertForm},
|
||||
person_post_mention::{PersonPostMention, PersonPostMentionInsertForm},
|
||||
post::{Post, PostInsertForm},
|
||||
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
||||
},
|
||||
traits::{Blockable, Crud},
|
||||
utils::{build_db_pool_for_tests, DbPool},
|
||||
InboxDataType,
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
struct Data {
|
||||
instance: Instance,
|
||||
timmy: Person,
|
||||
sara: Person,
|
||||
jessica: Person,
|
||||
timmy_post: Post,
|
||||
jessica_post: Post,
|
||||
timmy_comment: Comment,
|
||||
sara_comment: Comment,
|
||||
}
|
||||
|
||||
async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult<Data> {
|
||||
let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
|
||||
let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_pcv");
|
||||
let timmy = Person::create(pool, &timmy_form).await?;
|
||||
|
||||
let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv");
|
||||
let sara = Person::create(pool, &sara_form).await?;
|
||||
|
||||
let jessica_form = PersonInsertForm::test_form(instance.id, "jessica_mrv");
|
||||
let jessica = Person::create(pool, &jessica_form).await?;
|
||||
|
||||
let community_form = CommunityInsertForm::new(
|
||||
instance.id,
|
||||
"test community pcv".to_string(),
|
||||
"nada".to_owned(),
|
||||
"pubkey".to_string(),
|
||||
);
|
||||
let community = Community::create(pool, &community_form).await?;
|
||||
|
||||
let timmy_post_form = PostInsertForm::new("timmy post prv".into(), timmy.id, community.id);
|
||||
let timmy_post = Post::create(pool, &timmy_post_form).await?;
|
||||
|
||||
let jessica_post_form =
|
||||
PostInsertForm::new("jessica post prv".into(), jessica.id, community.id);
|
||||
let jessica_post = Post::create(pool, &jessica_post_form).await?;
|
||||
|
||||
let timmy_comment_form =
|
||||
CommentInsertForm::new(timmy.id, timmy_post.id, "timmy comment prv".into());
|
||||
let timmy_comment = Comment::create(pool, &timmy_comment_form, None).await?;
|
||||
|
||||
let sara_comment_form =
|
||||
CommentInsertForm::new(sara.id, timmy_post.id, "sara comment prv".into());
|
||||
let sara_comment = Comment::create(pool, &sara_comment_form, Some(&timmy_comment.path)).await?;
|
||||
|
||||
Ok(Data {
|
||||
instance,
|
||||
timmy,
|
||||
sara,
|
||||
jessica,
|
||||
timmy_post,
|
||||
jessica_post,
|
||||
timmy_comment,
|
||||
sara_comment,
|
||||
})
|
||||
}
|
||||
|
||||
async fn setup_private_messages(data: &Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
||||
let sara_timmy_message_form =
|
||||
PrivateMessageInsertForm::new(data.sara.id, data.timmy.id, "sara to timmy".into());
|
||||
PrivateMessage::create(pool, &sara_timmy_message_form).await?;
|
||||
|
||||
let sara_jessica_message_form =
|
||||
PrivateMessageInsertForm::new(data.sara.id, data.jessica.id, "sara to jessica".into());
|
||||
PrivateMessage::create(pool, &sara_jessica_message_form).await?;
|
||||
|
||||
let timmy_sara_message_form =
|
||||
PrivateMessageInsertForm::new(data.timmy.id, data.sara.id, "timmy to sara".into());
|
||||
PrivateMessage::create(pool, &timmy_sara_message_form).await?;
|
||||
|
||||
let jessica_timmy_message_form =
|
||||
PrivateMessageInsertForm::new(data.jessica.id, data.timmy.id, "jessica to timmy".into());
|
||||
PrivateMessage::create(pool, &jessica_timmy_message_form).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> {
|
||||
Instance::delete(pool, data.instance.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn replies() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
// Sara replied to timmys comment, but lets create the row now
|
||||
let form = CommentReplyInsertForm {
|
||||
recipient_id: data.timmy.id,
|
||||
comment_id: data.sara_comment.id,
|
||||
read: None,
|
||||
};
|
||||
let reply = CommentReply::create(pool, &form).await?;
|
||||
|
||||
let timmy_unread_replies =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.timmy.id, true).await?;
|
||||
assert_eq!(1, timmy_unread_replies);
|
||||
|
||||
let timmy_inbox = InboxCombinedQuery::default()
|
||||
.list(pool, data.timmy.id)
|
||||
.await?;
|
||||
assert_length!(1, timmy_inbox);
|
||||
|
||||
if let InboxCombinedView::CommentReply(v) = &timmy_inbox[0] {
|
||||
assert_eq!(data.sara_comment.id, v.comment_reply.comment_id);
|
||||
assert_eq!(data.sara_comment.id, v.comment.id);
|
||||
assert_eq!(data.timmy_post.id, v.post.id);
|
||||
assert_eq!(data.sara.id, v.creator.id);
|
||||
assert_eq!(data.timmy.id, v.recipient.id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
|
||||
// Mark it as read
|
||||
let form = CommentReplyUpdateForm { read: Some(true) };
|
||||
CommentReply::update(pool, reply.id, &form).await?;
|
||||
|
||||
let timmy_unread_replies =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.timmy.id, true).await?;
|
||||
assert_eq!(0, timmy_unread_replies);
|
||||
|
||||
let timmy_inbox_unread = InboxCombinedQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.timmy.id)
|
||||
.await?;
|
||||
assert_length!(0, timmy_inbox_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn mentions() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
// Timmy mentions sara in a comment
|
||||
let timmy_mention_sara_comment_form = PersonCommentMentionInsertForm {
|
||||
recipient_id: data.sara.id,
|
||||
comment_id: data.timmy_comment.id,
|
||||
read: None,
|
||||
};
|
||||
PersonCommentMention::create(pool, &timmy_mention_sara_comment_form).await?;
|
||||
|
||||
// Jessica mentions sara in a post
|
||||
let jessica_mention_sara_post_form = PersonPostMentionInsertForm {
|
||||
recipient_id: data.sara.id,
|
||||
post_id: data.jessica_post.id,
|
||||
read: None,
|
||||
};
|
||||
PersonPostMention::create(pool, &jessica_mention_sara_post_form).await?;
|
||||
|
||||
// Test to make sure counts and blocks work correctly
|
||||
let sara_unread_mentions =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.sara.id, true).await?;
|
||||
assert_eq!(2, sara_unread_mentions);
|
||||
|
||||
let sara_inbox = InboxCombinedQuery::default()
|
||||
.list(pool, data.sara.id)
|
||||
.await?;
|
||||
assert_length!(2, sara_inbox);
|
||||
|
||||
if let InboxCombinedView::PostMention(v) = &sara_inbox[0] {
|
||||
assert_eq!(data.jessica_post.id, v.person_post_mention.post_id);
|
||||
assert_eq!(data.jessica_post.id, v.post.id);
|
||||
assert_eq!(data.jessica.id, v.creator.id);
|
||||
assert_eq!(data.sara.id, v.recipient.id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
|
||||
if let InboxCombinedView::CommentMention(v) = &sara_inbox[1] {
|
||||
assert_eq!(data.timmy_comment.id, v.person_comment_mention.comment_id);
|
||||
assert_eq!(data.timmy_comment.id, v.comment.id);
|
||||
assert_eq!(data.timmy_post.id, v.post.id);
|
||||
assert_eq!(data.timmy.id, v.creator.id);
|
||||
assert_eq!(data.sara.id, v.recipient.id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
|
||||
// Sara blocks timmy, and make sure these counts are now empty
|
||||
let sara_blocks_timmy_form = PersonBlockForm {
|
||||
person_id: data.sara.id,
|
||||
target_id: data.timmy.id,
|
||||
};
|
||||
PersonBlock::block(pool, &sara_blocks_timmy_form).await?;
|
||||
|
||||
let sara_unread_mentions_after_block =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.sara.id, true).await?;
|
||||
assert_eq!(1, sara_unread_mentions_after_block);
|
||||
|
||||
let sara_inbox_after_block = InboxCombinedQuery::default()
|
||||
.list(pool, data.sara.id)
|
||||
.await?;
|
||||
assert_length!(1, sara_inbox_after_block);
|
||||
|
||||
// Make sure the comment mention which timmy made is the hidden one
|
||||
assert!(matches!(
|
||||
sara_inbox_after_block[0],
|
||||
InboxCombinedView::PostMention(_)
|
||||
));
|
||||
|
||||
// Unblock user so we can reuse the same person
|
||||
PersonBlock::unblock(pool, &sara_blocks_timmy_form).await?;
|
||||
|
||||
// Test the type filter
|
||||
let sara_inbox_post_mentions_only = InboxCombinedQuery {
|
||||
type_: Some(InboxDataType::PostMention),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.sara.id)
|
||||
.await?;
|
||||
assert_length!(1, sara_inbox_post_mentions_only);
|
||||
|
||||
assert!(matches!(
|
||||
sara_inbox_post_mentions_only[0],
|
||||
InboxCombinedView::PostMention(_)
|
||||
));
|
||||
|
||||
// Turn Jessica into a bot account
|
||||
let person_update_form = PersonUpdateForm {
|
||||
bot_account: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
Person::update(pool, data.jessica.id, &person_update_form).await?;
|
||||
|
||||
// Make sure sara hides bots
|
||||
let sara_unread_mentions_after_hide_bots =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.sara.id, false).await?;
|
||||
assert_eq!(1, sara_unread_mentions_after_hide_bots);
|
||||
|
||||
let sara_inbox_after_hide_bots = InboxCombinedQuery::default()
|
||||
.list(pool, data.sara.id)
|
||||
.await?;
|
||||
assert_length!(1, sara_inbox_after_hide_bots);
|
||||
|
||||
// Make sure the post mention which jessica made is the hidden one
|
||||
assert!(matches!(
|
||||
sara_inbox_after_hide_bots[0],
|
||||
InboxCombinedView::CommentMention(_)
|
||||
));
|
||||
|
||||
// Mark them all as read
|
||||
PersonPostMention::mark_all_as_read(pool, data.sara.id).await?;
|
||||
PersonCommentMention::mark_all_as_read(pool, data.sara.id).await?;
|
||||
|
||||
// Make sure none come back
|
||||
let sara_unread_mentions =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.sara.id, false).await?;
|
||||
assert_eq!(0, sara_unread_mentions);
|
||||
|
||||
let sara_inbox_unread = InboxCombinedQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.sara.id)
|
||||
.await?;
|
||||
assert_length!(0, sara_inbox_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A helper function to coerce to a private message type for tests
|
||||
fn map_to_pm(inbox: &[InboxCombinedView]) -> Vec<PrivateMessageView> {
|
||||
inbox
|
||||
.iter()
|
||||
// Filter map to collect private messages
|
||||
.filter_map(|f| {
|
||||
if let InboxCombinedView::PrivateMessage(v) = f {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<PrivateMessageView>>()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn read_private_messages() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
setup_private_messages(&data, pool).await?;
|
||||
|
||||
let timmy_messages = map_to_pm(
|
||||
&InboxCombinedQuery::default()
|
||||
.list(pool, data.timmy.id)
|
||||
.await?,
|
||||
);
|
||||
|
||||
// The read even shows timmy's sent messages
|
||||
assert_length!(3, &timmy_messages);
|
||||
assert_eq!(timmy_messages[0].creator.id, data.jessica.id);
|
||||
assert_eq!(timmy_messages[0].recipient.id, data.timmy.id);
|
||||
assert_eq!(timmy_messages[1].creator.id, data.timmy.id);
|
||||
assert_eq!(timmy_messages[1].recipient.id, data.sara.id);
|
||||
assert_eq!(timmy_messages[2].creator.id, data.sara.id);
|
||||
assert_eq!(timmy_messages[2].recipient.id, data.timmy.id);
|
||||
|
||||
let timmy_unread =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.timmy.id, false).await?;
|
||||
assert_eq!(2, timmy_unread);
|
||||
|
||||
let timmy_unread_messages = map_to_pm(
|
||||
&InboxCombinedQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.timmy.id)
|
||||
.await?,
|
||||
);
|
||||
|
||||
// The unread hides timmy's sent messages
|
||||
assert_length!(2, &timmy_unread_messages);
|
||||
assert_eq!(timmy_unread_messages[0].creator.id, data.jessica.id);
|
||||
assert_eq!(timmy_unread_messages[0].recipient.id, data.timmy.id);
|
||||
assert_eq!(timmy_unread_messages[1].creator.id, data.sara.id);
|
||||
assert_eq!(timmy_unread_messages[1].recipient.id, data.timmy.id);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn ensure_private_message_person_block() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
setup_private_messages(&data, pool).await?;
|
||||
|
||||
// Make sure blocks are working
|
||||
let timmy_blocks_sara_form = PersonBlockForm {
|
||||
person_id: data.timmy.id,
|
||||
target_id: data.sara.id,
|
||||
};
|
||||
|
||||
let inserted_block = PersonBlock::block(pool, &timmy_blocks_sara_form).await?;
|
||||
|
||||
let expected_block = PersonBlock {
|
||||
person_id: data.timmy.id,
|
||||
target_id: data.sara.id,
|
||||
published: inserted_block.published,
|
||||
};
|
||||
assert_eq!(expected_block, inserted_block);
|
||||
|
||||
let timmy_messages = map_to_pm(
|
||||
&InboxCombinedQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.timmy.id)
|
||||
.await?,
|
||||
);
|
||||
|
||||
assert_length!(1, &timmy_messages);
|
||||
|
||||
let timmy_unread =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.timmy.id, false).await?;
|
||||
assert_eq!(1, timmy_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn ensure_private_message_instance_block() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
let data = init_data(pool).await?;
|
||||
setup_private_messages(&data, pool).await?;
|
||||
|
||||
// Make sure instance_blocks are working
|
||||
let timmy_blocks_instance_form = InstanceBlockForm {
|
||||
person_id: data.timmy.id,
|
||||
instance_id: data.sara.instance_id,
|
||||
};
|
||||
|
||||
let inserted_instance_block = InstanceBlock::block(pool, &timmy_blocks_instance_form).await?;
|
||||
|
||||
let expected_instance_block = InstanceBlock {
|
||||
person_id: data.timmy.id,
|
||||
instance_id: data.sara.instance_id,
|
||||
published: inserted_instance_block.published,
|
||||
};
|
||||
assert_eq!(expected_instance_block, inserted_instance_block);
|
||||
|
||||
let timmy_messages = map_to_pm(
|
||||
&InboxCombinedQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.timmy.id)
|
||||
.await?,
|
||||
);
|
||||
|
||||
assert_length!(0, &timmy_messages);
|
||||
|
||||
let timmy_unread =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.timmy.id, false).await?;
|
||||
assert_eq!(0, timmy_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
#[cfg(feature = "full")]
|
||||
pub mod comment_reply_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod community_follower_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod community_moderator_view;
|
||||
|
@ -9,7 +7,9 @@ pub mod community_person_ban_view;
|
|||
#[cfg(feature = "full")]
|
||||
pub mod community_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod person_mention_view;
|
||||
pub mod inbox_combined_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod person_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod private_message_view;
|
||||
pub mod structs;
|
||||
|
|
|
@ -1,383 +0,0 @@
|
|||
use crate::structs::PersonMentionView;
|
||||
use diesel::{
|
||||
dsl::{exists, not},
|
||||
pg::Pg,
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases::{self, creator_community_actions},
|
||||
newtypes::{PersonId, PersonMentionId},
|
||||
schema::{
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_aggregates,
|
||||
community,
|
||||
community_actions,
|
||||
local_user,
|
||||
person,
|
||||
person_actions,
|
||||
person_mention,
|
||||
post,
|
||||
},
|
||||
source::{community::CommunityFollower, local_user::LocalUser},
|
||||
utils::{
|
||||
actions,
|
||||
actions_alias,
|
||||
get_conn,
|
||||
limit_and_offset,
|
||||
DbConn,
|
||||
DbPool,
|
||||
ListFn,
|
||||
Queries,
|
||||
ReadFn,
|
||||
},
|
||||
CommentSortType,
|
||||
};
|
||||
|
||||
fn queries<'a>() -> Queries<
|
||||
impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option<PersonId>)>,
|
||||
impl ListFn<'a, PersonMentionView, PersonMentionQuery>,
|
||||
> {
|
||||
let creator_is_admin = exists(
|
||||
local_user::table.filter(
|
||||
comment::creator_id
|
||||
.eq(local_user::person_id)
|
||||
.and(local_user::admin.eq(true)),
|
||||
),
|
||||
);
|
||||
|
||||
let all_joins = move |query: person_mention::BoxedQuery<'a, Pg>,
|
||||
my_person_id: Option<PersonId>| {
|
||||
query
|
||||
.inner_join(comment::table)
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.inner_join(post::table.on(comment::post_id.eq(post::id)))
|
||||
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||
.inner_join(aliases::person1)
|
||||
.inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id)))
|
||||
.left_join(actions(
|
||||
community_actions::table,
|
||||
my_person_id,
|
||||
post::community_id,
|
||||
))
|
||||
.left_join(actions(comment_actions::table, my_person_id, comment::id))
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
my_person_id,
|
||||
comment::creator_id,
|
||||
))
|
||||
.left_join(actions_alias(
|
||||
creator_community_actions,
|
||||
comment::creator_id,
|
||||
post::community_id,
|
||||
))
|
||||
.select((
|
||||
person_mention::all_columns,
|
||||
comment::all_columns,
|
||||
person::all_columns,
|
||||
post::all_columns,
|
||||
community::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
comment_aggregates::all_columns,
|
||||
creator_community_actions
|
||||
.field(community_actions::received_ban)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
community_actions::received_ban.nullable().is_not_null(),
|
||||
creator_community_actions
|
||||
.field(community_actions::became_moderator)
|
||||
.nullable()
|
||||
.is_not_null(),
|
||||
creator_is_admin,
|
||||
CommunityFollower::select_subscribed_type(),
|
||||
comment_actions::saved.nullable().is_not_null(),
|
||||
person_actions::blocked.nullable().is_not_null(),
|
||||
comment_actions::like_score.nullable(),
|
||||
))
|
||||
};
|
||||
|
||||
let read =
|
||||
move |mut conn: DbConn<'a>,
|
||||
(person_mention_id, my_person_id): (PersonMentionId, Option<PersonId>)| async move {
|
||||
all_joins(
|
||||
person_mention::table.find(person_mention_id).into_boxed(),
|
||||
my_person_id,
|
||||
)
|
||||
.first(&mut conn)
|
||||
.await
|
||||
};
|
||||
|
||||
let list = move |mut conn: DbConn<'a>, o: PersonMentionQuery| async move {
|
||||
// These filters need to be kept in sync with the filters in
|
||||
// PersonMentionView::get_unread_mentions()
|
||||
let mut query = all_joins(person_mention::table.into_boxed(), o.my_person_id);
|
||||
|
||||
if let Some(recipient_id) = o.recipient_id {
|
||||
query = query.filter(person_mention::recipient_id.eq(recipient_id));
|
||||
}
|
||||
|
||||
if o.unread_only {
|
||||
query = query.filter(person_mention::read.eq(false));
|
||||
}
|
||||
|
||||
if !o.show_bot_accounts {
|
||||
query = query.filter(not(person::bot_account));
|
||||
};
|
||||
|
||||
query = match o.sort.unwrap_or(CommentSortType::Hot) {
|
||||
CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()),
|
||||
CommentSortType::Controversial => {
|
||||
query.then_order_by(comment_aggregates::controversy_rank.desc())
|
||||
}
|
||||
CommentSortType::New => query.then_order_by(comment::published.desc()),
|
||||
CommentSortType::Old => query.then_order_by(comment::published.asc()),
|
||||
CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
|
||||
};
|
||||
|
||||
// Don't show mentions from blocked persons
|
||||
query = query.filter(person_actions::blocked.is_null());
|
||||
|
||||
let (limit, offset) = limit_and_offset(o.page, o.limit)?;
|
||||
|
||||
query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load::<PersonMentionView>(&mut conn)
|
||||
.await
|
||||
};
|
||||
|
||||
Queries::new(read, list)
|
||||
}
|
||||
|
||||
impl PersonMentionView {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_mention_id: PersonMentionId,
|
||||
my_person_id: Option<PersonId>,
|
||||
) -> Result<Self, Error> {
|
||||
queries()
|
||||
.read(pool, (person_mention_id, my_person_id))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Gets the number of unread mentions
|
||||
pub async fn get_unread_mentions(
|
||||
pool: &mut DbPool<'_>,
|
||||
local_user: &LocalUser,
|
||||
) -> Result<i64, Error> {
|
||||
use diesel::dsl::count;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let mut query = person_mention::table
|
||||
.inner_join(comment::table)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(local_user.person_id),
|
||||
comment::creator_id,
|
||||
))
|
||||
.inner_join(person::table.on(comment::creator_id.eq(person::id)))
|
||||
.into_boxed();
|
||||
|
||||
// These filters need to be kept in sync with the filters in queries().list()
|
||||
if !local_user.show_bot_accounts {
|
||||
query = query.filter(not(person::bot_account));
|
||||
}
|
||||
|
||||
query
|
||||
// Don't count replies from blocked users
|
||||
.filter(person_actions::blocked.is_null())
|
||||
.filter(person_mention::recipient_id.eq(local_user.person_id))
|
||||
.filter(person_mention::read.eq(false))
|
||||
.filter(comment::deleted.eq(false))
|
||||
.filter(comment::removed.eq(false))
|
||||
.select(count(person_mention::id))
|
||||
.first::<i64>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct PersonMentionQuery {
|
||||
pub my_person_id: Option<PersonId>,
|
||||
pub recipient_id: Option<PersonId>,
|
||||
pub sort: Option<CommentSortType>,
|
||||
pub unread_only: bool,
|
||||
pub show_bot_accounts: bool,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
impl PersonMentionQuery {
|
||||
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonMentionView>, Error> {
|
||||
queries().list(pool, self).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::{person_mention_view::PersonMentionQuery, structs::PersonMentionView};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::Instance,
|
||||
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||
person::{Person, PersonInsertForm, PersonUpdateForm},
|
||||
person_block::{PersonBlock, PersonBlockForm},
|
||||
person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm},
|
||||
post::{Post, PostInsertForm},
|
||||
},
|
||||
traits::{Blockable, Crud},
|
||||
utils::build_db_pool_for_tests,
|
||||
};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_crud() -> LemmyResult<()> {
|
||||
let pool = &build_db_pool_for_tests();
|
||||
let pool = &mut pool.into();
|
||||
|
||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
|
||||
let new_person = PersonInsertForm::test_form(inserted_instance.id, "terrylake");
|
||||
|
||||
let inserted_person = Person::create(pool, &new_person).await?;
|
||||
|
||||
let recipient_form = PersonInsertForm::test_form(inserted_instance.id, "terrylakes recipient");
|
||||
|
||||
let inserted_recipient = Person::create(pool, &recipient_form).await?;
|
||||
let recipient_id = inserted_recipient.id;
|
||||
|
||||
let recipient_local_user =
|
||||
LocalUser::create(pool, &LocalUserInsertForm::test_form(recipient_id), vec![]).await?;
|
||||
|
||||
let new_community = CommunityInsertForm::new(
|
||||
inserted_instance.id,
|
||||
"test community lake".to_string(),
|
||||
"nada".to_owned(),
|
||||
"pubkey".to_string(),
|
||||
);
|
||||
let inserted_community = Community::create(pool, &new_community).await?;
|
||||
|
||||
let new_post = PostInsertForm::new(
|
||||
"A test post".into(),
|
||||
inserted_person.id,
|
||||
inserted_community.id,
|
||||
);
|
||||
let inserted_post = Post::create(pool, &new_post).await?;
|
||||
|
||||
let comment_form = CommentInsertForm::new(
|
||||
inserted_person.id,
|
||||
inserted_post.id,
|
||||
"A test comment".into(),
|
||||
);
|
||||
let inserted_comment = Comment::create(pool, &comment_form, None).await?;
|
||||
|
||||
let person_mention_form = PersonMentionInsertForm {
|
||||
recipient_id: inserted_recipient.id,
|
||||
comment_id: inserted_comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
let inserted_mention = PersonMention::create(pool, &person_mention_form).await?;
|
||||
|
||||
let expected_mention = PersonMention {
|
||||
id: inserted_mention.id,
|
||||
recipient_id: inserted_mention.recipient_id,
|
||||
comment_id: inserted_mention.comment_id,
|
||||
read: false,
|
||||
published: inserted_mention.published,
|
||||
};
|
||||
|
||||
let read_mention = PersonMention::read(pool, inserted_mention.id).await?;
|
||||
|
||||
let person_mention_update_form = PersonMentionUpdateForm { read: Some(false) };
|
||||
let updated_mention =
|
||||
PersonMention::update(pool, inserted_mention.id, &person_mention_update_form).await?;
|
||||
|
||||
// Test to make sure counts and blocks work correctly
|
||||
let unread_mentions =
|
||||
PersonMentionView::get_unread_mentions(pool, &recipient_local_user).await?;
|
||||
|
||||
let query = PersonMentionQuery {
|
||||
recipient_id: Some(recipient_id),
|
||||
my_person_id: Some(recipient_id),
|
||||
sort: None,
|
||||
unread_only: false,
|
||||
show_bot_accounts: true,
|
||||
page: None,
|
||||
limit: None,
|
||||
};
|
||||
let mentions = query.clone().list(pool).await?;
|
||||
assert_eq!(1, unread_mentions);
|
||||
assert_eq!(1, mentions.len());
|
||||
|
||||
// Block the person, and make sure these counts are now empty
|
||||
let block_form = PersonBlockForm {
|
||||
person_id: recipient_id,
|
||||
target_id: inserted_person.id,
|
||||
};
|
||||
PersonBlock::block(pool, &block_form).await?;
|
||||
|
||||
let unread_mentions_after_block =
|
||||
PersonMentionView::get_unread_mentions(pool, &recipient_local_user).await?;
|
||||
let mentions_after_block = query.clone().list(pool).await?;
|
||||
assert_eq!(0, unread_mentions_after_block);
|
||||
assert_eq!(0, mentions_after_block.len());
|
||||
|
||||
// Unblock user so we can reuse the same person
|
||||
PersonBlock::unblock(pool, &block_form).await?;
|
||||
|
||||
// Turn Terry into a bot account
|
||||
let person_update_form = PersonUpdateForm {
|
||||
bot_account: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
Person::update(pool, inserted_person.id, &person_update_form).await?;
|
||||
|
||||
let recipient_local_user_update_form = LocalUserUpdateForm {
|
||||
show_bot_accounts: Some(false),
|
||||
..Default::default()
|
||||
};
|
||||
LocalUser::update(
|
||||
pool,
|
||||
recipient_local_user.id,
|
||||
&recipient_local_user_update_form,
|
||||
)
|
||||
.await?;
|
||||
let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?;
|
||||
|
||||
let unread_mentions_after_hide_bots =
|
||||
PersonMentionView::get_unread_mentions(pool, &recipient_local_user_view.local_user).await?;
|
||||
|
||||
let mut query_without_bots = query.clone();
|
||||
query_without_bots.show_bot_accounts = false;
|
||||
let replies_after_hide_bots = query_without_bots.list(pool).await?;
|
||||
assert_eq!(0, unread_mentions_after_hide_bots);
|
||||
assert_eq!(0, replies_after_hide_bots.len());
|
||||
|
||||
Comment::delete(pool, inserted_comment.id).await?;
|
||||
Post::delete(pool, inserted_post.id).await?;
|
||||
Community::delete(pool, inserted_community.id).await?;
|
||||
Person::delete(pool, inserted_person.id).await?;
|
||||
Person::delete(pool, inserted_recipient.id).await?;
|
||||
Instance::delete(pool, inserted_instance.id).await?;
|
||||
|
||||
assert_eq!(expected_mention, read_mention);
|
||||
assert_eq!(expected_mention, inserted_mention);
|
||||
assert_eq!(expected_mention, updated_mention);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
42
crates/db_views_actor/src/private_message_view.rs
Normal file
42
crates/db_views_actor/src/private_message_view.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use crate::structs::PrivateMessageView;
|
||||
use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
aliases,
|
||||
newtypes::PrivateMessageId,
|
||||
schema::{instance_actions, person, person_actions, private_message},
|
||||
utils::{actions, get_conn, DbPool},
|
||||
};
|
||||
|
||||
impl PrivateMessageView {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
private_message_id: PrivateMessageId,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
private_message::table
|
||||
.find(private_message_id)
|
||||
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
|
||||
.inner_join(
|
||||
aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))),
|
||||
)
|
||||
.left_join(actions(
|
||||
person_actions::table,
|
||||
Some(aliases::person1.field(person::id)),
|
||||
private_message::creator_id,
|
||||
))
|
||||
.left_join(actions(
|
||||
instance_actions::table,
|
||||
Some(aliases::person1.field(person::id)),
|
||||
person::instance_id,
|
||||
))
|
||||
.select((
|
||||
private_message::all_columns,
|
||||
person::all_columns,
|
||||
aliases::person1.fields(person::all_columns),
|
||||
))
|
||||
.first(conn)
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
#[cfg(feature = "full")]
|
||||
use diesel::Queryable;
|
||||
use lemmy_db_schema::{
|
||||
aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates},
|
||||
aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates, PostAggregates},
|
||||
source::{
|
||||
comment::Comment,
|
||||
comment_reply::CommentReply,
|
||||
community::Community,
|
||||
images::ImageDetails,
|
||||
person::Person,
|
||||
person_mention::PersonMention,
|
||||
person_comment_mention::PersonCommentMention,
|
||||
person_post_mention::PersonPostMention,
|
||||
post::Post,
|
||||
private_message::PrivateMessage,
|
||||
},
|
||||
SubscribedType,
|
||||
};
|
||||
|
@ -93,9 +96,9 @@ pub enum CommunitySortType {
|
|||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A person mention view.
|
||||
pub struct PersonMentionView {
|
||||
pub person_mention: PersonMention,
|
||||
/// A person comment mention view.
|
||||
pub struct PersonCommentMentionView {
|
||||
pub person_comment_mention: PersonCommentMention,
|
||||
pub comment: Comment,
|
||||
pub creator: Person,
|
||||
pub post: Post,
|
||||
|
@ -113,6 +116,35 @@ pub struct PersonMentionView {
|
|||
pub my_vote: Option<i16>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A person post mention view.
|
||||
pub struct PersonPostMentionView {
|
||||
pub person_post_mention: PersonPostMention,
|
||||
pub post: Post,
|
||||
pub creator: Person,
|
||||
pub community: Community,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub image_details: Option<ImageDetails>,
|
||||
pub recipient: Person,
|
||||
pub counts: PostAggregates,
|
||||
pub creator_banned_from_community: bool,
|
||||
pub banned_from_community: bool,
|
||||
pub creator_is_moderator: bool,
|
||||
pub creator_is_admin: bool,
|
||||
pub subscribed: SubscribedType,
|
||||
pub saved: bool,
|
||||
pub read: bool,
|
||||
pub hidden: bool,
|
||||
pub creator_blocked: bool,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub my_vote: Option<i16>,
|
||||
pub unread_comments: i64,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
|
@ -159,3 +191,69 @@ pub struct PendingFollow {
|
|||
pub is_new_instance: bool,
|
||||
pub subscribed: SubscribedType,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// A private message view.
|
||||
pub struct PrivateMessageView {
|
||||
pub private_message: PrivateMessage,
|
||||
pub creator: Person,
|
||||
pub recipient: Person,
|
||||
}
|
||||
|
||||
/// like PaginationCursor but for the report_combined table
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct InboxCombinedPaginationCursor(pub String);
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
/// A combined inbox view
|
||||
pub struct InboxCombinedViewInternal {
|
||||
// Comment reply
|
||||
pub comment_reply: Option<CommentReply>,
|
||||
// Person comment mention
|
||||
pub person_comment_mention: Option<PersonCommentMention>,
|
||||
// Person post mention
|
||||
pub person_post_mention: Option<PersonPostMention>,
|
||||
pub post_counts: Option<PostAggregates>,
|
||||
pub post_unread_comments: Option<i64>,
|
||||
pub post_saved: bool,
|
||||
pub post_read: bool,
|
||||
pub post_hidden: bool,
|
||||
pub my_post_vote: Option<i16>,
|
||||
pub image_details: Option<ImageDetails>,
|
||||
// Private message
|
||||
pub private_message: Option<PrivateMessage>,
|
||||
// Shared
|
||||
pub post: Option<Post>,
|
||||
pub community: Option<Community>,
|
||||
pub comment: Option<Comment>,
|
||||
pub comment_counts: Option<CommentAggregates>,
|
||||
pub comment_saved: bool,
|
||||
pub my_comment_vote: Option<i16>,
|
||||
pub subscribed: SubscribedType,
|
||||
pub item_creator: Person,
|
||||
pub item_recipient: Person,
|
||||
pub item_creator_is_admin: bool,
|
||||
pub item_creator_is_moderator: bool,
|
||||
pub item_creator_banned_from_community: bool,
|
||||
pub item_creator_blocked: bool,
|
||||
pub banned_from_community: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
// Use serde's internal tagging, to work easier with javascript libraries
|
||||
#[serde(tag = "type_")]
|
||||
pub enum InboxCombinedView {
|
||||
CommentReply(CommentReplyView),
|
||||
CommentMention(PersonCommentMentionView),
|
||||
PostMention(PersonPostMentionView),
|
||||
PrivateMessage(PrivateMessageView),
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ use lemmy_api_common::{context::LemmyContext, utils::check_private_instance};
|
|||
use lemmy_db_schema::{
|
||||
source::{community::Community, person::Person},
|
||||
traits::ApubActor,
|
||||
CommentSortType,
|
||||
CommunityVisibility,
|
||||
ListingType,
|
||||
PostSortType,
|
||||
|
@ -15,11 +14,7 @@ use lemmy_db_views::{
|
|||
post_view::PostQuery,
|
||||
structs::{PostView, SiteView},
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
comment_reply_view::CommentReplyQuery,
|
||||
person_mention_view::PersonMentionQuery,
|
||||
structs::{CommentReplyView, PersonMentionView},
|
||||
};
|
||||
use lemmy_db_views_actor::{inbox_combined_view::InboxCombinedQuery, structs::InboxCombinedView};
|
||||
use lemmy_utils::{
|
||||
cache_header::cache_1hour,
|
||||
error::{LemmyError, LemmyErrorType, LemmyResult},
|
||||
|
@ -360,37 +355,20 @@ async fn get_feed_front(
|
|||
async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult<Channel> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let local_user = local_user_view_from_jwt(jwt, context).await?;
|
||||
let person_id = local_user.local_user.person_id;
|
||||
let show_bot_accounts = local_user.local_user.show_bot_accounts;
|
||||
|
||||
let sort = CommentSortType::New;
|
||||
let my_person_id = local_user.person.id;
|
||||
let show_bot_accounts = Some(local_user.local_user.show_bot_accounts);
|
||||
|
||||
check_private_instance(&Some(local_user.clone()), &site_view.local_site)?;
|
||||
|
||||
let replies = CommentReplyQuery {
|
||||
recipient_id: (Some(person_id)),
|
||||
my_person_id: (Some(person_id)),
|
||||
show_bot_accounts: (show_bot_accounts),
|
||||
sort: (Some(sort)),
|
||||
limit: (Some(RSS_FETCH_LIMIT)),
|
||||
let inbox = InboxCombinedQuery {
|
||||
show_bot_accounts,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.await?;
|
||||
|
||||
let mentions = PersonMentionQuery {
|
||||
recipient_id: (Some(person_id)),
|
||||
my_person_id: (Some(person_id)),
|
||||
show_bot_accounts: (show_bot_accounts),
|
||||
sort: (Some(sort)),
|
||||
limit: (Some(RSS_FETCH_LIMIT)),
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool())
|
||||
.list(&mut context.pool(), my_person_id)
|
||||
.await?;
|
||||
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let items = create_reply_and_mention_items(replies, mentions, &protocol_and_hostname)?;
|
||||
let items = create_reply_and_mention_items(inbox, &protocol_and_hostname)?;
|
||||
|
||||
let mut channel = Channel {
|
||||
namespaces: RSS_NAMESPACE.clone(),
|
||||
|
@ -409,39 +387,55 @@ async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult<Channe
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn create_reply_and_mention_items(
|
||||
replies: Vec<CommentReplyView>,
|
||||
mentions: Vec<PersonMentionView>,
|
||||
inbox: Vec<InboxCombinedView>,
|
||||
protocol_and_hostname: &str,
|
||||
) -> LemmyResult<Vec<Item>> {
|
||||
let mut reply_items: Vec<Item> = replies
|
||||
let reply_items: Vec<Item> = inbox
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let reply_url = format!("{}/comment/{}", protocol_and_hostname, r.comment.id);
|
||||
build_item(
|
||||
&r.creator.name,
|
||||
&r.comment.published,
|
||||
&reply_url,
|
||||
&r.comment.content,
|
||||
protocol_and_hostname,
|
||||
)
|
||||
.map(|r| match r {
|
||||
InboxCombinedView::CommentReply(v) => {
|
||||
let reply_url = format!("{}/comment/{}", protocol_and_hostname, v.comment.id);
|
||||
build_item(
|
||||
&v.creator.name,
|
||||
&v.comment.published,
|
||||
&reply_url,
|
||||
&v.comment.content,
|
||||
protocol_and_hostname,
|
||||
)
|
||||
}
|
||||
InboxCombinedView::CommentMention(v) => {
|
||||
let mention_url = format!("{}/comment/{}", protocol_and_hostname, v.comment.id);
|
||||
build_item(
|
||||
&v.creator.name,
|
||||
&v.comment.published,
|
||||
&mention_url,
|
||||
&v.comment.content,
|
||||
protocol_and_hostname,
|
||||
)
|
||||
}
|
||||
InboxCombinedView::PostMention(v) => {
|
||||
let mention_url = format!("{}/post/{}", protocol_and_hostname, v.post.id);
|
||||
build_item(
|
||||
&v.creator.name,
|
||||
&v.post.published,
|
||||
&mention_url,
|
||||
&v.post.body.clone().unwrap_or_default(),
|
||||
protocol_and_hostname,
|
||||
)
|
||||
}
|
||||
InboxCombinedView::PrivateMessage(v) => {
|
||||
let inbox_url = format!("{}/inbox", protocol_and_hostname);
|
||||
build_item(
|
||||
&v.creator.name,
|
||||
&v.private_message.published,
|
||||
&inbox_url,
|
||||
&v.private_message.content,
|
||||
protocol_and_hostname,
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<LemmyResult<Vec<Item>>>()?;
|
||||
|
||||
let mut mention_items: Vec<Item> = mentions
|
||||
.iter()
|
||||
.map(|m| {
|
||||
let mention_url = format!("{}/comment/{}", protocol_and_hostname, m.comment.id);
|
||||
build_item(
|
||||
&m.creator.name,
|
||||
&m.comment.published,
|
||||
&mention_url,
|
||||
&m.comment.content,
|
||||
protocol_and_hostname,
|
||||
)
|
||||
})
|
||||
.collect::<LemmyResult<Vec<Item>>>()?;
|
||||
|
||||
reply_items.append(&mut mention_items);
|
||||
Ok(reply_items)
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ pub enum LemmyErrorType {
|
|||
CouldntHidePost,
|
||||
CouldntUpdateCommunity,
|
||||
CouldntUpdateReplies,
|
||||
CouldntUpdatePersonMentions,
|
||||
CouldntUpdatePersonCommentMentions,
|
||||
CouldntCreatePost,
|
||||
CouldntCreatePrivateMessage,
|
||||
CouldntUpdatePrivate,
|
||||
|
|
|
@ -33,12 +33,8 @@ CREATE TABLE person_saved_combined (
|
|||
id serial PRIMARY KEY,
|
||||
saved timestamptz NOT NULL,
|
||||
person_id int NOT NULL REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
-- Unique constraints are different here, check for person AND item.
|
||||
-- Otherwise you won't be able to add multiple posts
|
||||
UNIQUE (person_id, post_id),
|
||||
UNIQUE (person_id, comment_id),
|
||||
post_id int UNIQUE REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
comment_id int UNIQUE REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
-- Make sure only one of the columns is not null
|
||||
CHECK (num_nonnulls (post_id, comment_id) = 1)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
-- Rename the person_mention table to person_comment_mention
|
||||
ALTER TABLE person_comment_mention RENAME TO person_mention;
|
||||
|
||||
-- Drop the new tables
|
||||
DROP TABLE person_post_mention, inbox_combined;
|
||||
|
69
migrations/2024-12-10-193418_add_inbox_combined_table/up.sql
Normal file
69
migrations/2024-12-10-193418_add_inbox_combined_table/up.sql
Normal file
|
@ -0,0 +1,69 @@
|
|||
-- Creates combined tables for
|
||||
-- Inbox: (replies, comment mentions, post mentions, and private_messages)
|
||||
-- Also add post mentions, since these didn't exist before.
|
||||
-- Rename the person_mention table to person_comment_mention
|
||||
ALTER TABLE person_mention RENAME TO person_comment_mention;
|
||||
|
||||
-- Create the new post_mention table
|
||||
CREATE TABLE person_post_mention (
|
||||
id serial PRIMARY KEY,
|
||||
recipient_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
read boolean DEFAULT FALSE NOT NULL,
|
||||
published timestamptz NOT NULL DEFAULT now(),
|
||||
UNIQUE (recipient_id, post_id)
|
||||
);
|
||||
|
||||
CREATE TABLE inbox_combined (
|
||||
id serial PRIMARY KEY,
|
||||
published timestamptz NOT NULL,
|
||||
comment_reply_id int UNIQUE REFERENCES comment_reply ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
person_comment_mention_id int UNIQUE REFERENCES person_comment_mention ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
person_post_mention_id int UNIQUE REFERENCES person_post_mention ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
private_message_id int UNIQUE REFERENCES private_message ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
-- Make sure only one of the columns is not null
|
||||
CHECK (num_nonnulls (comment_reply_id, person_comment_mention_id, person_post_mention_id, private_message_id) = 1)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_inbox_combined_published ON inbox_combined (published DESC, id DESC);
|
||||
|
||||
CREATE INDEX idx_inbox_combined_published_asc ON inbox_combined (reverse_timestamp_sort (published) DESC, id DESC);
|
||||
|
||||
-- Updating the history
|
||||
INSERT INTO inbox_combined (published, comment_reply_id, person_comment_mention_id, person_post_mention_id, private_message_id)
|
||||
SELECT
|
||||
published,
|
||||
id,
|
||||
NULL::int,
|
||||
NULL::int,
|
||||
NULL::int
|
||||
FROM
|
||||
comment_reply
|
||||
UNION ALL
|
||||
SELECT
|
||||
published,
|
||||
NULL::int,
|
||||
id,
|
||||
NULL::int,
|
||||
NULL::int
|
||||
FROM
|
||||
person_comment_mention
|
||||
UNION ALL
|
||||
SELECT
|
||||
published,
|
||||
NULL::int,
|
||||
NULL::int,
|
||||
id,
|
||||
NULL::int
|
||||
FROM
|
||||
person_post_mention
|
||||
UNION ALL
|
||||
SELECT
|
||||
published,
|
||||
NULL::int,
|
||||
NULL::int,
|
||||
NULL::int,
|
||||
id
|
||||
FROM
|
||||
private_message;
|
||||
|
|
@ -28,10 +28,8 @@ use lemmy_api::{
|
|||
login::login,
|
||||
logout::logout,
|
||||
notifications::{
|
||||
list_mentions::list_mentions,
|
||||
list_replies::list_replies,
|
||||
mark_all_read::mark_all_notifications_read,
|
||||
mark_mention_read::mark_person_mention_as_read,
|
||||
mark_comment_mention_read::mark_comment_mention_as_read,
|
||||
mark_reply_read::mark_reply_as_read,
|
||||
unread_count::unread_count,
|
||||
},
|
||||
|
@ -108,7 +106,6 @@ use lemmy_api_crud::{
|
|||
private_message::{
|
||||
create::create_private_message,
|
||||
delete::delete_private_message,
|
||||
read::get_private_message,
|
||||
update::update_private_message,
|
||||
},
|
||||
site::{create::create_site, read::get_site_v3, update::update_site},
|
||||
|
@ -254,7 +251,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
.service(
|
||||
scope("/private_message")
|
||||
.wrap(rate_limit.message())
|
||||
.route("/list", get().to(get_private_message))
|
||||
.route("", post().to(create_private_message))
|
||||
.route("", put().to(update_private_message))
|
||||
.route("/delete", post().to(delete_private_message))
|
||||
|
@ -315,12 +311,10 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
scope("/user")
|
||||
.wrap(rate_limit.message())
|
||||
.route("", get().to(read_person))
|
||||
.route("/mention", get().to(list_mentions))
|
||||
.route(
|
||||
"/mention/mark_as_read",
|
||||
post().to(mark_person_mention_as_read),
|
||||
post().to(mark_comment_mention_as_read),
|
||||
)
|
||||
.route("/replies", get().to(list_replies))
|
||||
// Admin action. I don't like that it's in /user
|
||||
.route("/ban", post().to(ban_from_site))
|
||||
.route("/banned", get().to(list_banned_users))
|
||||
|
|
|
@ -35,10 +35,10 @@ use lemmy_api::{
|
|||
login::login,
|
||||
logout::logout,
|
||||
notifications::{
|
||||
list_mentions::list_mentions,
|
||||
list_replies::list_replies,
|
||||
list_inbox::list_inbox,
|
||||
mark_all_read::mark_all_notifications_read,
|
||||
mark_mention_read::mark_person_mention_as_read,
|
||||
mark_comment_mention_read::mark_comment_mention_as_read,
|
||||
mark_post_mention_read::mark_post_mention_as_read,
|
||||
mark_reply_read::mark_reply_as_read,
|
||||
unread_count::unread_count,
|
||||
},
|
||||
|
@ -125,7 +125,6 @@ use lemmy_api_crud::{
|
|||
private_message::{
|
||||
create::create_private_message,
|
||||
delete::delete_private_message,
|
||||
read::get_private_message,
|
||||
update::update_private_message,
|
||||
},
|
||||
site::{create::create_site, read::get_site_v4, update::update_site},
|
||||
|
@ -283,7 +282,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
// Private Message
|
||||
.service(
|
||||
scope("/private_message")
|
||||
.route("/list", get().to(get_private_message))
|
||||
.route("", post().to(create_private_message))
|
||||
.route("", put().to(update_private_message))
|
||||
.route("/delete", post().to(delete_private_message))
|
||||
|
@ -314,28 +312,21 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
.route("/verify_email", post().to(verify_email))
|
||||
.route("/saved", get().to(list_person_saved)),
|
||||
)
|
||||
.route("/account/settings/save", put().to(save_user_settings))
|
||||
.service(
|
||||
scope("/account/settings")
|
||||
.wrap(rate_limit.import_user_settings())
|
||||
.route("/export", get().to(export_settings))
|
||||
.route("/import", post().to(import_settings)),
|
||||
)
|
||||
.service(
|
||||
scope("/account")
|
||||
.route("", get().to(get_my_user))
|
||||
.route("/list_media", get().to(list_media))
|
||||
.route("/mention", get().to(list_mentions))
|
||||
.route("/replies", get().to(list_replies))
|
||||
.route("/inbox", get().to(list_inbox))
|
||||
.route("/delete", post().to(delete_account))
|
||||
.route(
|
||||
"/mention/mark_as_read",
|
||||
post().to(mark_person_mention_as_read),
|
||||
)
|
||||
.route(
|
||||
"/mention/mark_as_read/all",
|
||||
post().to(mark_all_notifications_read),
|
||||
.service(
|
||||
scope("/mention")
|
||||
.route(
|
||||
"/comment/mark_as_read",
|
||||
post().to(mark_comment_mention_as_read),
|
||||
)
|
||||
.route("/post/mark_as_read", post().to(mark_post_mention_as_read)),
|
||||
)
|
||||
.route("/mark_as_read/all", post().to(mark_all_notifications_read))
|
||||
.route("/report_count", get().to(report_count))
|
||||
.route("/unread_count", get().to(unread_count))
|
||||
.route("/list_logins", get().to(list_logins))
|
||||
|
@ -349,6 +340,14 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
|
|||
.route("/person", post().to(user_block_person))
|
||||
.route("/community", post().to(user_block_community))
|
||||
.route("/instance", post().to(user_block_instance)),
|
||||
)
|
||||
.route("/settings/save", put().to(save_user_settings))
|
||||
// Account settings import / export have a strict rate limit
|
||||
.service(
|
||||
scope("/settings")
|
||||
.wrap(rate_limit.import_user_settings())
|
||||
.route("/export", get().to(export_settings))
|
||||
.route("/import", post().to(import_settings)),
|
||||
),
|
||||
)
|
||||
// User actions
|
||||
|
|
Loading…
Add table
Reference in a new issue