mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-09-02 03:03:57 +00:00
* Add post_actions.disable_notifications (fixes #3042) * Split up logic for send_local_notifs() * refactor * fmt * add api endpoint, check * dont call send_local_notifs from comment delete/remove * move scrape_text_for_mentions() inside send_local_notifs() * nullable * simplify * handle parent notification first * cleanup * remove `CommentResponse.recipient_ids` * post notifications enum * Follow posts (fixes #3069) * use references * cleanup * new file * db migration to merge mention, reply tables * code adjustments * adjust test code * adjust enum case * wip: get rid of inbox_combined table * add table local_user_notification * tests compiling * get rid of inbox_combined, only use notification table * rename view * notify private messages * clippy * copy existing data * wip: tests * move tests * clippy * notify post subscribers * community subscribe * test fixes * migration fix * rename routes * separate struct for api * rename api params * merge migrations * separate notification modes for post/community * fix * down migration copy data * test fix * update api tests * only single notification table * clippy * use local user id for recipient * fix comments * rename table * recipient local user * add indices * keep local user id * renames and cleanup * NotificationDataType * change notification response * fix api tests * test fix * remove private_message.read * fixes * test fix * fix
This commit is contained in:
parent
31dd9719b0
commit
72d254b4db
98 changed files with 2336 additions and 2686 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -3107,10 +3107,10 @@ dependencies = [
|
|||
"lemmy_db_views_community_follower",
|
||||
"lemmy_db_views_community_moderator",
|
||||
"lemmy_db_views_community_person_ban",
|
||||
"lemmy_db_views_inbox_combined",
|
||||
"lemmy_db_views_local_image",
|
||||
"lemmy_db_views_local_user",
|
||||
"lemmy_db_views_modlog_combined",
|
||||
"lemmy_db_views_notification",
|
||||
"lemmy_db_views_person",
|
||||
"lemmy_db_views_person_content_combined",
|
||||
"lemmy_db_views_person_liked_combined",
|
||||
|
@ -3143,10 +3143,10 @@ dependencies = [
|
|||
"lemmy_db_views_community_follower",
|
||||
"lemmy_db_views_community_moderator",
|
||||
"lemmy_db_views_custom_emoji",
|
||||
"lemmy_db_views_inbox_combined",
|
||||
"lemmy_db_views_local_image",
|
||||
"lemmy_db_views_local_user",
|
||||
"lemmy_db_views_modlog_combined",
|
||||
"lemmy_db_views_notification",
|
||||
"lemmy_db_views_person",
|
||||
"lemmy_db_views_person_content_combined",
|
||||
"lemmy_db_views_person_liked_combined",
|
||||
|
@ -3209,6 +3209,7 @@ dependencies = [
|
|||
"actix-web-httpauth",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"derive-new",
|
||||
"diesel_ltree",
|
||||
"either",
|
||||
"encoding_rs",
|
||||
|
@ -3227,6 +3228,7 @@ dependencies = [
|
|||
"lemmy_db_views_community_person_ban",
|
||||
"lemmy_db_views_local_image",
|
||||
"lemmy_db_views_local_user",
|
||||
"lemmy_db_views_notification",
|
||||
"lemmy_db_views_person",
|
||||
"lemmy_db_views_post",
|
||||
"lemmy_db_views_private_message",
|
||||
|
@ -3316,6 +3318,7 @@ dependencies = [
|
|||
"lemmy_db_views_community_moderator",
|
||||
"lemmy_db_views_community_person_ban",
|
||||
"lemmy_db_views_local_user",
|
||||
"lemmy_db_views_private_message",
|
||||
"lemmy_db_views_site",
|
||||
"lemmy_utils",
|
||||
"moka",
|
||||
|
@ -3500,25 +3503,6 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lemmy_db_views_inbox_combined"
|
||||
version = "1.0.0-alpha.5"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"i-love-jesus",
|
||||
"lemmy_db_schema",
|
||||
"lemmy_db_schema_file",
|
||||
"lemmy_db_views_private_message",
|
||||
"lemmy_utils",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"serial_test",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lemmy_db_views_local_image"
|
||||
version = "1.0.0-alpha.5"
|
||||
|
@ -3572,6 +3556,24 @@ dependencies = [
|
|||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lemmy_db_views_notification"
|
||||
version = "1.0.0-alpha.5"
|
||||
dependencies = [
|
||||
"diesel",
|
||||
"diesel-async",
|
||||
"i-love-jesus",
|
||||
"lemmy_db_schema",
|
||||
"lemmy_db_schema_file",
|
||||
"lemmy_db_views_comment",
|
||||
"lemmy_db_views_post",
|
||||
"lemmy_db_views_private_message",
|
||||
"lemmy_utils",
|
||||
"serde",
|
||||
"serde_with",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lemmy_db_views_person"
|
||||
version = "1.0.0-alpha.5"
|
||||
|
@ -3882,10 +3884,10 @@ dependencies = [
|
|||
"lemmy_db_schema",
|
||||
"lemmy_db_schema_file",
|
||||
"lemmy_db_views_community",
|
||||
"lemmy_db_views_inbox_combined",
|
||||
"lemmy_db_views_local_image",
|
||||
"lemmy_db_views_local_user",
|
||||
"lemmy_db_views_modlog_combined",
|
||||
"lemmy_db_views_notification",
|
||||
"lemmy_db_views_person_content_combined",
|
||||
"lemmy_db_views_post",
|
||||
"lemmy_db_views_site",
|
||||
|
|
|
@ -65,7 +65,7 @@ members = [
|
|||
"crates/db_views/community_follower",
|
||||
"crates/db_views/community_person_ban",
|
||||
"crates/db_views/custom_emoji",
|
||||
"crates/db_views/inbox_combined",
|
||||
"crates/db_views/notification",
|
||||
"crates/db_views/modlog_combined",
|
||||
"crates/db_views/person_content_combined",
|
||||
"crates/db_views/person_saved_combined",
|
||||
|
@ -124,7 +124,7 @@ lemmy_db_views_community_follower = { version = "=1.0.0-alpha.5", path = "./crat
|
|||
lemmy_db_views_community_moderator = { version = "=1.0.0-alpha.5", path = "./crates/db_views/community_moderator" }
|
||||
lemmy_db_views_community_person_ban = { version = "=1.0.0-alpha.5", path = "./crates/db_views/community_person_ban" }
|
||||
lemmy_db_views_custom_emoji = { version = "=1.0.0-alpha.5", path = "./crates/db_views/custom_emoji" }
|
||||
lemmy_db_views_inbox_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/inbox_combined" }
|
||||
lemmy_db_views_notification = { version = "=1.0.0-alpha.5", path = "./crates/db_views/notification" }
|
||||
lemmy_db_views_local_image = { version = "=1.0.0-alpha.5", path = "./crates/db_views/local_image" }
|
||||
lemmy_db_views_local_user = { version = "=1.0.0-alpha.5", path = "./crates/db_views/local_user" }
|
||||
lemmy_db_views_modlog_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/modlog_combined" }
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"jest": "^29.5.0",
|
||||
"joi": "^17.13.3",
|
||||
"lemmy-js-client": "1.0.0-rename-rate-limit-columns.1",
|
||||
"lemmy-js-client": "1.0.0-post-notifications.5",
|
||||
"prettier": "^3.5.3",
|
||||
"ts-jest": "^29.3.2",
|
||||
"tsoa": "^6.6.0",
|
||||
|
|
|
@ -36,8 +36,8 @@ importers:
|
|||
specifier: ^17.13.3
|
||||
version: 17.13.3
|
||||
lemmy-js-client:
|
||||
specifier: 1.0.0-rename-rate-limit-columns.1
|
||||
version: 1.0.0-rename-rate-limit-columns.1
|
||||
specifier: 1.0.0-post-notifications.5
|
||||
version: 1.0.0-post-notifications.5
|
||||
prettier:
|
||||
specifier: ^3.5.3
|
||||
version: 3.5.3
|
||||
|
@ -1594,8 +1594,8 @@ packages:
|
|||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
lemmy-js-client@1.0.0-rename-rate-limit-columns.1:
|
||||
resolution: {integrity: sha512-zlVJ4zkoI/7hNm6x7vr+Su2cRjAr8PQCA9j0GeK1UCMEIBLLSltknuRPC79VJY2sUhRAuR2JwTR0JtZ75SH2XQ==}
|
||||
lemmy-js-client@1.0.0-post-notifications.5:
|
||||
resolution: {integrity: sha512-2P0KPCordLRfuGTcgsU3pHSFJlVN5t91e04yhpUf5fZT7iTdlEctFQFtURsvfYPNYK/sdvsucqYbnpbbHJUCTA==}
|
||||
|
||||
leven@3.1.0:
|
||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||
|
@ -4404,7 +4404,7 @@ snapshots:
|
|||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
lemmy-js-client@1.0.0-rename-rate-limit-columns.1:
|
||||
lemmy-js-client@1.0.0-post-notifications.5:
|
||||
dependencies:
|
||||
'@tsoa/runtime': 6.6.0
|
||||
transitivePeerDependencies:
|
||||
|
|
|
@ -36,16 +36,14 @@ import {
|
|||
saveUserSettings,
|
||||
listReports,
|
||||
listPersonContent,
|
||||
listInbox,
|
||||
listNotifications,
|
||||
} from "./shared";
|
||||
import {
|
||||
CommentReplyView,
|
||||
CommentReportView,
|
||||
CommentView,
|
||||
CommunityView,
|
||||
DistinguishComment,
|
||||
LemmyError,
|
||||
PersonCommentMentionView,
|
||||
ReportCombinedView,
|
||||
SaveUserSettings,
|
||||
} from "lemmy-js-client";
|
||||
|
@ -409,23 +407,25 @@ test("Reply to a comment from another instance, get notification", async () => {
|
|||
|
||||
// check inbox of replies on alpha, fetching read/unread both
|
||||
let alphaRepliesRes = await waitUntil(
|
||||
() => listInbox(alpha, "CommentReply"),
|
||||
r => r.inbox.length > 0,
|
||||
() => listNotifications(alpha, "Reply"),
|
||||
r => r.notifications.length > 0,
|
||||
);
|
||||
const alphaReply = alphaRepliesRes.notifications.find(
|
||||
r =>
|
||||
r.data.type_ == "Comment" &&
|
||||
r.data.comment.id === alphaComment.comment.id,
|
||||
);
|
||||
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();
|
||||
expect(alphaReply.community.local).toBe(false);
|
||||
expect(alphaReply.creator.local).toBe(false);
|
||||
expect(alphaReply.comment.score).toBe(1);
|
||||
const alphaReplyData = alphaReply.data as CommentView;
|
||||
expect(alphaReplyData.comment!.content).toBeDefined();
|
||||
expect(alphaReplyData.community!.local).toBe(false);
|
||||
expect(alphaReplyData.creator.local).toBe(false);
|
||||
expect(alphaReplyData.comment!.score).toBe(1);
|
||||
// ToDo: interesting alphaRepliesRes.replies[0].comment_reply.id is 1, meaning? how did that come about?
|
||||
expect(alphaReply.comment.id).toBe(alphaComment.comment.id);
|
||||
expect(alphaReplyData.comment!.id).toBe(alphaComment.comment.id);
|
||||
// this is a new notification, getReplies fetch was for read/unread both, confirm it is unread.
|
||||
expect(alphaReply.comment_reply.read).toBe(false);
|
||||
assertCommentFederation(alphaReply, replyRes.comment_view);
|
||||
expect(alphaReply.notification.read).toBe(false);
|
||||
});
|
||||
|
||||
test("Bot reply notifications are filtered when bots are hidden", async () => {
|
||||
|
@ -472,9 +472,9 @@ test("Bot reply notifications are filtered when bots are hidden", async () => {
|
|||
alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||
expect(alphaUnreadCountRes.count).toBe(1);
|
||||
|
||||
let alphaUnreadRepliesRes = await listInbox(alpha, "CommentReply", true);
|
||||
expect(alphaUnreadRepliesRes.inbox.length).toBe(1);
|
||||
expect((alphaUnreadRepliesRes.inbox[0] as CommentReplyView).comment.id).toBe(
|
||||
let alphaUnreadRepliesRes = await listNotifications(alpha, "Reply", true);
|
||||
expect(alphaUnreadRepliesRes.notifications.length).toBe(1);
|
||||
expect(alphaUnreadRepliesRes.notifications[0].notification.comment_id).toBe(
|
||||
commentRes.comment_view.comment.id,
|
||||
);
|
||||
});
|
||||
|
@ -526,17 +526,18 @@ test("Mention beta from alpha comment", async () => {
|
|||
assertCommentFederation(betaRootComment, commentRes.comment_view);
|
||||
|
||||
let mentionsRes = await waitUntil(
|
||||
() => listInbox(beta, "CommentMention"),
|
||||
m => !!m.inbox[0],
|
||||
() => listNotifications(beta, "Mention"),
|
||||
m => !!m.notifications[0],
|
||||
);
|
||||
|
||||
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.comment.score).toBe(1);
|
||||
const firstMention = mentionsRes.notifications[0];
|
||||
let firstMentionData = firstMention.data as CommentView;
|
||||
expect(firstMentionData.comment!.content).toBeDefined();
|
||||
expect(firstMentionData.community!.local).toBe(true);
|
||||
expect(firstMentionData.creator.local).toBe(false);
|
||||
expect(firstMentionData.comment!.score).toBe(1);
|
||||
// the reply comment with mention should be the most fresh, newest, index 0
|
||||
expect(firstMention.person_comment_mention.comment_id).toBe(
|
||||
expect(firstMentionData.comment!.id).toBe(
|
||||
betaPostComments.comments[0].comment.id,
|
||||
);
|
||||
});
|
||||
|
@ -599,21 +600,24 @@ 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(
|
||||
() =>
|
||||
listInbox(beta, "CommentMention").then(m =>
|
||||
m.inbox.find(
|
||||
m =>
|
||||
m.type_ == "CommentMention" &&
|
||||
m.comment.ap_id === commentRes.comment_view.comment.ap_id,
|
||||
),
|
||||
listNotifications(beta, "Mention").then(m =>
|
||||
m.notifications.find(m => {
|
||||
let data = m.data as CommentView;
|
||||
return (
|
||||
m.notification.kind == "Mention" &&
|
||||
data.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);
|
||||
expect(relevantMention.creator.local).toBe(false);
|
||||
let relevantMentionData = relevantMention.data as CommentView;
|
||||
expect(relevantMentionData.comment!.content).toBe(commentContent);
|
||||
expect(relevantMentionData.community!.local).toBe(false);
|
||||
expect(relevantMentionData.creator.local).toBe(false);
|
||||
// TODO this is failing because fetchInReplyTos aren't getting score
|
||||
// expect(mentionsRes.mentions[0].score).toBe(1);
|
||||
});
|
||||
|
@ -839,8 +843,8 @@ test("Dont send a comment reply to a blocked community", async () => {
|
|||
unreadCount = await getUnreadCount(beta);
|
||||
expect(unreadCount.count).toBe(0);
|
||||
|
||||
let replies = await listInbox(beta, "CommentReply", true);
|
||||
expect(replies.inbox.length).toBe(0);
|
||||
let replies = await listNotifications(beta, "Reply", true);
|
||||
expect(replies.notifications.length).toBe(0);
|
||||
|
||||
// Unblock the community
|
||||
blockRes = await blockCommunity(beta, newCommunityId, false);
|
||||
|
|
|
@ -45,7 +45,6 @@ import {
|
|||
FollowMultiCommunity,
|
||||
GetPosts,
|
||||
LemmyError,
|
||||
MultiCommunity,
|
||||
MultiCommunityView,
|
||||
ReportCombinedView,
|
||||
ResolveCommunityReport,
|
||||
|
|
|
@ -38,7 +38,7 @@ import {
|
|||
createCommunity,
|
||||
listReports,
|
||||
getMyUser,
|
||||
listInbox,
|
||||
listNotifications,
|
||||
getModlog,
|
||||
getCommunity,
|
||||
} from "./shared";
|
||||
|
@ -48,7 +48,6 @@ import {
|
|||
AddModToCommunity,
|
||||
EditSite,
|
||||
EditPost,
|
||||
PersonPostMentionView,
|
||||
PostReport,
|
||||
PostReportView,
|
||||
ReportCombinedView,
|
||||
|
@ -942,16 +941,15 @@ test("Mention beta from alpha post body", async () => {
|
|||
await assertPostFederation(betaPost, postOnAlphaRes.post_view);
|
||||
|
||||
let mentionsRes = await waitUntil(
|
||||
() => listInbox(beta, "PostMention"),
|
||||
m => !!m.inbox[0],
|
||||
() => listNotifications(beta, "Mention"),
|
||||
m => !!m.notifications[0],
|
||||
);
|
||||
|
||||
const firstMention = mentionsRes.inbox[0] as PersonPostMentionView;
|
||||
expect(firstMention.post.body).toBeDefined();
|
||||
expect(firstMention.community.local).toBe(true);
|
||||
const firstMention = mentionsRes.notifications[0].data as PostView;
|
||||
expect(firstMention.post!.body).toBeDefined();
|
||||
expect(firstMention.community!.local).toBe(true);
|
||||
expect(firstMention.creator.local).toBe(false);
|
||||
expect(firstMention.post.score).toBe(1);
|
||||
expect(firstMention.person_post_mention.post_id).toBe(betaPost.post.id);
|
||||
expect(firstMention.post!.score).toBe(1);
|
||||
});
|
||||
|
||||
test("Rewrite markdown links", async () => {
|
||||
|
|
|
@ -4,14 +4,13 @@ import {
|
|||
alpha,
|
||||
beta,
|
||||
setupLogins,
|
||||
followBeta,
|
||||
createPrivateMessage,
|
||||
editPrivateMessage,
|
||||
deletePrivateMessage,
|
||||
waitUntil,
|
||||
reportPrivateMessage,
|
||||
unfollows,
|
||||
listInbox,
|
||||
listNotifications,
|
||||
resolvePerson,
|
||||
} from "./shared";
|
||||
|
||||
|
@ -37,10 +36,10 @@ test("Create a private message", async () => {
|
|||
expect(pmRes.private_message_view.recipient.local).toBe(false);
|
||||
|
||||
let betaPms = await waitUntil(
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
e => !!e.inbox[0],
|
||||
() => listNotifications(beta, "PrivateMessage"),
|
||||
e => !!e.notifications[0],
|
||||
);
|
||||
const firstPm = betaPms.inbox[0] as PrivateMessageView;
|
||||
const firstPm = betaPms.notifications[0].data as PrivateMessageView;
|
||||
expect(firstPm.private_message.content).toBeDefined();
|
||||
expect(firstPm.private_message.local).toBe(false);
|
||||
expect(firstPm.creator.local).toBe(false);
|
||||
|
@ -60,25 +59,24 @@ test("Update a private message", async () => {
|
|||
);
|
||||
|
||||
let betaPms = await waitUntil(
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
() => listNotifications(beta, "PrivateMessage"),
|
||||
p =>
|
||||
p.inbox[0].type_ == "PrivateMessage" &&
|
||||
p.inbox[0].private_message.content === updatedContent,
|
||||
);
|
||||
expect((betaPms.inbox[0] as PrivateMessageView).private_message.content).toBe(
|
||||
updatedContent,
|
||||
p.notifications[0].data.type_ == "PrivateMessage" &&
|
||||
p.notifications[0].data.private_message.content === updatedContent,
|
||||
);
|
||||
let pm = betaPms.notifications[0].data as PrivateMessageView;
|
||||
expect(pm.private_message.content).toBe(updatedContent);
|
||||
});
|
||||
|
||||
test("Delete a private message", async () => {
|
||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||
let betaPms1 = await waitUntil(
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
() => listNotifications(beta, "PrivateMessage"),
|
||||
m =>
|
||||
!!m.inbox.find(
|
||||
!!m.notifications.find(
|
||||
e =>
|
||||
e.type_ == "PrivateMessage" &&
|
||||
e.private_message.ap_id ===
|
||||
e.data.type_ == "PrivateMessage" &&
|
||||
e.data.private_message.ap_id ===
|
||||
pmRes.private_message_view.private_message.ap_id,
|
||||
),
|
||||
);
|
||||
|
@ -93,10 +91,10 @@ test("Delete a private message", async () => {
|
|||
// even though they are in the actual database.
|
||||
// no reason to show them
|
||||
let betaPms2 = await waitUntil(
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
p => p.inbox.length === betaPms1.inbox.length - 1,
|
||||
() => listNotifications(beta, "PrivateMessage"),
|
||||
p => p.notifications.length === betaPms1.notifications.length - 1,
|
||||
);
|
||||
expect(betaPms2.inbox.length).toBe(betaPms1.inbox.length - 1);
|
||||
expect(betaPms2.notifications.length).toBe(betaPms1.notifications.length - 1);
|
||||
|
||||
// Undelete
|
||||
let undeletedPmRes = await deletePrivateMessage(
|
||||
|
@ -109,25 +107,25 @@ test("Delete a private message", async () => {
|
|||
);
|
||||
|
||||
let betaPms3 = await waitUntil(
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
p => p.inbox.length === betaPms1.inbox.length,
|
||||
() => listNotifications(beta, "PrivateMessage"),
|
||||
p => p.notifications.length === betaPms1.notifications.length,
|
||||
);
|
||||
expect(betaPms3.inbox.length).toBe(betaPms1.inbox.length);
|
||||
expect(betaPms3.notifications.length).toBe(betaPms1.notifications.length);
|
||||
});
|
||||
|
||||
test("Create a private message report", async () => {
|
||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||
let betaPms1 = await waitUntil(
|
||||
() => listInbox(beta, "PrivateMessage"),
|
||||
() => listNotifications(beta, "PrivateMessage"),
|
||||
m =>
|
||||
!!m.inbox.find(
|
||||
!!m.notifications.find(
|
||||
e =>
|
||||
e.type_ == "PrivateMessage" &&
|
||||
e.private_message.ap_id ===
|
||||
e.data.type_ == "PrivateMessage" &&
|
||||
e.data.private_message.ap_id ===
|
||||
pmRes.private_message_view.private_message.ap_id,
|
||||
),
|
||||
);
|
||||
let betaPm = betaPms1.inbox[0] as PrivateMessageView;
|
||||
let betaPm = betaPms1.notifications[0].data as PrivateMessageView;
|
||||
expect(betaPm).toBeDefined();
|
||||
|
||||
// Make sure that only the recipient can report it, so this should fail
|
||||
|
|
|
@ -24,14 +24,14 @@ import {
|
|||
ListPersonContentResponse,
|
||||
ListPersonContent,
|
||||
PersonContentType,
|
||||
ListInboxResponse,
|
||||
ListInbox,
|
||||
InboxDataType,
|
||||
GetModlogResponse,
|
||||
GetModlog,
|
||||
CommunityView,
|
||||
CommentView,
|
||||
PersonView,
|
||||
ListNotifications,
|
||||
ListNotificationsResponse,
|
||||
} from "lemmy-js-client";
|
||||
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
||||
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
|
||||
|
@ -384,16 +384,16 @@ export async function getUnreadCount(
|
|||
return api.getUnreadCount();
|
||||
}
|
||||
|
||||
export async function listInbox(
|
||||
export async function listNotifications(
|
||||
api: LemmyHttp,
|
||||
type_?: InboxDataType,
|
||||
unread_only: boolean = false,
|
||||
): Promise<ListInboxResponse> {
|
||||
let form: ListInbox = {
|
||||
): Promise<ListNotificationsResponse> {
|
||||
let form: ListNotifications = {
|
||||
unread_only,
|
||||
type_,
|
||||
};
|
||||
return api.listInbox(form);
|
||||
return api.listNotifications(form);
|
||||
}
|
||||
|
||||
export async function resolveComment(
|
||||
|
|
|
@ -29,7 +29,7 @@ lemmy_db_views_vote = { workspace = true, features = ["full"] }
|
|||
lemmy_db_views_local_user = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_person = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_local_image = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_inbox_combined = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_notification = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_modlog_combined = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_person_saved_combined = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_person_liked_combined = { workspace = true, features = ["full"] }
|
||||
|
|
|
@ -69,8 +69,5 @@ pub async fn distinguish_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids: Vec::new(),
|
||||
}))
|
||||
Ok(Json(CommentResponse { comment_view }))
|
||||
}
|
||||
|
|
|
@ -8,10 +8,9 @@ use lemmy_api_utils::{
|
|||
utils::{check_bot_account, check_community_user_action, check_local_vote_mode},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{LocalUserId, PostOrCommentId},
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
comment::{CommentActions, CommentLikeForm},
|
||||
comment_reply::CommentReply,
|
||||
person::PersonActions,
|
||||
},
|
||||
traits::Likeable,
|
||||
|
@ -35,8 +34,6 @@ pub async fn like_comment(
|
|||
let comment_id = data.comment_id;
|
||||
let my_person_id = local_user_view.person.id;
|
||||
|
||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
||||
|
||||
check_local_vote_mode(
|
||||
data.score,
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
|
@ -63,16 +60,6 @@ pub async fn like_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
// Add parent poster or commenter to recipients
|
||||
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
|
||||
if let Ok(Some(reply)) = comment_reply {
|
||||
let recipient_id = reply.recipient_id;
|
||||
if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
|
||||
{
|
||||
recipient_ids.push(local_recipient.local_user.id);
|
||||
}
|
||||
}
|
||||
|
||||
let mut like_form = CommentLikeForm::new(my_person_id, data.comment_id, data.score);
|
||||
|
||||
// Remove any likes first
|
||||
|
@ -119,7 +106,6 @@ pub async fn like_comment(
|
|||
context.deref(),
|
||||
comment_id,
|
||||
Some(local_user_view),
|
||||
recipient_ids,
|
||||
local_instance_id,
|
||||
)
|
||||
.await?,
|
||||
|
|
|
@ -34,8 +34,5 @@ pub async fn save_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids: Vec::new(),
|
||||
}))
|
||||
Ok(Json(CommentResponse { comment_view }))
|
||||
}
|
||||
|
|
|
@ -7,3 +7,4 @@ pub mod pending_follows;
|
|||
pub mod random;
|
||||
pub mod tag;
|
||||
pub mod transfer;
|
||||
pub mod update_notifications;
|
||||
|
|
23
crates/api/api/src/community/update_notifications.rs
Normal file
23
crates/api/api/src/community/update_notifications.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::source::community::CommunityActions;
|
||||
use lemmy_db_views_community::api::UpdateCommunityNotifications;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn update_community_notifications(
|
||||
data: Json<UpdateCommunityNotifications>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
CommunityActions::update_notification_state(
|
||||
data.community_id,
|
||||
local_user_view.person.id,
|
||||
data.mode,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -13,7 +13,6 @@ pub mod comment;
|
|||
pub mod community;
|
||||
pub mod local_user;
|
||||
pub mod post;
|
||||
pub mod private_message;
|
||||
pub mod reports;
|
||||
pub mod site;
|
||||
pub mod sitemap;
|
||||
|
|
|
@ -3,8 +3,8 @@ use actix_web::web::Json;
|
|||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::source::local_user::LocalUser;
|
||||
use lemmy_db_views_community_moderator::CommunityModeratorView;
|
||||
use lemmy_db_views_inbox_combined::{impls::InboxCombinedQuery, InboxCombinedView};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_notification::{impls::NotificationQuery, NotificationData};
|
||||
use lemmy_db_views_person_content_combined::{
|
||||
impls::PersonContentCombinedQuery,
|
||||
PersonContentCombinedView,
|
||||
|
@ -46,18 +46,18 @@ pub async fn export_data(
|
|||
})
|
||||
.collect();
|
||||
|
||||
let inbox = InboxCombinedQuery {
|
||||
let notifications = NotificationQuery {
|
||||
no_limit: Some(true),
|
||||
..InboxCombinedQuery::default()
|
||||
show_bot_accounts: Some(local_user_view.local_user.show_bot_accounts),
|
||||
..NotificationQuery::default()
|
||||
}
|
||||
.list(pool, my_person_id, local_instance_id)
|
||||
.list(pool, &local_user_view.person)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|u| match u {
|
||||
InboxCombinedView::CommentReply(cr) => Comment(cr.comment),
|
||||
InboxCombinedView::CommentMention(cm) => Comment(cm.comment),
|
||||
InboxCombinedView::PostMention(pm) => Post(pm.post),
|
||||
InboxCombinedView::PrivateMessage(pm) => PrivateMessage(pm.private_message),
|
||||
.map(|u| match u.data {
|
||||
NotificationData::Post(p) => Post(p.post),
|
||||
NotificationData::Comment(c) => Comment(c.comment),
|
||||
NotificationData::PrivateMessage(pm) => PrivateMessage(pm.private_message),
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -93,7 +93,7 @@ pub async fn export_data(
|
|||
let settings = user_backup_list_to_user_settings_backup(local_user_view, lists);
|
||||
|
||||
Ok(Json(ExportDataResponse {
|
||||
inbox,
|
||||
notifications,
|
||||
content,
|
||||
liked,
|
||||
read_posts,
|
||||
|
|
46
crates/api/api/src/local_user/notifications/list.rs
Normal file
46
crates/api/api/src/local_user/notifications/list.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::traits::PaginationCursorBuilder;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_notification::{
|
||||
impls::NotificationQuery,
|
||||
ListNotifications,
|
||||
ListNotificationsResponse,
|
||||
NotificationView,
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn list_notifications(
|
||||
data: Query<ListNotifications>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<ListNotificationsResponse>> {
|
||||
let cursor_data = if let Some(cursor) = &data.page_cursor {
|
||||
Some(NotificationView::from_cursor(cursor, &mut context.pool()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let notifications = NotificationQuery {
|
||||
type_: data.type_,
|
||||
unread_only: data.unread_only,
|
||||
show_bot_accounts: Some(local_user_view.local_user.show_bot_accounts),
|
||||
cursor_data,
|
||||
page_back: data.page_back,
|
||||
limit: data.limit,
|
||||
no_limit: None,
|
||||
}
|
||||
.list(&mut context.pool(), &local_user_view.person)
|
||||
.await?;
|
||||
|
||||
let next_page = notifications.last().map(PaginationCursorBuilder::to_cursor);
|
||||
let prev_page = notifications
|
||||
.first()
|
||||
.map(PaginationCursorBuilder::to_cursor);
|
||||
|
||||
Ok(Json(ListNotificationsResponse {
|
||||
notifications,
|
||||
next_page,
|
||||
prev_page,
|
||||
}))
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
use actix_web::web::{Data, Json, Query};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::traits::PaginationCursorBuilder;
|
||||
use lemmy_db_views_inbox_combined::{
|
||||
impls::InboxCombinedQuery,
|
||||
InboxCombinedView,
|
||||
ListInbox,
|
||||
ListInboxResponse,
|
||||
};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn list_inbox(
|
||||
data: Query<ListInbox>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<ListInboxResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
let local_instance_id = local_user_view.person.instance_id;
|
||||
|
||||
let cursor_data = if let Some(cursor) = &data.page_cursor {
|
||||
Some(InboxCombinedView::from_cursor(cursor, &mut context.pool()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let inbox = InboxCombinedQuery {
|
||||
type_: data.type_,
|
||||
unread_only: data.unread_only,
|
||||
show_bot_accounts: Some(local_user_view.local_user.show_bot_accounts),
|
||||
cursor_data,
|
||||
page_back: data.page_back,
|
||||
limit: data.limit,
|
||||
no_limit: None,
|
||||
}
|
||||
.list(&mut context.pool(), person_id, local_instance_id)
|
||||
.await?;
|
||||
|
||||
let next_page = inbox.last().map(PaginationCursorBuilder::to_cursor);
|
||||
let prev_page = inbox.first().map(PaginationCursorBuilder::to_cursor);
|
||||
|
||||
Ok(Json(ListInboxResponse {
|
||||
inbox,
|
||||
next_page,
|
||||
prev_page,
|
||||
}))
|
||||
}
|
|
@ -1,11 +1,6 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::source::{
|
||||
comment_reply::CommentReply,
|
||||
person_comment_mention::PersonCommentMention,
|
||||
person_post_mention::PersonPostMention,
|
||||
private_message::PrivateMessage,
|
||||
};
|
||||
use lemmy_db_schema::source::notification::Notification;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -14,19 +9,7 @@ pub async fn mark_all_notifications_read(
|
|||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
|
||||
// Mark all comment_replies as read
|
||||
CommentReply::mark_all_as_read(&mut context.pool(), person_id).await?;
|
||||
|
||||
// Mark all comment mentions as read
|
||||
PersonCommentMention::mark_all_as_read(&mut context.pool(), person_id).await?;
|
||||
|
||||
// Mark all post mentions as read
|
||||
PersonPostMention::mark_all_as_read(&mut context.pool(), person_id).await?;
|
||||
|
||||
// Mark all private_messages as read
|
||||
PrivateMessage::mark_all_as_read(&mut context.pool(), person_id).await?;
|
||||
Notification::mark_all_as_read(&mut context.pool(), local_user_view.person.id).await?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
source::person_comment_mention::{PersonCommentMention, PersonCommentMentionUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views_inbox_combined::api::MarkPersonCommentMentionAsRead;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
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?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::source::notification::Notification;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_notification::api::MarkNotificationAsRead;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn mark_notification_as_read(
|
||||
data: Json<MarkNotificationAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
Notification::mark_read_by_id_and_person(
|
||||
&mut context.pool(),
|
||||
data.notification_id,
|
||||
local_user_view.person.id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
source::person_post_mention::{PersonPostMention, PersonPostMentionUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views_inbox_combined::api::MarkPersonPostMentionAsRead;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
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?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
source::comment_reply::{CommentReply, CommentReplyUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views_inbox_combined::api::MarkCommentReplyAsRead;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
pub async fn mark_reply_as_read(
|
||||
data: Json<MarkCommentReplyAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
let comment_reply_id = data.comment_reply_id;
|
||||
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;
|
||||
|
||||
if local_user_view.person.id != read_comment_reply.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdateComment)?
|
||||
}
|
||||
|
||||
let comment_reply_id = read_comment_reply.id;
|
||||
let read = Some(data.read);
|
||||
|
||||
CommentReply::update(
|
||||
&mut context.pool(),
|
||||
comment_reply_id,
|
||||
&CommentReplyUpdateForm { read },
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
pub mod list_inbox;
|
||||
pub mod list;
|
||||
pub mod mark_all_read;
|
||||
pub mod mark_comment_mention_read;
|
||||
pub mod mark_post_mention_read;
|
||||
pub mod mark_reply_read;
|
||||
pub mod mark_notification_read;
|
||||
pub mod unread_count;
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_views_inbox_combined::{api::GetUnreadCountResponse, InboxCombinedViewInternal};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_notification::{api::GetUnreadCountResponse, NotificationView};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn unread_count(
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<GetUnreadCountResponse>> {
|
||||
let person_id = local_user_view.person.id;
|
||||
let local_instance_id = local_user_view.person.instance_id;
|
||||
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||
|
||||
let count = InboxCombinedViewInternal::get_unread_count(
|
||||
let count = NotificationView::get_unread_count(
|
||||
&mut context.pool(),
|
||||
person_id,
|
||||
local_instance_id,
|
||||
&local_user_view.person,
|
||||
show_bot_accounts,
|
||||
)
|
||||
.await?;
|
||||
|
|
|
@ -7,3 +7,4 @@ pub mod lock;
|
|||
pub mod mark_many_read;
|
||||
pub mod mark_read;
|
||||
pub mod save;
|
||||
pub mod update_notifications;
|
||||
|
|
23
crates/api/api/src/post/update_notifications.rs
Normal file
23
crates/api/api/src/post/update_notifications.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::source::post::PostActions;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_post::api::UpdatePostNotifications;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn update_post_notifications(
|
||||
data: Json<UpdatePostNotifications>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<SuccessResponse>> {
|
||||
PostActions::update_notification_state(
|
||||
data.post_id,
|
||||
local_user_view.person.id,
|
||||
data.mode,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use lemmy_api_utils::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
source::private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_db_views_inbox_combined::api::MarkPrivateMessageAsRead;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_site::api::SuccessResponse;
|
||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||
|
||||
pub async fn mark_pm_as_read(
|
||||
data: Json<MarkPrivateMessageAsRead>,
|
||||
context: Data<LemmyContext>,
|
||||
local_user_view: LocalUserView,
|
||||
) -> 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?;
|
||||
if local_user_view.person.id != orig_private_message.recipient_id {
|
||||
Err(LemmyErrorType::CouldntUpdatePrivateMessage)?
|
||||
}
|
||||
|
||||
// Doing the update
|
||||
let private_message_id = data.private_message_id;
|
||||
let read = data.read;
|
||||
PrivateMessage::update(
|
||||
&mut context.pool(),
|
||||
private_message_id,
|
||||
&PrivateMessageUpdateForm {
|
||||
read: Some(read),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(SuccessResponse::default()))
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
pub mod mark_read;
|
|
@ -20,8 +20,8 @@ use lemmy_db_schema::{
|
|||
utils::DbPool,
|
||||
};
|
||||
use lemmy_db_schema_file::enums::RegistrationMode;
|
||||
use lemmy_db_views_inbox_combined::api::GetUnreadRegistrationApplicationCountResponse;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_notification::api::GetUnreadRegistrationApplicationCountResponse;
|
||||
use lemmy_db_views_registration_applications::api::{
|
||||
ApproveRegistrationApplication,
|
||||
ListRegistrationApplicationsResponse,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::{context::LemmyContext, utils::is_admin};
|
||||
use lemmy_db_views_inbox_combined::api::GetUnreadRegistrationApplicationCountResponse;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_notification::api::GetUnreadRegistrationApplicationCountResponse;
|
||||
use lemmy_db_views_registration_applications::RegistrationApplicationView;
|
||||
use lemmy_db_views_site::SiteView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
|
|
@ -27,7 +27,7 @@ ts-rs = [
|
|||
"lemmy_db_views_community_follower/ts-rs",
|
||||
"lemmy_db_views_community_moderator/ts-rs",
|
||||
"lemmy_db_views_custom_emoji/ts-rs",
|
||||
"lemmy_db_views_inbox_combined/ts-rs",
|
||||
"lemmy_db_views_notification/ts-rs",
|
||||
"lemmy_db_views_local_image/ts-rs",
|
||||
"lemmy_db_views_local_user/ts-rs",
|
||||
"lemmy_db_views_modlog_combined/ts-rs",
|
||||
|
@ -54,7 +54,7 @@ lemmy_db_views_community.workspace = true
|
|||
lemmy_db_views_community_follower.workspace = true
|
||||
lemmy_db_views_community_moderator.workspace = true
|
||||
lemmy_db_views_custom_emoji.workspace = true
|
||||
lemmy_db_views_inbox_combined.workspace = true
|
||||
lemmy_db_views_notification.workspace = true
|
||||
lemmy_db_views_local_image.workspace = true
|
||||
lemmy_db_views_local_user.workspace = true
|
||||
lemmy_db_views_modlog_combined.workspace = true
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
pub use lemmy_db_schema::{
|
||||
newtypes::{CommentReplyId, PersonCommentMentionId, PersonPostMentionId},
|
||||
source::{
|
||||
comment_reply::CommentReply,
|
||||
person_comment_mention::PersonCommentMention,
|
||||
person_post_mention::PersonPostMention,
|
||||
},
|
||||
InboxDataType,
|
||||
};
|
||||
pub use lemmy_db_views_inbox_combined::{
|
||||
api::{
|
||||
GetUnreadCountResponse,
|
||||
MarkCommentReplyAsRead,
|
||||
MarkPersonCommentMentionAsRead,
|
||||
MarkPersonPostMentionAsRead,
|
||||
MarkPrivateMessageAsRead,
|
||||
},
|
||||
CommentReplyView,
|
||||
InboxCombinedView,
|
||||
ListInbox,
|
||||
ListInboxResponse,
|
||||
PersonCommentMentionView,
|
||||
PersonPostMentionView,
|
||||
};
|
|
@ -4,10 +4,10 @@ pub mod community;
|
|||
pub mod custom_emoji;
|
||||
pub mod error;
|
||||
pub mod federation;
|
||||
pub mod inbox;
|
||||
pub mod language;
|
||||
pub mod media;
|
||||
pub mod modlog;
|
||||
pub mod notification;
|
||||
pub mod oauth;
|
||||
pub mod person;
|
||||
pub mod plugin;
|
||||
|
|
11
crates/api/api_common/src/notification.rs
Normal file
11
crates/api/api_common/src/notification.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
pub use lemmy_db_schema::{
|
||||
newtypes::NotificationId,
|
||||
source::notification::Notification,
|
||||
NotificationDataType,
|
||||
};
|
||||
pub use lemmy_db_views_notification::{
|
||||
api::{GetUnreadCountResponse, MarkNotificationAsRead, MarkPrivateMessageAsRead},
|
||||
ListNotifications,
|
||||
ListNotificationsResponse,
|
||||
NotificationView,
|
||||
};
|
|
@ -14,8 +14,8 @@ pub use lemmy_db_views_site::{
|
|||
};
|
||||
|
||||
pub mod administration {
|
||||
pub use lemmy_db_views_inbox_combined::api::GetUnreadRegistrationApplicationCountResponse;
|
||||
pub use lemmy_db_views_local_user::api::{AdminListUsers, AdminListUsersResponse};
|
||||
pub use lemmy_db_views_notification::api::GetUnreadRegistrationApplicationCountResponse;
|
||||
pub use lemmy_db_views_person::api::{AddAdmin, AddAdminResponse};
|
||||
pub use lemmy_db_views_registration_applications::api::{
|
||||
ApproveRegistrationApplication,
|
||||
|
|
|
@ -2,8 +2,9 @@ use crate::community_use_pending;
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::{
|
||||
build_response::{build_comment_response, send_local_notifs},
|
||||
build_response::build_comment_response,
|
||||
context::LemmyContext,
|
||||
notify::NotifyData,
|
||||
plugins::{plugin_hook_after, plugin_hook_before},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{
|
||||
|
@ -19,11 +20,9 @@ use lemmy_api_utils::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
comment::{Comment, CommentActions, CommentInsertForm, CommentLikeForm},
|
||||
comment_reply::{CommentReply, CommentReplyUpdateForm},
|
||||
person_comment_mention::{PersonCommentMention, PersonCommentMentionUpdateForm},
|
||||
notification::Notification,
|
||||
},
|
||||
traits::{Crud, Likeable},
|
||||
};
|
||||
|
@ -33,7 +32,7 @@ use lemmy_db_views_post::PostView;
|
|||
use lemmy_db_views_site::SiteView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
|
||||
utils::validation::is_valid_body_field,
|
||||
};
|
||||
|
||||
pub async fn create_comment(
|
||||
|
@ -113,20 +112,14 @@ pub async fn create_comment(
|
|||
Comment::create(&mut context.pool(), &comment_form, parent_path.as_ref()).await?;
|
||||
plugin_hook_after("after_create_local_comment", &inserted_comment)?;
|
||||
|
||||
let inserted_comment_id = inserted_comment.id;
|
||||
|
||||
// Scan the comment for user mentions, add those rows
|
||||
let mentions = scrape_text_for_mentions(&content);
|
||||
let do_send_email = !local_site.disable_email_notifications;
|
||||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
PostOrCommentId::Comment(inserted_comment_id),
|
||||
NotifyData::new(
|
||||
&post,
|
||||
Some(&inserted_comment),
|
||||
&local_user_view.person,
|
||||
do_send_email,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
local_instance_id,
|
||||
&post_view.community,
|
||||
!local_site.disable_email_notifications,
|
||||
)
|
||||
.send(&context)
|
||||
.await?;
|
||||
|
||||
// You like your own comment by default
|
||||
|
@ -153,30 +146,10 @@ pub async fn create_comment(
|
|||
// then mark the parent as read.
|
||||
// Then we don't have to do it manually after we respond to a comment.
|
||||
if let Some(parent) = parent_opt {
|
||||
let person_id = local_user_view.person.id;
|
||||
let parent_id = parent.id;
|
||||
let comment_reply =
|
||||
CommentReply::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await;
|
||||
if let Ok(Some(reply)) = comment_reply {
|
||||
CommentReply::update(
|
||||
&mut context.pool(),
|
||||
reply.id,
|
||||
&CommentReplyUpdateForm { read: Some(true) },
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 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,
|
||||
&PersonCommentMentionUpdateForm { read: Some(true) },
|
||||
)
|
||||
.await?;
|
||||
let notif = Notification::read_by_comment_id(&mut context.pool(), parent.id).await;
|
||||
if let Ok(notif) = notif {
|
||||
let person_id = local_user_view.person.id;
|
||||
Notification::mark_read_by_id_and_person(&mut context.pool(), notif.id, person_id).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,7 +158,6 @@ pub async fn create_comment(
|
|||
&context,
|
||||
inserted_comment.id,
|
||||
Some(local_user_view),
|
||||
recipient_ids,
|
||||
local_instance_id,
|
||||
)
|
||||
.await?,
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::{
|
||||
build_response::{build_comment_response, send_local_notifs},
|
||||
build_response::build_comment_response,
|
||||
context::LemmyContext,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::check_community_user_action,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PostOrCommentId,
|
||||
source::comment::{Comment, CommentUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
|
@ -62,16 +61,6 @@ pub async fn delete_comment(
|
|||
)
|
||||
.await?;
|
||||
|
||||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
local_instance_id,
|
||||
)
|
||||
.await?;
|
||||
let updated_comment_id = updated_comment.id;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
|
@ -88,7 +77,6 @@ pub async fn delete_comment(
|
|||
&context,
|
||||
updated_comment_id,
|
||||
Some(local_user_view),
|
||||
recipient_ids,
|
||||
local_instance_id,
|
||||
)
|
||||
.await?,
|
||||
|
|
|
@ -21,13 +21,6 @@ pub async fn get_comment(
|
|||
check_private_instance(&local_user_view, &local_site)?;
|
||||
|
||||
Ok(Json(
|
||||
build_comment_response(
|
||||
&context,
|
||||
data.id,
|
||||
local_user_view,
|
||||
vec![],
|
||||
local_instance_id,
|
||||
)
|
||||
.await?,
|
||||
build_comment_response(&context, data.id, local_user_view, local_instance_id).await?,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::{
|
||||
build_response::{build_comment_response, send_local_notifs},
|
||||
build_response::build_comment_response,
|
||||
context::LemmyContext,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::check_community_mod_action,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
comment_report::CommentReport,
|
||||
|
@ -84,16 +83,6 @@ pub async fn remove_comment(
|
|||
};
|
||||
ModRemoveComment::create(&mut context.pool(), &form).await?;
|
||||
|
||||
let recipient_ids = send_local_notifs(
|
||||
vec![],
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
&local_user_view.person,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
local_instance_id,
|
||||
)
|
||||
.await?;
|
||||
let updated_comment_id = updated_comment.id;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
|
@ -111,7 +100,6 @@ pub async fn remove_comment(
|
|||
&context,
|
||||
updated_comment_id,
|
||||
Some(local_user_view),
|
||||
recipient_ids,
|
||||
local_instance_id,
|
||||
)
|
||||
.await?,
|
||||
|
|
|
@ -2,15 +2,15 @@ use activitypub_federation::config::Data;
|
|||
use actix_web::web::Json;
|
||||
use chrono::Utc;
|
||||
use lemmy_api_utils::{
|
||||
build_response::{build_comment_response, send_local_notifs},
|
||||
build_response::build_comment_response,
|
||||
context::LemmyContext,
|
||||
notify::NotifyData,
|
||||
plugins::{plugin_hook_after, plugin_hook_before},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{check_community_user_action, get_url_blocklist, process_markdown_opt, slur_regex},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::comment::{Comment, CommentUpdateForm},
|
||||
traits::Crud,
|
||||
};
|
||||
|
@ -21,7 +21,7 @@ use lemmy_db_views_comment::{
|
|||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
utils::{mention::scrape_text_for_mentions, validation::is_valid_body_field},
|
||||
utils::validation::is_valid_body_field,
|
||||
};
|
||||
|
||||
pub async fn update_comment(
|
||||
|
@ -79,17 +79,14 @@ pub async fn update_comment(
|
|||
plugin_hook_after("after_update_local_comment", &updated_comment)?;
|
||||
|
||||
// Do the mentions / recipients
|
||||
let updated_comment_content = updated_comment.content.clone();
|
||||
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
||||
let recipient_ids = send_local_notifs(
|
||||
mentions,
|
||||
PostOrCommentId::Comment(comment_id),
|
||||
NotifyData::new(
|
||||
&orig_comment.post,
|
||||
Some(&updated_comment),
|
||||
&local_user_view.person,
|
||||
&orig_comment.community,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
local_instance_id,
|
||||
)
|
||||
.send(&context)
|
||||
.await?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
|
@ -102,7 +99,6 @@ pub async fn update_comment(
|
|||
&context,
|
||||
updated_comment.id,
|
||||
Some(local_user_view),
|
||||
recipient_ids,
|
||||
local_instance_id,
|
||||
)
|
||||
.await?,
|
||||
|
|
|
@ -3,8 +3,9 @@ use crate::community_use_pending;
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::{
|
||||
build_response::{build_post_response, send_local_notifs},
|
||||
build_response::build_post_response,
|
||||
context::LemmyContext,
|
||||
notify::NotifyData,
|
||||
plugins::{plugin_hook_after, plugin_hook_before},
|
||||
request::generate_post_link_metadata,
|
||||
send_activity::SendActivityData,
|
||||
|
@ -21,7 +22,6 @@ use lemmy_api_utils::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::post::{Post, PostActions, PostInsertForm, PostLikeForm, PostReadForm},
|
||||
traits::{Crud, Likeable},
|
||||
utils::diesel_url_create,
|
||||
|
@ -34,7 +34,6 @@ use lemmy_db_views_site::SiteView;
|
|||
use lemmy_utils::{
|
||||
error::LemmyResult,
|
||||
utils::{
|
||||
mention::scrape_text_for_mentions,
|
||||
slurs::check_slurs,
|
||||
validation::{
|
||||
is_url_blocked,
|
||||
|
@ -169,23 +168,18 @@ pub async fn create_post(
|
|||
// They like their own post by default
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_id = inserted_post.id;
|
||||
let local_instance_id = local_user_view.person.instance_id;
|
||||
let like_form = PostLikeForm::new(post_id, person_id, 1);
|
||||
|
||||
PostActions::like(&mut context.pool(), &like_form).await?;
|
||||
|
||||
// Scan the post body for user mentions, add those rows
|
||||
let mentions = scrape_text_for_mentions(&inserted_post.body.clone().unwrap_or_default());
|
||||
let do_send_email = !local_site.disable_email_notifications;
|
||||
send_local_notifs(
|
||||
mentions,
|
||||
PostOrCommentId::Post(inserted_post.id),
|
||||
NotifyData::new(
|
||||
&inserted_post,
|
||||
None,
|
||||
&local_user_view.person,
|
||||
do_send_email,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
local_instance_id,
|
||||
community,
|
||||
!local_site.disable_email_notifications,
|
||||
)
|
||||
.send(&context)
|
||||
.await?;
|
||||
|
||||
let read_form = PostReadForm::new(post_id, person_id);
|
||||
|
|
|
@ -3,8 +3,9 @@ use activitypub_federation::config::Data;
|
|||
use actix_web::web::Json;
|
||||
use chrono::Utc;
|
||||
use lemmy_api_utils::{
|
||||
build_response::{build_post_response, send_local_notifs},
|
||||
build_response::build_post_response,
|
||||
context::LemmyContext,
|
||||
notify::NotifyData,
|
||||
plugins::{plugin_hook_after, plugin_hook_before},
|
||||
request::generate_post_link_metadata,
|
||||
send_activity::SendActivityData,
|
||||
|
@ -20,7 +21,6 @@ use lemmy_api_utils::{
|
|||
};
|
||||
use lemmy_db_schema::{
|
||||
impls::actor_language::validate_post_language,
|
||||
newtypes::PostOrCommentId,
|
||||
source::{
|
||||
community::Community,
|
||||
post::{Post, PostUpdateForm},
|
||||
|
@ -38,7 +38,6 @@ use lemmy_db_views_site::SiteView;
|
|||
use lemmy_utils::{
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
utils::{
|
||||
mention::scrape_text_for_mentions,
|
||||
slurs::check_slurs,
|
||||
validation::{
|
||||
is_url_blocked,
|
||||
|
@ -163,17 +162,14 @@ pub async fn update_post(
|
|||
let updated_post = Post::update(&mut context.pool(), post_id, &post_form).await?;
|
||||
plugin_hook_after("after_update_local_post", &post_form)?;
|
||||
|
||||
// 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),
|
||||
NotifyData::new(
|
||||
&updated_post,
|
||||
None,
|
||||
&local_user_view.person,
|
||||
&orig_post.community,
|
||||
false,
|
||||
&context,
|
||||
Some(&local_user_view),
|
||||
local_instance_id,
|
||||
)
|
||||
.send(&context)
|
||||
.await?;
|
||||
|
||||
// send out federation/webmention if necessary
|
||||
|
|
|
@ -2,6 +2,7 @@ use activitypub_federation::config::Data;
|
|||
use actix_web::web::Json;
|
||||
use lemmy_api_utils::{
|
||||
context::LemmyContext,
|
||||
notify::notify_private_message,
|
||||
plugins::{plugin_hook_after, plugin_hook_before},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{check_private_messages_enabled, get_url_blocklist, process_markdown, slur_regex},
|
||||
|
@ -18,7 +19,6 @@ use lemmy_db_views_private_message::{
|
|||
api::{CreatePrivateMessage, PrivateMessageResponse},
|
||||
PrivateMessageView,
|
||||
};
|
||||
use lemmy_email::notifications::send_private_message_email;
|
||||
use lemmy_utils::{error::LemmyResult, utils::validation::is_valid_body_field};
|
||||
|
||||
pub async fn create_private_message(
|
||||
|
@ -63,18 +63,7 @@ pub async fn create_private_message(
|
|||
|
||||
let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?;
|
||||
|
||||
// Send email to the local recipient, if one exists
|
||||
if view.recipient.local {
|
||||
let local_recipient =
|
||||
LocalUserView::read_person(&mut context.pool(), data.recipient_id).await?;
|
||||
send_private_message_email(
|
||||
&local_user_view,
|
||||
&local_recipient,
|
||||
&content,
|
||||
context.settings(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
notify_private_message(&view, true, &context).await?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
SendActivityData::CreatePrivateMessage(view.clone()),
|
||||
|
|
|
@ -3,6 +3,7 @@ use actix_web::web::Json;
|
|||
use chrono::Utc;
|
||||
use lemmy_api_utils::{
|
||||
context::LemmyContext,
|
||||
notify::notify_private_message,
|
||||
plugins::{plugin_hook_after, plugin_hook_before},
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{get_url_blocklist, process_markdown, slur_regex},
|
||||
|
@ -52,6 +53,8 @@ pub async fn update_private_message(
|
|||
|
||||
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
|
||||
|
||||
notify_private_message(&view, false, &context).await?;
|
||||
|
||||
ActivityChannel::submit_activity(
|
||||
SendActivityData::UpdatePrivateMessage(view.clone()),
|
||||
&context,
|
||||
|
|
|
@ -77,8 +77,10 @@ webpage = { version = "2.0", default-features = false, features = ["serde"] }
|
|||
regex = { workspace = true }
|
||||
jsonwebtoken = { version = "9.3.1" }
|
||||
either.workspace = true
|
||||
derive-new.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
lemmy_db_views_notification = { workspace = true, features = ["full"] }
|
||||
diesel_ltree = { workspace = true }
|
||||
|
|
|
@ -1,38 +1,19 @@
|
|||
use crate::{
|
||||
context::LemmyContext,
|
||||
utils::{check_person_instance_community_block, is_mod_or_admin},
|
||||
};
|
||||
use crate::{context::LemmyContext, utils::is_mod_or_admin};
|
||||
use actix_web::web::Json;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommentId, CommunityId, InstanceId, LocalUserId, PostId, PostOrCommentId},
|
||||
source::{
|
||||
actor_language::CommunityLanguage,
|
||||
comment::Comment,
|
||||
comment_reply::{CommentReply, CommentReplyInsertForm},
|
||||
community::Community,
|
||||
person::Person,
|
||||
person_comment_mention::{PersonCommentMention, PersonCommentMentionInsertForm},
|
||||
person_post_mention::{PersonPostMention, PersonPostMentionInsertForm},
|
||||
post::Post,
|
||||
},
|
||||
traits::Crud,
|
||||
newtypes::{CommentId, CommunityId, InstanceId, PostId},
|
||||
source::actor_language::CommunityLanguage,
|
||||
};
|
||||
use lemmy_db_views_comment::{api::CommentResponse, CommentView};
|
||||
use lemmy_db_views_community::{api::CommunityResponse, CommunityView};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_post::{api::PostResponse, PostView};
|
||||
use lemmy_email::notifications::{
|
||||
send_comment_reply_email,
|
||||
send_mention_email,
|
||||
send_post_reply_email,
|
||||
};
|
||||
use lemmy_utils::{error::LemmyResult, utils::mention::MentionData};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
pub async fn build_comment_response(
|
||||
context: &LemmyContext,
|
||||
comment_id: CommentId,
|
||||
local_user_view: Option<LocalUserView>,
|
||||
recipient_ids: Vec<LocalUserId>,
|
||||
local_instance_id: InstanceId,
|
||||
) -> LemmyResult<CommentResponse> {
|
||||
let local_user = local_user_view.map(|l| l.local_user);
|
||||
|
@ -43,10 +24,7 @@ pub async fn build_comment_response(
|
|||
local_instance_id,
|
||||
)
|
||||
.await?;
|
||||
Ok(CommentResponse {
|
||||
comment_view,
|
||||
recipient_ids,
|
||||
})
|
||||
Ok(CommentResponse { comment_view })
|
||||
}
|
||||
|
||||
pub async fn build_community_response(
|
||||
|
@ -93,221 +71,3 @@ pub async fn build_post_response(
|
|||
.await?;
|
||||
Ok(Json(PostResponse { post_view }))
|
||||
}
|
||||
|
||||
// TODO: this function is a mess and should be split up to handle email separately
|
||||
pub async fn send_local_notifs(
|
||||
mentions: Vec<MentionData>,
|
||||
post_or_comment_id: PostOrCommentId,
|
||||
person: &Person,
|
||||
do_send_email: bool,
|
||||
context: &LemmyContext,
|
||||
local_user_view: Option<&LocalUserView>,
|
||||
local_instance_id: InstanceId,
|
||||
) -> LemmyResult<Vec<LocalUserId>> {
|
||||
let mut recipient_ids = Vec::new();
|
||||
|
||||
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),
|
||||
local_instance_id,
|
||||
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),
|
||||
local_instance_id,
|
||||
)
|
||||
.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
|
||||
for mention in mentions
|
||||
.iter()
|
||||
.filter(|m| m.is_local(&context.settings().hostname) && m.name.ne(&person.name))
|
||||
{
|
||||
let mention_name = mention.name.clone();
|
||||
let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await;
|
||||
if let Ok(mention_user_view) = user_view {
|
||||
// TODO
|
||||
// At some point, make it so you can't tag the parent creator either
|
||||
// Potential duplication of notifications, one for reply and the other for mention, is handled
|
||||
// below by checking recipient ids
|
||||
recipient_ids.push(mention_user_view.local_user.id);
|
||||
|
||||
// Make the correct reply form depending on whether its a post or comment mention
|
||||
let (link, 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
|
||||
PersonCommentMention::create(&mut context.pool(), &person_comment_mention_form)
|
||||
.await
|
||||
.ok();
|
||||
(
|
||||
comment.local_url(context.settings())?,
|
||||
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.local_url(context.settings())?,
|
||||
post.body.clone().unwrap_or_default(),
|
||||
)
|
||||
};
|
||||
|
||||
// Send an email to those local users that have notifications on
|
||||
if do_send_email {
|
||||
send_mention_email(
|
||||
&mention_user_view,
|
||||
&comment_content_or_post_body,
|
||||
person,
|
||||
link.into(),
|
||||
context.settings(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send comment_reply to the parent commenter / poster
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
if do_send_email {
|
||||
send_comment_reply_email(
|
||||
&parent_user_view,
|
||||
comment,
|
||||
person,
|
||||
&parent_comment,
|
||||
&post,
|
||||
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();
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
// 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 {
|
||||
send_post_reply_email(
|
||||
&parent_user_view,
|
||||
comment,
|
||||
person,
|
||||
&post,
|
||||
context.settings(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(recipient_ids)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub mod build_response;
|
||||
pub mod claims;
|
||||
pub mod context;
|
||||
pub mod notify;
|
||||
pub mod plugins;
|
||||
pub mod request;
|
||||
pub mod send_activity;
|
||||
|
|
765
crates/api/api_utils/src/notify.rs
Normal file
765
crates/api/api_utils/src/notify.rs
Normal file
|
@ -0,0 +1,765 @@
|
|||
use crate::context::LemmyContext;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
source::{
|
||||
comment::Comment,
|
||||
community::{Community, CommunityActions},
|
||||
instance::InstanceActions,
|
||||
notification::{Notification, NotificationInsertForm},
|
||||
person::{Person, PersonActions},
|
||||
post::{Post, PostActions},
|
||||
},
|
||||
traits::{Blockable, Crud},
|
||||
};
|
||||
use lemmy_db_schema_file::enums::{
|
||||
CommunityNotificationsMode,
|
||||
NotificationTypes,
|
||||
PostNotificationsMode,
|
||||
};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_private_message::PrivateMessageView;
|
||||
use lemmy_db_views_site::SiteView;
|
||||
use lemmy_email::notifications::{
|
||||
send_community_subscribed_email,
|
||||
send_mention_email,
|
||||
send_post_subscribed_email,
|
||||
send_private_message_email,
|
||||
send_reply_email,
|
||||
};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorType, LemmyResult},
|
||||
utils::mention::scrape_text_for_mentions,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[derive(derive_new::new, Debug)]
|
||||
pub struct NotifyData<'a> {
|
||||
post: &'a Post,
|
||||
comment_opt: Option<&'a Comment>,
|
||||
creator: &'a Person,
|
||||
community: &'a Community,
|
||||
do_send_email: bool,
|
||||
}
|
||||
|
||||
impl NotifyData<'_> {
|
||||
/// Scans the post/comment content for mentions, then sends notifications via db and email
|
||||
/// to mentioned users and parent creator.
|
||||
pub async fn send(self, context: &LemmyContext) -> LemmyResult<()> {
|
||||
notify_parent_creator(&self, context).await?;
|
||||
|
||||
notify_mentions(&self, context).await?;
|
||||
|
||||
notify_subscribers(&self, context).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_notifications_allowed(
|
||||
&self,
|
||||
potential_blocker_id: PersonId,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<()> {
|
||||
let pool = &mut context.pool();
|
||||
// TODO: this needs too many queries for each user
|
||||
PersonActions::read_block(pool, potential_blocker_id, self.post.creator_id).await?;
|
||||
InstanceActions::read_block(pool, potential_blocker_id, self.community.instance_id).await?;
|
||||
CommunityActions::read_block(pool, potential_blocker_id, self.post.community_id).await?;
|
||||
let post_notifications = PostActions::read(pool, self.post.id, potential_blocker_id)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|a| a.notifications)
|
||||
.unwrap_or_default();
|
||||
let community_notifications =
|
||||
CommunityActions::read(pool, self.community.id, potential_blocker_id)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|a| a.notifications)
|
||||
.unwrap_or_default();
|
||||
if post_notifications == PostNotificationsMode::Mute
|
||||
|| community_notifications == CommunityNotificationsMode::Mute
|
||||
{
|
||||
// The specific error type is irrelevant
|
||||
return Err(LemmyErrorType::NotFound.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn content(&self) -> String {
|
||||
if let Some(comment) = self.comment_opt.as_ref() {
|
||||
comment.content.clone()
|
||||
} else {
|
||||
self.post.body.clone().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
fn link(&self, context: &LemmyContext) -> LemmyResult<Url> {
|
||||
if let Some(comment) = self.comment_opt.as_ref() {
|
||||
Ok(comment.local_url(context.settings())?)
|
||||
} else {
|
||||
Ok(self.post.local_url(context.settings())?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn insert(
|
||||
&self,
|
||||
recipient_id: PersonId,
|
||||
kind: NotificationTypes,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Notification> {
|
||||
let form = if let Some(comment) = self.comment_opt {
|
||||
NotificationInsertForm::new_comment(comment.id, recipient_id, kind)
|
||||
} else {
|
||||
NotificationInsertForm::new_post(self.post.id, recipient_id, kind)
|
||||
};
|
||||
Notification::create(&mut context.pool(), &form).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn notify_private_message(
|
||||
view: &PrivateMessageView,
|
||||
is_create: bool,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<()> {
|
||||
let Ok(local_recipient) =
|
||||
LocalUserView::read_person(&mut context.pool(), view.recipient.id).await
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let form = NotificationInsertForm::new_private_message(
|
||||
view.private_message.id,
|
||||
local_recipient.person.id,
|
||||
NotificationTypes::PrivateMessage,
|
||||
);
|
||||
Notification::create(&mut context.pool(), &form).await?;
|
||||
|
||||
if is_create {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
if !site_view.local_site.disable_email_notifications {
|
||||
send_private_message_email(
|
||||
&view.creator,
|
||||
&local_recipient,
|
||||
&view.private_message.content,
|
||||
context.settings(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn notify_parent_creator(data: &NotifyData<'_>, context: &LemmyContext) -> LemmyResult<()> {
|
||||
let Some(comment) = data.comment_opt.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Get the parent data
|
||||
let (parent_creator_id, parent_comment) =
|
||||
if let Some(parent_comment_id) = comment.parent_comment_id() {
|
||||
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
|
||||
(parent_comment.creator_id, Some(parent_comment))
|
||||
} else {
|
||||
(data.post.creator_id, None)
|
||||
};
|
||||
|
||||
// Dont send notification to yourself
|
||||
if parent_creator_id == data.creator.id {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let is_blocked = data
|
||||
.check_notifications_allowed(parent_creator_id, context)
|
||||
.await
|
||||
.is_err();
|
||||
if is_blocked {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Ok(user_view) = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
data
|
||||
.insert(user_view.person.id, NotificationTypes::Reply, context)
|
||||
.await?;
|
||||
|
||||
if data.do_send_email {
|
||||
send_reply_email(
|
||||
&user_view,
|
||||
comment,
|
||||
data.creator,
|
||||
&parent_comment,
|
||||
data.post,
|
||||
context.settings(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn notify_mentions(data: &NotifyData<'_>, context: &LemmyContext) -> LemmyResult<()> {
|
||||
let mentions = scrape_text_for_mentions(&data.content())
|
||||
.into_iter()
|
||||
.filter(|m| m.is_local(&context.settings().hostname) && m.name.ne(&data.creator.name));
|
||||
for mention in mentions {
|
||||
// Ignore error if user is remote
|
||||
let Ok(user_view) = LocalUserView::read_from_name(&mut context.pool(), &mention.name).await
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let is_blocked = data
|
||||
.check_notifications_allowed(user_view.person.id, context)
|
||||
.await
|
||||
.is_err();
|
||||
if is_blocked {
|
||||
continue;
|
||||
};
|
||||
|
||||
data
|
||||
.insert(user_view.person.id, NotificationTypes::Mention, context)
|
||||
.await?;
|
||||
|
||||
// Send an email to those local users that have notifications on
|
||||
if data.do_send_email {
|
||||
send_mention_email(
|
||||
&user_view,
|
||||
&data.content(),
|
||||
data.creator,
|
||||
data.link(context)?.into(),
|
||||
context.settings(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn notify_subscribers(data: &NotifyData<'_>, context: &LemmyContext) -> LemmyResult<()> {
|
||||
let is_post = data.comment_opt.is_none();
|
||||
let subscribers = vec![
|
||||
PostActions::list_subscribers(data.post.id, &mut context.pool()).await?,
|
||||
CommunityActions::list_subscribers(data.post.community_id, is_post, &mut context.pool())
|
||||
.await?,
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for person_id in subscribers {
|
||||
if data
|
||||
.check_notifications_allowed(person_id, context)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
continue;
|
||||
};
|
||||
|
||||
data
|
||||
.insert(person_id, NotificationTypes::Subscribed, context)
|
||||
.await?;
|
||||
|
||||
if data.do_send_email {
|
||||
let user_view = LocalUserView::read_person(&mut context.pool(), person_id).await?;
|
||||
if let Some(comment) = data.comment_opt {
|
||||
send_post_subscribed_email(
|
||||
&user_view,
|
||||
data.post,
|
||||
comment,
|
||||
data.link(context)?.into(),
|
||||
context.settings(),
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
send_community_subscribed_email(
|
||||
&user_view,
|
||||
data.post,
|
||||
data.community,
|
||||
data.link(context)?.into(),
|
||||
context.settings(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[expect(clippy::indexing_slicing)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
context::LemmyContext,
|
||||
notify::{notify_private_message, NotifyData},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
assert_length,
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::{Instance, InstanceActions, InstanceBlockForm},
|
||||
notification::{Notification, NotificationInsertForm},
|
||||
person::{Person, PersonActions, PersonBlockForm, PersonInsertForm, PersonUpdateForm},
|
||||
post::{Post, PostInsertForm},
|
||||
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
||||
},
|
||||
traits::{Blockable, Crud},
|
||||
utils::{build_db_pool_for_tests, DbPool},
|
||||
NotificationDataType,
|
||||
};
|
||||
use lemmy_db_schema_file::enums::NotificationTypes;
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_notification::{impls::NotificationQuery, NotificationData, NotificationView};
|
||||
use lemmy_db_views_private_message::PrivateMessageView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
struct Data {
|
||||
instance: Instance,
|
||||
timmy: LocalUserView,
|
||||
sara: LocalUserView,
|
||||
jessica: Person,
|
||||
community: Community,
|
||||
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, "example.com".to_string()).await?;
|
||||
|
||||
let timmy = LocalUserView::create_test_user(pool, "timmy_pcv", "", false).await?;
|
||||
|
||||
let sara = LocalUserView::create_test_user(pool, "sara_pcv", "", false).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.person.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.person.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.person.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,
|
||||
community,
|
||||
timmy_post,
|
||||
jessica_post,
|
||||
timmy_comment,
|
||||
sara_comment,
|
||||
})
|
||||
}
|
||||
|
||||
async fn insert_private_message(
|
||||
form: PrivateMessageInsertForm,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<()> {
|
||||
let pool = &mut context.pool();
|
||||
let pm = PrivateMessage::create(pool, &form).await?;
|
||||
let view = PrivateMessageView::read(pool, pm.id).await?;
|
||||
notify_private_message(&view, false, context).await?;
|
||||
Ok(())
|
||||
}
|
||||
async fn setup_private_messages(data: &Data, context: &LemmyContext) -> LemmyResult<()> {
|
||||
let sara_timmy_message_form = PrivateMessageInsertForm::new(
|
||||
data.sara.person.id,
|
||||
data.timmy.person.id,
|
||||
"sara to timmy".into(),
|
||||
);
|
||||
insert_private_message(sara_timmy_message_form, context).await?;
|
||||
|
||||
let sara_jessica_message_form = PrivateMessageInsertForm::new(
|
||||
data.sara.person.id,
|
||||
data.jessica.id,
|
||||
"sara to jessica".into(),
|
||||
);
|
||||
insert_private_message(sara_jessica_message_form, context).await?;
|
||||
|
||||
let timmy_sara_message_form = PrivateMessageInsertForm::new(
|
||||
data.timmy.person.id,
|
||||
data.sara.person.id,
|
||||
"timmy to sara".into(),
|
||||
);
|
||||
insert_private_message(timmy_sara_message_form, context).await?;
|
||||
|
||||
let jessica_timmy_message_form = PrivateMessageInsertForm::new(
|
||||
data.jessica.id,
|
||||
data.timmy.person.id,
|
||||
"jessica to timmy".into(),
|
||||
);
|
||||
insert_private_message(jessica_timmy_message_form, context).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 context = LemmyContext::init_test_context().await;
|
||||
let pool = &mut context.pool();
|
||||
let data = init_data(pool).await?;
|
||||
|
||||
// Sara replied to timmys comment
|
||||
NotifyData {
|
||||
post: &data.timmy_post,
|
||||
comment_opt: Some(&data.sara_comment),
|
||||
creator: &data.sara.person,
|
||||
community: &data.community,
|
||||
do_send_email: false,
|
||||
}
|
||||
.send(&context)
|
||||
.await?;
|
||||
|
||||
let timmy_unread_replies =
|
||||
NotificationView::get_unread_count(pool, &data.timmy.person, true).await?;
|
||||
assert_eq!(1, timmy_unread_replies);
|
||||
|
||||
let timmy_inbox = NotificationQuery::default()
|
||||
.list(pool, &data.timmy.person)
|
||||
.await?;
|
||||
assert_length!(1, timmy_inbox);
|
||||
|
||||
if let NotificationData::Comment(comment) = &timmy_inbox[0].data {
|
||||
assert_eq!(data.sara_comment.id, comment.comment.id);
|
||||
assert_eq!(data.timmy_post.id, comment.post.id);
|
||||
assert_eq!(data.sara.person.id, comment.creator.id);
|
||||
assert_eq!(
|
||||
data.timmy.person.id,
|
||||
timmy_inbox[0].notification.recipient_id
|
||||
);
|
||||
assert_eq!(NotificationTypes::Reply, timmy_inbox[0].notification.kind);
|
||||
} else {
|
||||
panic!("wrong type")
|
||||
};
|
||||
|
||||
// Mark it as read
|
||||
Notification::mark_read_by_id_and_person(
|
||||
pool,
|
||||
timmy_inbox[0].notification.id,
|
||||
data.timmy.person.id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let timmy_unread_replies =
|
||||
NotificationView::get_unread_count(pool, &data.timmy.person, true).await?;
|
||||
assert_eq!(0, timmy_unread_replies);
|
||||
|
||||
let timmy_inbox_unread = NotificationQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, &data.timmy.person)
|
||||
.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 = NotificationInsertForm::new_comment(
|
||||
data.timmy_comment.id,
|
||||
data.sara.person.id,
|
||||
NotificationTypes::Mention,
|
||||
);
|
||||
Notification::create(pool, &timmy_mention_sara_comment_form).await?;
|
||||
|
||||
// Jessica mentions sara in a post
|
||||
let jessica_mention_sara_post_form = NotificationInsertForm::new_post(
|
||||
data.jessica_post.id,
|
||||
data.sara.person.id,
|
||||
NotificationTypes::Mention,
|
||||
);
|
||||
Notification::create(pool, &jessica_mention_sara_post_form).await?;
|
||||
|
||||
// Test to make sure counts and blocks work correctly
|
||||
let sara_unread_mentions =
|
||||
NotificationView::get_unread_count(pool, &data.sara.person, true).await?;
|
||||
assert_eq!(2, sara_unread_mentions);
|
||||
|
||||
let sara_inbox = NotificationQuery::default()
|
||||
.list(pool, &data.sara.person)
|
||||
.await?;
|
||||
assert_length!(2, sara_inbox);
|
||||
|
||||
if let NotificationData::Post(post) = &sara_inbox[0].data {
|
||||
assert_eq!(data.jessica_post.id, post.post.id);
|
||||
assert_eq!(data.jessica.id, post.creator.id);
|
||||
} else {
|
||||
panic!("wrong type")
|
||||
}
|
||||
assert_eq!(data.sara.person.id, sara_inbox[0].notification.recipient_id);
|
||||
assert_eq!(NotificationTypes::Mention, sara_inbox[0].notification.kind);
|
||||
|
||||
if let NotificationData::Comment(comment) = &sara_inbox[1].data {
|
||||
assert_eq!(data.timmy_comment.id, comment.comment.id);
|
||||
assert_eq!(data.timmy_post.id, comment.post.id);
|
||||
assert_eq!(data.timmy.person.id, comment.creator.id);
|
||||
} else {
|
||||
panic!("wrong type");
|
||||
}
|
||||
assert_eq!(data.sara.person.id, sara_inbox[1].notification.recipient_id);
|
||||
assert_eq!(NotificationTypes::Mention, sara_inbox[1].notification.kind);
|
||||
|
||||
// Sara blocks timmy, and make sure these counts are now empty
|
||||
let sara_blocks_timmy_form = PersonBlockForm::new(data.sara.person.id, data.timmy.person.id);
|
||||
PersonActions::block(pool, &sara_blocks_timmy_form).await?;
|
||||
|
||||
let sara_unread_mentions_after_block =
|
||||
NotificationView::get_unread_count(pool, &data.sara.person, true).await?;
|
||||
assert_eq!(1, sara_unread_mentions_after_block);
|
||||
|
||||
let sara_inbox_after_block = NotificationQuery::default()
|
||||
.list(pool, &data.sara.person)
|
||||
.await?;
|
||||
assert_length!(1, sara_inbox_after_block);
|
||||
|
||||
// Make sure the comment mention which timmy made is the hidden one
|
||||
assert_eq!(
|
||||
NotificationTypes::Mention,
|
||||
sara_inbox_after_block[0].notification.kind
|
||||
);
|
||||
|
||||
// Unblock user so we can reuse the same person
|
||||
PersonActions::unblock(pool, &sara_blocks_timmy_form).await?;
|
||||
|
||||
// Test the type filter
|
||||
let sara_inbox_mentions_only = NotificationQuery {
|
||||
type_: Some(NotificationDataType::Mention),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, &data.sara.person)
|
||||
.await?;
|
||||
assert_length!(2, sara_inbox_mentions_only);
|
||||
|
||||
assert_eq!(
|
||||
NotificationTypes::Mention,
|
||||
sara_inbox_mentions_only[0].notification.kind
|
||||
);
|
||||
|
||||
// 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 bot
|
||||
let sara_unread_mentions_after_hide_bots =
|
||||
NotificationView::get_unread_count(pool, &data.sara.person, false).await?;
|
||||
assert_eq!(1, sara_unread_mentions_after_hide_bots);
|
||||
|
||||
let sara_inbox_after_hide_bots = NotificationQuery::default()
|
||||
.list(pool, &data.sara.person)
|
||||
.await?;
|
||||
assert_length!(1, sara_inbox_after_hide_bots);
|
||||
|
||||
// Make sure the post mention which jessica made is the hidden one
|
||||
assert_eq!(
|
||||
NotificationTypes::Mention,
|
||||
sara_inbox_after_hide_bots[0].notification.kind
|
||||
);
|
||||
|
||||
// Mark them all as read
|
||||
Notification::mark_all_as_read(pool, data.sara.person.id).await?;
|
||||
|
||||
// Make sure none come back
|
||||
let sara_unread_mentions =
|
||||
NotificationView::get_unread_count(pool, &data.sara.person, true).await?;
|
||||
assert_eq!(0, sara_unread_mentions);
|
||||
|
||||
let sara_inbox_unread = NotificationQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, &data.sara.person)
|
||||
.await?;
|
||||
assert_length!(0, sara_inbox_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Useful in combination with filter_map
|
||||
fn to_pm(x: NotificationView) -> Option<PrivateMessageView> {
|
||||
if let NotificationData::PrivateMessage(v) = x.data {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn read_private_messages() -> LemmyResult<()> {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
let pool = &mut context.pool();
|
||||
let data = init_data(pool).await?;
|
||||
setup_private_messages(&data, &context).await?;
|
||||
|
||||
let timmy_messages: Vec<_> = NotificationQuery::default()
|
||||
.list(pool, &data.timmy.person)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(to_pm)
|
||||
.collect();
|
||||
|
||||
// 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.person.id);
|
||||
assert_eq!(timmy_messages[1].creator.id, data.timmy.person.id);
|
||||
assert_eq!(timmy_messages[1].recipient.id, data.sara.person.id);
|
||||
assert_eq!(timmy_messages[2].creator.id, data.sara.person.id);
|
||||
assert_eq!(timmy_messages[2].recipient.id, data.timmy.person.id);
|
||||
|
||||
let timmy_unread = NotificationView::get_unread_count(pool, &data.timmy.person, true).await?;
|
||||
assert_eq!(2, timmy_unread);
|
||||
|
||||
let timmy_unread_messages: Vec<_> = NotificationQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, &data.timmy.person)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(to_pm)
|
||||
.collect();
|
||||
|
||||
// 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.person.id);
|
||||
assert_eq!(timmy_unread_messages[1].creator.id, data.sara.person.id);
|
||||
assert_eq!(timmy_unread_messages[1].recipient.id, data.timmy.person.id);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn ensure_private_message_person_block() -> LemmyResult<()> {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
let pool = &mut context.pool();
|
||||
let data = init_data(pool).await?;
|
||||
setup_private_messages(&data, &context).await?;
|
||||
|
||||
// Make sure blocks are working
|
||||
let timmy_blocks_sara_form = PersonBlockForm::new(data.timmy.person.id, data.sara.person.id);
|
||||
|
||||
let inserted_block = PersonActions::block(pool, &timmy_blocks_sara_form).await?;
|
||||
|
||||
assert_eq!(
|
||||
(data.timmy.person.id, data.sara.person.id, true),
|
||||
(
|
||||
inserted_block.person_id,
|
||||
inserted_block.target_id,
|
||||
inserted_block.blocked_at.is_some()
|
||||
)
|
||||
);
|
||||
|
||||
let timmy_messages: Vec<_> = NotificationQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, &data.timmy.person)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(to_pm)
|
||||
.collect();
|
||||
|
||||
assert_length!(1, &timmy_messages);
|
||||
|
||||
let timmy_unread = NotificationView::get_unread_count(pool, &data.timmy.person, true).await?;
|
||||
assert_eq!(1, timmy_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn ensure_private_message_instance_block() -> LemmyResult<()> {
|
||||
let context = LemmyContext::init_test_context().await;
|
||||
let pool = &mut context.pool();
|
||||
let data = init_data(pool).await?;
|
||||
setup_private_messages(&data, &context).await?;
|
||||
|
||||
// Make sure instance_blocks are working
|
||||
let timmy_blocks_instance_form =
|
||||
InstanceBlockForm::new(data.timmy.person.id, data.sara.person.instance_id);
|
||||
|
||||
let inserted_instance_block = InstanceActions::block(pool, &timmy_blocks_instance_form).await?;
|
||||
|
||||
assert_eq!(data.timmy.person.id, inserted_instance_block.person_id);
|
||||
assert_eq!(
|
||||
data.sara.person.instance_id,
|
||||
inserted_instance_block.instance_id
|
||||
);
|
||||
assert!(inserted_instance_block.blocked_at.is_some());
|
||||
|
||||
let timmy_messages: Vec<_> = NotificationQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, &data.timmy.person)
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(to_pm)
|
||||
.collect();
|
||||
|
||||
assert_length!(0, &timmy_messages);
|
||||
|
||||
let timmy_unread = NotificationView::get_unread_count(pool, &data.timmy.person, true).await?;
|
||||
assert_eq!(0, timmy_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -24,13 +24,13 @@ use lemmy_db_schema::{
|
|||
ModRemovePostForm,
|
||||
},
|
||||
oauth_account::OAuthAccount,
|
||||
person::{Person, PersonActions, PersonUpdateForm},
|
||||
person::{Person, PersonUpdateForm},
|
||||
post::{Post, PostActions, PostReadCommentsForm},
|
||||
private_message::PrivateMessage,
|
||||
registration_application::RegistrationApplication,
|
||||
site::Site,
|
||||
},
|
||||
traits::{Blockable, Crud, Likeable},
|
||||
traits::{Crud, Likeable},
|
||||
utils::DbPool,
|
||||
};
|
||||
use lemmy_db_schema_file::enums::{FederationMode, RegistrationMode};
|
||||
|
@ -321,19 +321,6 @@ pub fn check_comment_deleted_or_removed(comment: &Comment) -> LemmyResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn check_person_instance_community_block(
|
||||
my_id: PersonId,
|
||||
potential_blocker_id: PersonId,
|
||||
community_instance_id: InstanceId,
|
||||
community_id: CommunityId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
PersonActions::read_block(pool, potential_blocker_id, my_id).await?;
|
||||
InstanceActions::read_block(pool, potential_blocker_id, community_instance_id).await?;
|
||||
CommunityActions::read_block(pool, potential_blocker_id, community_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn check_local_vote_mode(
|
||||
score: i16,
|
||||
post_or_comment_id: PostOrCommentId,
|
||||
|
|
|
@ -15,12 +15,12 @@ name = "lemmy_apub"
|
|||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
full = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_views_comment = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_community = { workspace = true, features = ["full"] }
|
||||
|
|
|
@ -14,8 +14,8 @@ use activitypub_federation::{
|
|||
traits::{Activity, Actor, Object},
|
||||
};
|
||||
use lemmy_api_utils::{
|
||||
build_response::send_local_notifs,
|
||||
context::LemmyContext,
|
||||
notify::NotifyData,
|
||||
utils::{check_is_mod_or_admin, check_post_deleted_or_removed},
|
||||
};
|
||||
use lemmy_apub_objects::{
|
||||
|
@ -27,7 +27,7 @@ use lemmy_apub_objects::{
|
|||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{PersonId, PostOrCommentId},
|
||||
newtypes::PersonId,
|
||||
source::{
|
||||
activity::ActivitySendTargets,
|
||||
comment::{Comment, CommentActions, CommentLikeForm},
|
||||
|
@ -38,10 +38,7 @@ use lemmy_db_schema::{
|
|||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_db_views_site::SiteView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyResult},
|
||||
utils::mention::scrape_text_for_mentions,
|
||||
};
|
||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||
use serde_json::{from_value, to_value};
|
||||
use url::Url;
|
||||
|
||||
|
@ -137,12 +134,12 @@ impl Activity for CreateOrUpdateNote {
|
|||
// Need to do this check here instead of Note::from_json because we need the person who
|
||||
// send the activity, not the comment author.
|
||||
let existing_comment = self.object.id.dereference_local(context).await.ok();
|
||||
let (post, _) = self.object.get_parents(context).await?;
|
||||
if let (Some(distinguished), Some(existing_comment)) =
|
||||
(self.object.distinguished, existing_comment)
|
||||
{
|
||||
if distinguished != existing_comment.distinguished {
|
||||
let creator = self.actor.dereference(context).await?;
|
||||
let (post, _) = self.object.get_parents(context).await?;
|
||||
check_is_mod_or_admin(
|
||||
&mut context.pool(),
|
||||
creator.id,
|
||||
|
@ -172,19 +169,10 @@ impl Activity for CreateOrUpdateNote {
|
|||
// anyway.
|
||||
// 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,
|
||||
PostOrCommentId::Comment(comment.id),
|
||||
&actor,
|
||||
do_send_email,
|
||||
context,
|
||||
None,
|
||||
local_instance_id,
|
||||
)
|
||||
.await?;
|
||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||
NotifyData::new(&post.0, Some(&comment.0), &actor, &community, do_send_email)
|
||||
.send(context)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use activitypub_federation::{
|
|||
protocol::verification::{verify_domains_match, verify_urls_match},
|
||||
traits::{Activity, Object},
|
||||
};
|
||||
use lemmy_api_utils::{build_response::send_local_notifs, context::LemmyContext};
|
||||
use lemmy_api_utils::{context::LemmyContext, notify::NotifyData};
|
||||
use lemmy_apub_objects::{
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
utils::{
|
||||
|
@ -21,7 +21,7 @@ use lemmy_apub_objects::{
|
|||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{PersonId, PostOrCommentId},
|
||||
newtypes::PersonId,
|
||||
source::{
|
||||
activity::ActivitySendTargets,
|
||||
community::Community,
|
||||
|
@ -31,10 +31,7 @@ use lemmy_db_schema::{
|
|||
traits::{Crud, Likeable},
|
||||
};
|
||||
use lemmy_db_views_site::SiteView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyResult},
|
||||
utils::mention::scrape_text_for_mentions,
|
||||
};
|
||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||
use url::Url;
|
||||
|
||||
impl CreateOrUpdatePage {
|
||||
|
@ -110,7 +107,6 @@ impl Activity for CreateOrUpdatePage {
|
|||
|
||||
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
|
||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
||||
let local_instance_id = site_view.site.instance_id;
|
||||
|
||||
let post = ApubPost::from_json(self.object, context).await?;
|
||||
|
||||
|
@ -125,18 +121,10 @@ impl Activity for CreateOrUpdatePage {
|
|||
self.kind == CreateOrUpdateType::Create && !site_view.local_site.disable_email_notifications;
|
||||
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,
|
||||
local_instance_id,
|
||||
)
|
||||
.await?;
|
||||
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||
NotifyData::new(&post.0, None, &actor, &community, do_send_email)
|
||||
.send(context)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -15,17 +15,18 @@ name = "lemmy_apub_objects"
|
|||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[features]
|
||||
full = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_views_community_moderator = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_community_person_ban = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_local_user = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_site = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_private_message = { workspace = true, features = ["full"] }
|
||||
lemmy_utils = { workspace = true, features = ["full"] }
|
||||
lemmy_db_schema = { workspace = true, features = ["full"] }
|
||||
lemmy_api_utils = { workspace = true, features = ["full"] }
|
||||
|
|
|
@ -17,6 +17,7 @@ use activitypub_federation::{
|
|||
use chrono::Utc;
|
||||
use lemmy_api_utils::{
|
||||
context::LemmyContext,
|
||||
notify::notify_private_message,
|
||||
plugins::{plugin_hook_after, plugin_hook_before},
|
||||
utils::{check_private_messages_enabled, get_url_blocklist, process_markdown, slur_regex},
|
||||
};
|
||||
|
@ -29,6 +30,7 @@ use lemmy_db_schema::{
|
|||
traits::{Blockable, Crud},
|
||||
};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_db_views_private_message::PrivateMessageView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorType, LemmyResult},
|
||||
utils::markdown::markdown_to_html,
|
||||
|
@ -158,7 +160,6 @@ impl Object for ApubPrivateMessage {
|
|||
published_at: note.published,
|
||||
updated_at: note.updated,
|
||||
deleted: Some(false),
|
||||
read: None,
|
||||
ap_id: Some(note.id.into()),
|
||||
local: Some(false),
|
||||
};
|
||||
|
@ -166,6 +167,8 @@ impl Object for ApubPrivateMessage {
|
|||
let timestamp = note.updated.or(note.published).unwrap_or_else(Utc::now);
|
||||
let pm = DbPrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?;
|
||||
plugin_hook_after("after_receive_federated_private_message", &pm)?;
|
||||
let view = PrivateMessageView::read(&mut context.pool(), pm.id).await?;
|
||||
notify_private_message(&view, pm.updated_at.is_none(), context).await?;
|
||||
Ok(pm.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{CommentId, CommentReplyId, PersonId},
|
||||
source::comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{dsl::insert_into, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema_file::schema::comment_reply;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
impl Crud for CommentReply {
|
||||
type InsertForm = CommentReplyInsertForm;
|
||||
type UpdateForm = CommentReplyUpdateForm;
|
||||
type IdType = CommentReplyId;
|
||||
|
||||
async fn create(
|
||||
pool: &mut DbPool<'_>,
|
||||
comment_reply_form: &Self::InsertForm,
|
||||
) -> LemmyResult<Self> {
|
||||
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(comment_reply::table)
|
||||
.values(comment_reply_form)
|
||||
.on_conflict((comment_reply::recipient_id, comment_reply::comment_id))
|
||||
.do_update()
|
||||
.set(comment_reply_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateCommentReply)
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
comment_reply_id: CommentReplyId,
|
||||
comment_reply_form: &Self::UpdateForm,
|
||||
) -> LemmyResult<Self> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(comment_reply::table.find(comment_reply_id))
|
||||
.set(comment_reply_form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommentReply)
|
||||
}
|
||||
}
|
||||
|
||||
impl CommentReply {
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<Vec<CommentReply>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(
|
||||
comment_reply::table
|
||||
.filter(comment_reply::recipient_id.eq(for_recipient_id))
|
||||
.filter(comment_reply::read.eq(false)),
|
||||
)
|
||||
.set(comment_reply::read.eq(true))
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntMarkCommentReplyAsRead)
|
||||
}
|
||||
|
||||
pub async fn read_by_comment(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_comment_id: CommentId,
|
||||
) -> LemmyResult<Option<Self>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
comment_reply::table
|
||||
.filter(comment_reply::comment_id.eq(for_comment_id))
|
||||
.first(conn)
|
||||
.await
|
||||
.optional()
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
pub async fn read_by_comment_and_person(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_comment_id: CommentId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<Option<Self>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
comment_reply::table
|
||||
.filter(comment_reply::comment_id.eq(for_comment_id))
|
||||
.filter(comment_reply::recipient_id.eq(for_recipient_id))
|
||||
.first(conn)
|
||||
.await
|
||||
.optional()
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
|
@ -37,8 +37,8 @@ use diesel::{
|
|||
use diesel_async::RunQueryDsl;
|
||||
use diesel_uplete::{uplete, UpleteCount};
|
||||
use lemmy_db_schema_file::{
|
||||
enums::{CommunityFollowerState, CommunityVisibility, ListingType},
|
||||
schema::{comment, community, community_actions, instance, post},
|
||||
enums::{CommunityFollowerState, CommunityNotificationsMode, CommunityVisibility, ListingType},
|
||||
schema::{comment, community, community_actions, instance, local_user, post},
|
||||
};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||
|
@ -455,6 +455,61 @@ impl CommunityActions {
|
|||
.await
|
||||
.map_err(|_e: Arc<LemmyError>| LemmyErrorType::NotFound.into())
|
||||
}
|
||||
|
||||
pub async fn update_notification_state(
|
||||
community_id: CommunityId,
|
||||
person_id: PersonId,
|
||||
new_state: CommunityNotificationsMode,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let form = (
|
||||
community_actions::person_id.eq(person_id),
|
||||
community_actions::community_id.eq(community_id),
|
||||
community_actions::notifications.eq(new_state),
|
||||
);
|
||||
|
||||
insert_into(community_actions::table)
|
||||
.values(form.clone())
|
||||
.on_conflict((
|
||||
community_actions::person_id,
|
||||
community_actions::community_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(form)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_subscribers(
|
||||
community_id: CommunityId,
|
||||
is_post: bool,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<Vec<PersonId>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let mut query = community_actions::table
|
||||
.inner_join(local_user::table.on(community_actions::person_id.eq(local_user::person_id)))
|
||||
.filter(community_actions::community_id.eq(community_id))
|
||||
.select(local_user::person_id)
|
||||
.into_boxed();
|
||||
if is_post {
|
||||
query = query.filter(
|
||||
community_actions::notifications
|
||||
.eq(CommunityNotificationsMode::AllPosts)
|
||||
.or(community_actions::notifications.eq(CommunityNotificationsMode::AllPostsAndComments)),
|
||||
);
|
||||
} else {
|
||||
query = query.filter(
|
||||
community_actions::notifications.eq(CommunityNotificationsMode::AllPostsAndComments),
|
||||
);
|
||||
}
|
||||
query
|
||||
.get_results(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl Bannable for CommunityActions {
|
||||
|
|
|
@ -2,7 +2,6 @@ pub mod activity;
|
|||
pub mod actor_language;
|
||||
pub mod captcha_answer;
|
||||
pub mod comment;
|
||||
pub mod comment_reply;
|
||||
pub mod comment_report;
|
||||
pub mod community;
|
||||
pub mod community_report;
|
||||
|
@ -22,12 +21,11 @@ pub mod local_user;
|
|||
pub mod login_token;
|
||||
pub mod mod_log;
|
||||
pub mod multi_community;
|
||||
pub mod notification;
|
||||
pub mod oauth_account;
|
||||
pub mod oauth_provider;
|
||||
pub mod password_reset_request;
|
||||
pub mod person;
|
||||
pub mod person_comment_mention;
|
||||
pub mod person_post_mention;
|
||||
pub mod post;
|
||||
pub mod post_report;
|
||||
pub mod post_tag;
|
||||
|
|
69
crates/db_schema/src/impls/notification.rs
Normal file
69
crates/db_schema/src/impls/notification.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use crate::{
|
||||
newtypes::{CommentId, NotificationId, PersonId},
|
||||
source::notification::{Notification, NotificationInsertForm},
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{
|
||||
dsl::{insert_into, update},
|
||||
ExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema_file::schema::notification;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
impl Notification {
|
||||
pub async fn create(pool: &mut DbPool<'_>, form: &NotificationInsertForm) -> LemmyResult<Self> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
insert_into(notification::table)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreateNotification)
|
||||
}
|
||||
|
||||
pub async fn read_by_comment_id(
|
||||
pool: &mut DbPool<'_>,
|
||||
comment_id: CommentId,
|
||||
) -> LemmyResult<Self> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
notification::table
|
||||
.filter(notification::comment_id.eq(comment_id))
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<usize> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(
|
||||
notification::table
|
||||
.filter(notification::recipient_id.eq(for_recipient_id))
|
||||
.filter(notification::read.eq(false)),
|
||||
)
|
||||
.set(notification::read.eq(true))
|
||||
.execute(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateNotification)
|
||||
}
|
||||
|
||||
pub async fn mark_read_by_id_and_person(
|
||||
pool: &mut DbPool<'_>,
|
||||
notification_id: NotificationId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<usize> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
update(
|
||||
notification::table
|
||||
.filter(notification::id.eq(notification_id))
|
||||
.filter(notification::recipient_id.eq(for_recipient_id)),
|
||||
)
|
||||
.set(notification::read.eq(true))
|
||||
.execute(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{CommentId, PersonCommentMentionId, PersonId},
|
||||
source::person_comment_mention::{
|
||||
PersonCommentMention,
|
||||
PersonCommentMentionInsertForm,
|
||||
PersonCommentMentionUpdateForm,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{dsl::insert_into, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema_file::schema::person_comment_mention;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
impl Crud for PersonCommentMention {
|
||||
type InsertForm = PersonCommentMentionInsertForm;
|
||||
type UpdateForm = PersonCommentMentionUpdateForm;
|
||||
type IdType = PersonCommentMentionId;
|
||||
|
||||
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> LemmyResult<Self> {
|
||||
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(form)
|
||||
.on_conflict((
|
||||
person_comment_mention::recipient_id,
|
||||
person_comment_mention::comment_id,
|
||||
))
|
||||
.do_update()
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePersonCommentMention)
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_comment_mention_id: PersonCommentMentionId,
|
||||
person_comment_mention_form: &Self::UpdateForm,
|
||||
) -> LemmyResult<Self> {
|
||||
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
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePersonCommentMention)
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonCommentMention {
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<Vec<PersonCommentMention>> {
|
||||
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
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePersonCommentMention)
|
||||
}
|
||||
|
||||
pub async fn read_by_comment_and_person(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_comment_id: CommentId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<Option<Self>> {
|
||||
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()
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
use crate::{
|
||||
diesel::OptionalExtension,
|
||||
newtypes::{PersonId, PersonPostMentionId, PostId},
|
||||
source::person_post_mention::{
|
||||
PersonPostMention,
|
||||
PersonPostMentionInsertForm,
|
||||
PersonPostMentionUpdateForm,
|
||||
},
|
||||
traits::Crud,
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{dsl::insert_into, ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema_file::schema::person_post_mention;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
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,
|
||||
) -> LemmyResult<Self> {
|
||||
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
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePersonPostMention)
|
||||
}
|
||||
|
||||
async fn update(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_post_mention_id: PersonPostMentionId,
|
||||
person_post_mention_form: &Self::UpdateForm,
|
||||
) -> LemmyResult<Self> {
|
||||
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
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePersonPostMention)
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonPostMention {
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<Vec<Self>> {
|
||||
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
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePersonPostMention)
|
||||
}
|
||||
|
||||
pub async fn read_by_post_and_person(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_post_id: PostId,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<Option<Self>> {
|
||||
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()
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ use crate::{
|
|||
SITEMAP_LIMIT,
|
||||
},
|
||||
};
|
||||
use ::url::Url;
|
||||
use chrono::{DateTime, Utc};
|
||||
use diesel::{
|
||||
dsl::{count, insert_into, not, update},
|
||||
|
@ -39,11 +38,15 @@ use diesel::{
|
|||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use diesel_uplete::{uplete, UpleteCount};
|
||||
use lemmy_db_schema_file::schema::{community, person, post, post_actions};
|
||||
use lemmy_db_schema_file::{
|
||||
enums::PostNotificationsMode,
|
||||
schema::{community, local_user, person, post, post_actions},
|
||||
};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||
settings::structs::Settings,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
impl Crud for Post {
|
||||
type InsertForm = PostInsertForm;
|
||||
|
@ -542,9 +545,7 @@ impl PostActions {
|
|||
.map(|post_id| (PostReadForm::new(*post_id, person_id)))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl PostActions {
|
||||
pub async fn read(
|
||||
pool: &mut DbPool<'_>,
|
||||
post_id: PostId,
|
||||
|
@ -570,6 +571,45 @@ impl PostActions {
|
|||
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
|
||||
Self::read(pool, PostId(*post_id), PersonId(*person_id)).await
|
||||
}
|
||||
|
||||
pub async fn update_notification_state(
|
||||
post_id: PostId,
|
||||
person_id: PersonId,
|
||||
new_state: PostNotificationsMode,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let form = (
|
||||
post_actions::person_id.eq(person_id),
|
||||
post_actions::post_id.eq(post_id),
|
||||
post_actions::notifications.eq(new_state),
|
||||
);
|
||||
|
||||
insert_into(post_actions::table)
|
||||
.values(form.clone())
|
||||
.on_conflict((post_actions::person_id, post_actions::post_id))
|
||||
.do_update()
|
||||
.set(form)
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_subscribers(
|
||||
post_id: PostId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<Vec<PersonId>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
post_actions::table
|
||||
.inner_join(local_user::table.on(post_actions::person_id.eq(local_user::person_id)))
|
||||
.filter(post_actions::post_id.eq(post_id))
|
||||
.filter(post_actions::notifications.eq(PostNotificationsMode::AllComments))
|
||||
.select(local_user::person_id)
|
||||
.get_results(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -63,22 +63,6 @@ impl PrivateMessage {
|
|||
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)
|
||||
}
|
||||
|
||||
pub async fn mark_all_as_read(
|
||||
pool: &mut DbPool<'_>,
|
||||
for_recipient_id: PersonId,
|
||||
) -> LemmyResult<Vec<Self>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::update(
|
||||
private_message::table
|
||||
.filter(private_message::recipient_id.eq(for_recipient_id))
|
||||
.filter(private_message::read.eq(false)),
|
||||
)
|
||||
.set(private_message::read.eq(true))
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)
|
||||
}
|
||||
|
||||
pub async fn read_from_apub_id(
|
||||
pool: &mut DbPool<'_>,
|
||||
object_id: Url,
|
||||
|
@ -161,7 +145,6 @@ mod tests {
|
|||
creator_id: inserted_creator.id,
|
||||
recipient_id: inserted_recipient.id,
|
||||
deleted: false,
|
||||
read: false,
|
||||
updated_at: None,
|
||||
published_at: inserted_private_message.published_at,
|
||||
ap_id: Url::parse(&format!(
|
||||
|
@ -195,15 +178,6 @@ mod tests {
|
|||
},
|
||||
)
|
||||
.await?;
|
||||
let marked_read_private_message = PrivateMessage::update(
|
||||
pool,
|
||||
inserted_private_message.id,
|
||||
&PrivateMessageUpdateForm {
|
||||
read: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Person::delete(pool, inserted_creator.id).await?;
|
||||
Person::delete(pool, inserted_recipient.id).await?;
|
||||
Instance::delete(pool, inserted_instance.id).await?;
|
||||
|
@ -212,7 +186,6 @@ mod tests {
|
|||
assert_eq!(expected_private_message, updated_private_message);
|
||||
assert_eq!(expected_private_message, inserted_private_message);
|
||||
assert!(deleted_private_message.deleted);
|
||||
assert!(marked_read_private_message.read);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -117,12 +117,12 @@ pub enum ModlogActionType {
|
|||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
/// A list of possible types for the inbox.
|
||||
pub enum InboxDataType {
|
||||
pub enum NotificationDataType {
|
||||
All,
|
||||
CommentReply,
|
||||
CommentMention,
|
||||
PostMention,
|
||||
Reply,
|
||||
Mention,
|
||||
PrivateMessage,
|
||||
Subscribed,
|
||||
}
|
||||
|
||||
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -93,15 +93,7 @@ impl fmt::Display for PrivateMessageId {
|
|||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// 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))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// The person post mention id.
|
||||
pub struct PersonPostMentionId(pub i32);
|
||||
pub struct NotificationId(pub i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
|
@ -145,13 +137,6 @@ pub struct SiteId(pub i32);
|
|||
/// The language id.
|
||||
pub struct LanguageId(pub i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// The comment reply id.
|
||||
pub struct CommentReplyId(pub i32);
|
||||
|
||||
#[derive(
|
||||
Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default, Ord, PartialOrd,
|
||||
)]
|
||||
|
@ -242,13 +227,6 @@ pub struct PersonLikedCombinedId(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))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
/// The inbox combined id
|
||||
pub struct InboxCombinedId(i32);
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||
/// The search combined id
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
use crate::newtypes::{
|
||||
CommentReplyId,
|
||||
InboxCombinedId,
|
||||
PersonCommentMentionId,
|
||||
PersonPostMentionId,
|
||||
PrivateMessageId,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use i_love_jesus::CursorKeysModule;
|
||||
#[cfg(feature = "full")]
|
||||
use lemmy_db_schema_file::schema::inbox_combined;
|
||||
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))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// A combined inbox table.
|
||||
pub struct InboxCombined {
|
||||
pub id: InboxCombinedId,
|
||||
pub published_at: 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,4 +1,3 @@
|
|||
pub mod inbox;
|
||||
pub mod modlog;
|
||||
pub mod person_content;
|
||||
pub mod person_liked;
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
use crate::newtypes::{CommentId, CommentReplyId, PersonId};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use lemmy_db_schema_file::schema::comment_reply;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Queryable, Selectable, Associations, Identifiable)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_reply))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// A comment reply.
|
||||
pub struct CommentReply {
|
||||
pub id: CommentReplyId,
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
pub read: bool,
|
||||
pub published_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = comment_reply))]
|
||||
pub struct CommentReplyInsertForm {
|
||||
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 = comment_reply))]
|
||||
pub struct CommentReplyUpdateForm {
|
||||
pub read: Option<bool>,
|
||||
}
|
|
@ -4,7 +4,11 @@ use crate::{
|
|||
source::placeholder_apub_url,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use lemmy_db_schema_file::enums::{CommunityFollowerState, CommunityVisibility};
|
||||
use lemmy_db_schema_file::enums::{
|
||||
CommunityFollowerState,
|
||||
CommunityNotificationsMode,
|
||||
CommunityVisibility,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -210,6 +214,7 @@ pub struct CommunityActions {
|
|||
pub received_ban_at: Option<DateTime<Utc>>,
|
||||
/// When their ban expires.
|
||||
pub ban_expires_at: Option<DateTime<Utc>>,
|
||||
pub notifications: Option<CommunityNotificationsMode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, derive_new::new)]
|
||||
|
|
|
@ -7,7 +7,6 @@ pub mod actor_language;
|
|||
pub mod captcha_answer;
|
||||
pub mod combined;
|
||||
pub mod comment;
|
||||
pub mod comment_reply;
|
||||
pub mod comment_report;
|
||||
pub mod community;
|
||||
pub mod community_report;
|
||||
|
@ -28,12 +27,11 @@ pub mod local_user;
|
|||
pub mod login_token;
|
||||
pub mod mod_log;
|
||||
pub mod multi_community;
|
||||
pub mod notification;
|
||||
pub mod oauth_account;
|
||||
pub mod oauth_provider;
|
||||
pub mod password_reset_request;
|
||||
pub mod person;
|
||||
pub mod person_comment_mention;
|
||||
pub mod person_post_mention;
|
||||
pub mod post;
|
||||
pub mod post_report;
|
||||
pub mod post_tag;
|
||||
|
|
77
crates/db_schema/src/source/notification.rs
Normal file
77
crates/db_schema/src/source/notification.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use crate::newtypes::{CommentId, NotificationId, PersonId, PostId, PrivateMessageId};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use i_love_jesus::CursorKeysModule;
|
||||
use lemmy_db_schema_file::enums::NotificationTypes;
|
||||
#[cfg(feature = "full")]
|
||||
use lemmy_db_schema_file::schema::notification;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Queryable, Selectable, Identifiable, CursorKeysModule)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = notification))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
#[cfg_attr(feature = "full", cursor_keys_module(name = notification_keys))]
|
||||
pub struct Notification {
|
||||
pub id: NotificationId,
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: Option<CommentId>,
|
||||
pub read: bool,
|
||||
pub published_at: DateTime<Utc>,
|
||||
pub kind: NotificationTypes,
|
||||
pub post_id: Option<PostId>,
|
||||
pub private_message_id: Option<PrivateMessageId>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = notification))]
|
||||
pub struct NotificationInsertForm {
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: Option<CommentId>,
|
||||
pub kind: NotificationTypes,
|
||||
pub post_id: Option<PostId>,
|
||||
pub private_message_id: Option<PrivateMessageId>,
|
||||
}
|
||||
|
||||
impl NotificationInsertForm {
|
||||
pub fn new_post(post_id: PostId, recipient_id: PersonId, kind: NotificationTypes) -> Self {
|
||||
Self {
|
||||
post_id: Some(post_id),
|
||||
comment_id: None,
|
||||
private_message_id: None,
|
||||
recipient_id,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
pub fn new_comment(
|
||||
comment_id: CommentId,
|
||||
recipient_id: PersonId,
|
||||
kind: NotificationTypes,
|
||||
) -> Self {
|
||||
Self {
|
||||
post_id: None,
|
||||
comment_id: Some(comment_id),
|
||||
private_message_id: None,
|
||||
recipient_id,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
pub fn new_private_message(
|
||||
private_message_id: PrivateMessageId,
|
||||
recipient_id: PersonId,
|
||||
kind: NotificationTypes,
|
||||
) -> Self {
|
||||
Self {
|
||||
post_id: None,
|
||||
comment_id: None,
|
||||
private_message_id: Some(private_message_id),
|
||||
recipient_id,
|
||||
kind,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use crate::newtypes::{CommentId, PersonCommentMentionId, PersonId};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use lemmy_db_schema_file::schema::person_comment_mention;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Queryable, Selectable, Associations, Identifiable)
|
||||
)]
|
||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
|
||||
#[cfg_attr(feature = "full", diesel(table_name = person_comment_mention))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// A person mention.
|
||||
pub struct PersonCommentMention {
|
||||
pub id: PersonCommentMentionId,
|
||||
pub recipient_id: PersonId,
|
||||
pub comment_id: CommentId,
|
||||
pub read: bool,
|
||||
pub published_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[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_comment_mention))]
|
||||
pub struct PersonCommentMentionUpdateForm {
|
||||
pub read: Option<bool>,
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use crate::newtypes::{PersonId, PersonPostMentionId, PostId};
|
||||
use chrono::{DateTime, Utc};
|
||||
#[cfg(feature = "full")]
|
||||
use lemmy_db_schema_file::schema::person_post_mention;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Queryable, Selectable, Associations, Identifiable)
|
||||
)]
|
||||
#[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 = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// A person mention.
|
||||
pub struct PersonPostMention {
|
||||
pub id: PersonPostMentionId,
|
||||
pub recipient_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub read: bool,
|
||||
pub published_at: 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>,
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId};
|
||||
use chrono::{DateTime, Utc};
|
||||
use lemmy_db_schema_file::enums::PostNotificationsMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
|
@ -201,6 +202,7 @@ pub struct PostActions {
|
|||
pub like_score: Option<i16>,
|
||||
/// When the post was hidden.
|
||||
pub hidden_at: Option<DateTime<Utc>>,
|
||||
pub notifications: Option<PostNotificationsMode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, derive_new::new)]
|
||||
|
|
|
@ -26,7 +26,6 @@ pub struct PrivateMessage {
|
|||
pub recipient_id: PersonId,
|
||||
pub content: String,
|
||||
pub deleted: bool,
|
||||
pub read: bool,
|
||||
pub published_at: DateTime<Utc>,
|
||||
pub updated_at: Option<DateTime<Utc>>,
|
||||
pub ap_id: DbUrl,
|
||||
|
@ -47,8 +46,6 @@ pub struct PrivateMessageInsertForm {
|
|||
#[new(default)]
|
||||
pub deleted: Option<bool>,
|
||||
#[new(default)]
|
||||
pub read: Option<bool>,
|
||||
#[new(default)]
|
||||
pub published_at: Option<DateTime<Utc>>,
|
||||
#[new(default)]
|
||||
pub updated_at: Option<DateTime<Utc>>,
|
||||
|
@ -64,7 +61,6 @@ pub struct PrivateMessageInsertForm {
|
|||
pub struct PrivateMessageUpdateForm {
|
||||
pub content: Option<String>,
|
||||
pub deleted: Option<bool>,
|
||||
pub read: Option<bool>,
|
||||
pub published_at: Option<DateTime<Utc>>,
|
||||
pub updated_at: Option<Option<DateTime<Utc>>>,
|
||||
pub ap_id: Option<DbUrl>,
|
||||
|
|
|
@ -226,3 +226,59 @@ pub enum VoteShow {
|
|||
ShowForOthers,
|
||||
Hide,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
EnumString, Display, Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Hash,
|
||||
)]
|
||||
#[cfg_attr(feature = "full", derive(DbEnum))]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
ExistingTypePath = "crate::schema::sql_types::PostNotificationsModeEnum"
|
||||
)]
|
||||
#[cfg_attr(feature = "full", DbValueStyle = "verbatim")]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
/// Available settings for post notifications
|
||||
pub enum PostNotificationsMode {
|
||||
AllComments,
|
||||
#[default]
|
||||
RepliesAndMentions,
|
||||
Mute,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
EnumString, Display, Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Hash,
|
||||
)]
|
||||
#[cfg_attr(feature = "full", derive(DbEnum))]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
ExistingTypePath = "crate::schema::sql_types::CommunityNotificationsModeEnum"
|
||||
)]
|
||||
#[cfg_attr(feature = "full", DbValueStyle = "verbatim")]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
/// Available settings for community notifications
|
||||
pub enum CommunityNotificationsMode {
|
||||
AllPostsAndComments,
|
||||
AllPosts,
|
||||
#[default]
|
||||
RepliesAndMentions,
|
||||
Mute,
|
||||
}
|
||||
|
||||
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(DbEnum))]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
ExistingTypePath = "crate::schema::sql_types::NotificationTypeEnum"
|
||||
)]
|
||||
#[cfg_attr(feature = "full", DbValueStyle = "verbatim")]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
/// Types of notifications which can be received in inbox
|
||||
pub enum NotificationTypes {
|
||||
Mention,
|
||||
Reply,
|
||||
Subscribed,
|
||||
PrivateMessage,
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@ pub mod sql_types {
|
|||
#[diesel(postgres_type(name = "community_follower_state"))]
|
||||
pub struct CommunityFollowerState;
|
||||
|
||||
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "community_notifications_mode_enum"))]
|
||||
pub struct CommunityNotificationsModeEnum;
|
||||
|
||||
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "community_visibility"))]
|
||||
pub struct CommunityVisibility;
|
||||
|
@ -29,10 +33,18 @@ pub mod sql_types {
|
|||
#[diesel(postgres_type(name = "ltree"))]
|
||||
pub struct Ltree;
|
||||
|
||||
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "notification_type_enum"))]
|
||||
pub struct NotificationTypeEnum;
|
||||
|
||||
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "post_listing_mode_enum"))]
|
||||
pub struct PostListingModeEnum;
|
||||
|
||||
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "post_notifications_mode_enum"))]
|
||||
pub struct PostNotificationsModeEnum;
|
||||
|
||||
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "post_sort_type_enum"))]
|
||||
pub struct PostSortTypeEnum;
|
||||
|
@ -156,16 +168,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
comment_reply (id) {
|
||||
id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
comment_id -> Int4,
|
||||
read -> Bool,
|
||||
published_at -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
comment_report (id) {
|
||||
id -> Int4,
|
||||
|
@ -238,6 +240,7 @@ diesel::table! {
|
|||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::CommunityFollowerState;
|
||||
use super::sql_types::CommunityNotificationsModeEnum;
|
||||
|
||||
community_actions (person_id, community_id) {
|
||||
community_id -> Int4,
|
||||
|
@ -249,6 +252,7 @@ diesel::table! {
|
|||
became_moderator_at -> Nullable<Timestamptz>,
|
||||
received_ban_at -> Nullable<Timestamptz>,
|
||||
ban_expires_at -> Nullable<Timestamptz>,
|
||||
notifications -> Nullable<CommunityNotificationsModeEnum>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -347,17 +351,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
inbox_combined (id) {
|
||||
id -> Int4,
|
||||
published_at -> 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,
|
||||
|
@ -752,6 +745,22 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::NotificationTypeEnum;
|
||||
|
||||
notification (id) {
|
||||
id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
comment_id -> Nullable<Int4>,
|
||||
read -> Bool,
|
||||
published_at -> Timestamptz,
|
||||
kind -> NotificationTypeEnum,
|
||||
post_id -> Nullable<Int4>,
|
||||
private_message_id -> Nullable<Int4>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
oauth_account (oauth_provider_id, local_user_id) {
|
||||
local_user_id -> Int4,
|
||||
|
@ -838,16 +847,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_comment_mention (id) {
|
||||
id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
comment_id -> Int4,
|
||||
read -> Bool,
|
||||
published_at -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_content_combined (id) {
|
||||
id -> Int4,
|
||||
|
@ -868,16 +867,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_post_mention (id) {
|
||||
id -> Int4,
|
||||
recipient_id -> Int4,
|
||||
post_id -> Int4,
|
||||
read -> Bool,
|
||||
published_at -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
person_saved_combined (id) {
|
||||
id -> Int4,
|
||||
|
@ -934,6 +923,9 @@ diesel::table! {
|
|||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use super::sql_types::PostNotificationsModeEnum;
|
||||
|
||||
post_actions (person_id, post_id) {
|
||||
post_id -> Int4,
|
||||
person_id -> Int4,
|
||||
|
@ -944,6 +936,7 @@ diesel::table! {
|
|||
liked_at -> Nullable<Timestamptz>,
|
||||
like_score -> Nullable<Int2>,
|
||||
hidden_at -> Nullable<Timestamptz>,
|
||||
notifications -> Nullable<PostNotificationsModeEnum>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -980,7 +973,6 @@ diesel::table! {
|
|||
recipient_id -> Int4,
|
||||
content -> Text,
|
||||
deleted -> Bool,
|
||||
read -> Bool,
|
||||
published_at -> Timestamptz,
|
||||
updated_at -> Nullable<Timestamptz>,
|
||||
#[max_length = 255]
|
||||
|
@ -1145,8 +1137,6 @@ diesel::joinable!(comment -> person (creator_id));
|
|||
diesel::joinable!(comment -> post (post_id));
|
||||
diesel::joinable!(comment_actions -> comment (comment_id));
|
||||
diesel::joinable!(comment_actions -> person (person_id));
|
||||
diesel::joinable!(comment_reply -> comment (comment_id));
|
||||
diesel::joinable!(comment_reply -> person (recipient_id));
|
||||
diesel::joinable!(comment_report -> comment (comment_id));
|
||||
diesel::joinable!(community -> instance (instance_id));
|
||||
diesel::joinable!(community_actions -> community (community_id));
|
||||
|
@ -1158,10 +1148,6 @@ 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 -> person (person_id));
|
||||
|
@ -1214,19 +1200,19 @@ diesel::joinable!(multi_community_entry -> community (community_id));
|
|||
diesel::joinable!(multi_community_entry -> multi_community (multi_community_id));
|
||||
diesel::joinable!(multi_community_follow -> multi_community (multi_community_id));
|
||||
diesel::joinable!(multi_community_follow -> person (person_id));
|
||||
diesel::joinable!(notification -> comment (comment_id));
|
||||
diesel::joinable!(notification -> person (recipient_id));
|
||||
diesel::joinable!(notification -> post (post_id));
|
||||
diesel::joinable!(notification -> private_message (private_message_id));
|
||||
diesel::joinable!(oauth_account -> local_user (local_user_id));
|
||||
diesel::joinable!(oauth_account -> oauth_provider (oauth_provider_id));
|
||||
diesel::joinable!(password_reset_request -> local_user (local_user_id));
|
||||
diesel::joinable!(person -> instance (instance_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_liked_combined -> comment (comment_id));
|
||||
diesel::joinable!(person_liked_combined -> person (person_id));
|
||||
diesel::joinable!(person_liked_combined -> post (post_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));
|
||||
|
@ -1265,7 +1251,6 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
captcha_answer,
|
||||
comment,
|
||||
comment_actions,
|
||||
comment_reply,
|
||||
comment_report,
|
||||
community,
|
||||
community_actions,
|
||||
|
@ -1278,7 +1263,6 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
federation_blocklist,
|
||||
federation_queue_state,
|
||||
image_details,
|
||||
inbox_combined,
|
||||
instance,
|
||||
instance_actions,
|
||||
language,
|
||||
|
@ -1305,15 +1289,14 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||
multi_community,
|
||||
multi_community_entry,
|
||||
multi_community_follow,
|
||||
notification,
|
||||
oauth_account,
|
||||
oauth_provider,
|
||||
password_reset_request,
|
||||
person,
|
||||
person_actions,
|
||||
person_comment_mention,
|
||||
person_content_combined,
|
||||
person_liked_combined,
|
||||
person_post_mention,
|
||||
person_saved_combined,
|
||||
post,
|
||||
post_actions,
|
||||
|
|
|
@ -613,33 +613,6 @@ CALL r.create_modlog_combined_trigger ('mod_remove_comment');
|
|||
CALL r.create_modlog_combined_trigger ('mod_remove_community');
|
||||
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_at, thing_id)
|
||||
VALUES (NEW.published_at, 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');
|
||||
-- Prevent using delete instead of uplete on action tables
|
||||
CREATE FUNCTION r.require_uplete ()
|
||||
RETURNS TRIGGER
|
||||
|
|
|
@ -519,7 +519,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn check_test_data(conn: &mut PgConnection) -> LemmyResult<()> {
|
||||
use lemmy_db_schema_file::schema::{comment, comment_reply, community, person, post};
|
||||
use lemmy_db_schema_file::schema::{comment, community, notification, person, post};
|
||||
|
||||
// Check users
|
||||
let users: Vec<(i32, String, Option<String>, String, String)> = person::table
|
||||
|
@ -622,16 +622,16 @@ mod tests {
|
|||
assert_eq!(comments[1].6, 0); // Zero upvotes
|
||||
|
||||
// Check comment replies
|
||||
let replies: Vec<(i32, i32)> = comment_reply::table
|
||||
.select((comment_reply::comment_id, comment_reply::recipient_id))
|
||||
.order_by(comment_reply::comment_id)
|
||||
let replies: Vec<(Option<i32>, i32)> = notification::table
|
||||
.select((notification::comment_id, notification::recipient_id))
|
||||
.order_by(notification::comment_id)
|
||||
.load(conn)
|
||||
.map_err(|e| anyhow!("Failed to read comment replies: {}", e))?;
|
||||
|
||||
assert_eq!(replies.len(), 2);
|
||||
assert_eq!(replies[0].0, TEST_COMMENT_ID_1);
|
||||
assert_eq!(replies[0].0, Some(TEST_COMMENT_ID_1));
|
||||
assert_eq!(replies[0].1, TEST_USER_ID_1);
|
||||
assert_eq!(replies[1].0, TEST_COMMENT_ID_2);
|
||||
assert_eq!(replies[1].0, Some(TEST_COMMENT_ID_2));
|
||||
assert_eq!(replies[1].1, TEST_USER_ID_2);
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
use crate::{CommentSlimView, CommentView};
|
||||
use lemmy_db_schema::newtypes::{
|
||||
CommentId,
|
||||
CommunityId,
|
||||
LanguageId,
|
||||
LocalUserId,
|
||||
PaginationCursor,
|
||||
PostId,
|
||||
};
|
||||
use lemmy_db_schema::newtypes::{CommentId, CommunityId, LanguageId, PaginationCursor, PostId};
|
||||
use lemmy_db_schema_file::enums::{CommentSortType, ListingType};
|
||||
use lemmy_db_views_vote::VoteView;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -19,7 +12,6 @@ use serde_with::skip_serializing_none;
|
|||
/// A comment response.
|
||||
pub struct CommentResponse {
|
||||
pub comment_view: CommentView,
|
||||
pub recipient_ids: Vec<LocalUserId>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_db_schema::{
|
|||
source::site::Site,
|
||||
CommunitySortType,
|
||||
};
|
||||
use lemmy_db_schema_file::enums::{CommunityVisibility, ListingType};
|
||||
use lemmy_db_schema_file::enums::{CommunityNotificationsMode, CommunityVisibility, ListingType};
|
||||
use lemmy_db_views_community_moderator::CommunityModeratorView;
|
||||
use lemmy_db_views_person::PersonView;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -364,3 +364,12 @@ pub struct FollowMultiCommunity {
|
|||
pub multi_community_id: MultiCommunityId,
|
||||
pub follow: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Change notification settings for a community
|
||||
pub struct UpdateCommunityNotifications {
|
||||
pub community_id: CommunityId,
|
||||
pub mode: CommunityNotificationsMode,
|
||||
}
|
||||
|
|
|
@ -1,901 +0,0 @@
|
|||
use crate::{
|
||||
CommentReplyView,
|
||||
InboxCombinedView,
|
||||
InboxCombinedViewInternal,
|
||||
PersonCommentMentionView,
|
||||
PersonPostMentionView,
|
||||
};
|
||||
use diesel::{
|
||||
dsl::not,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
SelectableHelper,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use i_love_jesus::SortDirection;
|
||||
use lemmy_db_schema::{
|
||||
aliases::{self},
|
||||
newtypes::{InstanceId, PaginationCursor, PersonId},
|
||||
source::combined::inbox::{inbox_combined_keys as key, InboxCombined},
|
||||
traits::{InternalToCombinedView, PaginationCursorBuilder},
|
||||
utils::{
|
||||
get_conn,
|
||||
limit_fetch,
|
||||
paginate,
|
||||
queries::{
|
||||
community_join,
|
||||
creator_community_actions_join,
|
||||
creator_home_instance_actions_join,
|
||||
creator_local_instance_actions_join,
|
||||
creator_local_user_admin_join,
|
||||
image_details_join,
|
||||
my_comment_actions_join,
|
||||
my_community_actions_join,
|
||||
my_instance_actions_person_join,
|
||||
my_local_user_admin_join,
|
||||
my_person_actions_join,
|
||||
my_post_actions_join,
|
||||
},
|
||||
DbPool,
|
||||
},
|
||||
InboxDataType,
|
||||
};
|
||||
use lemmy_db_schema_file::schema::{
|
||||
comment,
|
||||
comment_reply,
|
||||
inbox_combined,
|
||||
instance_actions,
|
||||
person,
|
||||
person_actions,
|
||||
person_comment_mention,
|
||||
person_post_mention,
|
||||
post,
|
||||
private_message,
|
||||
};
|
||||
use lemmy_db_views_private_message::PrivateMessageView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
impl InboxCombinedViewInternal {
|
||||
#[diesel::dsl::auto_type(no_type_alias)]
|
||||
fn joins(my_person_id: PersonId, local_instance_id: InstanceId) -> _ {
|
||||
let item_creator = person::id;
|
||||
let recipient_person = aliases::person1.field(person::id);
|
||||
|
||||
let item_creator_join = person::table.on(
|
||||
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 = aliases::person1.on(
|
||||
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::table.on(
|
||||
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 = post::table.on(
|
||||
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 = private_message::table.on(
|
||||
inbox_combined::private_message_id
|
||||
.eq(private_message::id.nullable())
|
||||
.and(not(private_message::deleted))
|
||||
.and(not(private_message::removed)),
|
||||
);
|
||||
|
||||
let my_community_actions_join: my_community_actions_join =
|
||||
my_community_actions_join(Some(my_person_id));
|
||||
let my_post_actions_join: my_post_actions_join = my_post_actions_join(Some(my_person_id));
|
||||
let my_comment_actions_join: my_comment_actions_join =
|
||||
my_comment_actions_join(Some(my_person_id));
|
||||
let my_local_user_admin_join: my_local_user_admin_join =
|
||||
my_local_user_admin_join(Some(my_person_id));
|
||||
let my_instance_actions_person_join: my_instance_actions_person_join =
|
||||
my_instance_actions_person_join(Some(my_person_id));
|
||||
let my_person_actions_join: my_person_actions_join = my_person_actions_join(Some(my_person_id));
|
||||
let creator_local_instance_actions_join: creator_local_instance_actions_join =
|
||||
creator_local_instance_actions_join(local_instance_id);
|
||||
|
||||
inbox_combined::table
|
||||
.left_join(comment_reply::table)
|
||||
.left_join(person_comment_mention::table)
|
||||
.left_join(person_post_mention::table)
|
||||
.left_join(private_message_join)
|
||||
.left_join(comment_join)
|
||||
.left_join(post_join)
|
||||
.left_join(community_join())
|
||||
.inner_join(item_creator_join)
|
||||
.inner_join(recipient_join)
|
||||
.left_join(image_details_join())
|
||||
.left_join(creator_community_actions_join())
|
||||
.left_join(my_local_user_admin_join)
|
||||
.left_join(creator_local_user_admin_join())
|
||||
.left_join(my_community_actions_join)
|
||||
.left_join(my_instance_actions_person_join)
|
||||
.left_join(creator_home_instance_actions_join())
|
||||
.left_join(creator_local_instance_actions_join)
|
||||
.left_join(my_post_actions_join)
|
||||
.left_join(my_person_actions_join)
|
||||
.left_join(my_comment_actions_join)
|
||||
}
|
||||
|
||||
/// Gets the number of unread mentions
|
||||
pub async fn get_unread_count(
|
||||
pool: &mut DbPool<'_>,
|
||||
my_person_id: PersonId,
|
||||
local_instance_id: InstanceId,
|
||||
show_bot_accounts: bool,
|
||||
) -> LemmyResult<i64> {
|
||||
use diesel::dsl::count;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
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 mut query = Self::joins(my_person_id, local_instance_id)
|
||||
// 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_at.is_null())
|
||||
.filter(instance_actions::blocked_at.is_null())
|
||||
.select(count(inbox_combined::id))
|
||||
.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
|
||||
.first::<i64>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl PaginationCursorBuilder for InboxCombinedView {
|
||||
type CursorData = InboxCombined;
|
||||
|
||||
fn to_cursor(&self) -> PaginationCursor {
|
||||
let (prefix, id) = match &self {
|
||||
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),
|
||||
};
|
||||
PaginationCursor::new_single(prefix, id)
|
||||
}
|
||||
|
||||
async fn from_cursor(
|
||||
cursor: &PaginationCursor,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<Self::CursorData> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let pids = cursor.prefixes_and_ids();
|
||||
let (prefix, id) = pids
|
||||
.as_slice()
|
||||
.first()
|
||||
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
|
||||
|
||||
let mut query = inbox_combined::table
|
||||
.select(Self::CursorData::as_select())
|
||||
.into_boxed();
|
||||
|
||||
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(LemmyErrorType::CouldntParsePaginationToken.into()),
|
||||
};
|
||||
let token = query.first(conn).await?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InboxCombinedQuery {
|
||||
pub type_: Option<InboxDataType>,
|
||||
pub unread_only: Option<bool>,
|
||||
pub show_bot_accounts: Option<bool>,
|
||||
pub cursor_data: Option<InboxCombined>,
|
||||
pub page_back: Option<bool>,
|
||||
pub limit: Option<i64>,
|
||||
pub no_limit: Option<bool>,
|
||||
}
|
||||
|
||||
impl InboxCombinedQuery {
|
||||
pub async fn list(
|
||||
self,
|
||||
pool: &mut DbPool<'_>,
|
||||
my_person_id: PersonId,
|
||||
local_instance_id: InstanceId,
|
||||
) -> LemmyResult<Vec<InboxCombinedView>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let item_creator = person::id;
|
||||
let recipient_person = aliases::person1.field(person::id);
|
||||
|
||||
let mut query = InboxCombinedViewInternal::joins(my_person_id, local_instance_id)
|
||||
.select(InboxCombinedViewInternal::as_select())
|
||||
.into_boxed();
|
||||
|
||||
if !self.no_limit.unwrap_or_default() {
|
||||
let limit = limit_fetch(self.limit)?;
|
||||
query = query.limit(limit);
|
||||
}
|
||||
|
||||
// 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_at.is_null())
|
||||
.filter(instance_actions::blocked_at.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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting by published
|
||||
let paginated_query = paginate(
|
||||
query,
|
||||
SortDirection::Desc,
|
||||
self.cursor_data,
|
||||
None,
|
||||
self.page_back,
|
||||
)
|
||||
.then_order_by(key::published_at)
|
||||
// Tie breaker
|
||||
.then_order_by(key::id);
|
||||
|
||||
let res = paginated_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(post), Some(community)) = (
|
||||
v.comment_reply,
|
||||
v.comment.clone(),
|
||||
v.post.clone(),
|
||||
v.community.clone(),
|
||||
) {
|
||||
Some(InboxCombinedView::CommentReply(CommentReplyView {
|
||||
comment_reply,
|
||||
comment,
|
||||
recipient: v.item_recipient,
|
||||
post,
|
||||
community,
|
||||
creator: v.item_creator,
|
||||
community_actions: v.community_actions,
|
||||
comment_actions: v.comment_actions,
|
||||
person_actions: v.person_actions,
|
||||
instance_actions: v.instance_actions,
|
||||
creator_is_admin: v.item_creator_is_admin,
|
||||
post_tags: v.post_tags,
|
||||
can_mod: v.can_mod,
|
||||
creator_banned: v.creator_banned,
|
||||
creator_banned_from_community: v.creator_banned_from_community,
|
||||
creator_is_moderator: v.creator_is_moderator,
|
||||
}))
|
||||
} else if let (Some(person_comment_mention), Some(comment), Some(post), Some(community)) = (
|
||||
v.person_comment_mention,
|
||||
v.comment,
|
||||
v.post.clone(),
|
||||
v.community.clone(),
|
||||
) {
|
||||
Some(InboxCombinedView::CommentMention(
|
||||
PersonCommentMentionView {
|
||||
person_comment_mention,
|
||||
comment,
|
||||
recipient: v.item_recipient,
|
||||
post,
|
||||
community,
|
||||
creator: v.item_creator,
|
||||
community_actions: v.community_actions,
|
||||
comment_actions: v.comment_actions,
|
||||
person_actions: v.person_actions,
|
||||
instance_actions: v.instance_actions,
|
||||
creator_is_admin: v.item_creator_is_admin,
|
||||
can_mod: v.can_mod,
|
||||
creator_banned: v.creator_banned,
|
||||
creator_banned_from_community: v.creator_banned_from_community,
|
||||
creator_is_moderator: v.creator_is_moderator,
|
||||
},
|
||||
))
|
||||
} else if let (Some(person_post_mention), Some(post), Some(community)) =
|
||||
(v.person_post_mention, v.post, v.community)
|
||||
{
|
||||
Some(InboxCombinedView::PostMention(PersonPostMentionView {
|
||||
person_post_mention,
|
||||
post,
|
||||
community,
|
||||
creator: v.item_creator,
|
||||
recipient: v.item_recipient,
|
||||
community_actions: v.community_actions,
|
||||
person_actions: v.person_actions,
|
||||
instance_actions: v.instance_actions,
|
||||
post_actions: v.post_actions,
|
||||
image_details: v.image_details,
|
||||
creator_is_admin: v.item_creator_is_admin,
|
||||
post_tags: v.post_tags,
|
||||
can_mod: v.can_mod,
|
||||
creator_banned: v.creator_banned,
|
||||
creator_banned_from_community: v.creator_banned_from_community,
|
||||
creator_is_moderator: v.creator_is_moderator,
|
||||
}))
|
||||
} 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::{impls::InboxCombinedQuery, InboxCombinedView, InboxCombinedViewInternal};
|
||||
use lemmy_db_schema::{
|
||||
assert_length,
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
comment_reply::{CommentReply, CommentReplyInsertForm, CommentReplyUpdateForm},
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::{Instance, InstanceActions, InstanceBlockForm},
|
||||
person::{Person, PersonActions, PersonBlockForm, PersonInsertForm, PersonUpdateForm},
|
||||
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_db_views_private_message::PrivateMessageView;
|
||||
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, data.instance.id, true)
|
||||
.await?;
|
||||
assert_eq!(1, timmy_unread_replies);
|
||||
|
||||
let timmy_inbox = InboxCombinedQuery::default()
|
||||
.list(pool, data.timmy.id, data.instance.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, data.instance.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, data.instance.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, data.instance.id, true)
|
||||
.await?;
|
||||
assert_eq!(2, sara_unread_mentions);
|
||||
|
||||
let sara_inbox = InboxCombinedQuery::default()
|
||||
.list(pool, data.sara.id, data.instance.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::new(data.sara.id, data.timmy.id);
|
||||
PersonActions::block(pool, &sara_blocks_timmy_form).await?;
|
||||
|
||||
let sara_unread_mentions_after_block =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.sara.id, data.instance.id, true)
|
||||
.await?;
|
||||
assert_eq!(1, sara_unread_mentions_after_block);
|
||||
|
||||
let sara_inbox_after_block = InboxCombinedQuery::default()
|
||||
.list(pool, data.sara.id, data.instance.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
|
||||
PersonActions::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, data.instance.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, data.instance.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, data.instance.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, data.instance.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, data.instance.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, data.instance.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, data.instance.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, data.instance.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::new(data.timmy.id, data.sara.id);
|
||||
|
||||
let inserted_block = PersonActions::block(pool, &timmy_blocks_sara_form).await?;
|
||||
|
||||
assert_eq!(
|
||||
(data.timmy.id, data.sara.id, true),
|
||||
(
|
||||
inserted_block.person_id,
|
||||
inserted_block.target_id,
|
||||
inserted_block.blocked_at.is_some()
|
||||
)
|
||||
);
|
||||
|
||||
let timmy_messages = map_to_pm(
|
||||
&InboxCombinedQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.timmy.id, data.instance.id)
|
||||
.await?,
|
||||
);
|
||||
|
||||
assert_length!(1, &timmy_messages);
|
||||
|
||||
let timmy_unread =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.timmy.id, data.instance.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::new(data.timmy.id, data.sara.instance_id);
|
||||
|
||||
let inserted_instance_block = InstanceActions::block(pool, &timmy_blocks_instance_form).await?;
|
||||
|
||||
assert_eq!(
|
||||
(data.timmy.id, data.sara.instance_id, true),
|
||||
(
|
||||
inserted_instance_block.person_id,
|
||||
inserted_instance_block.instance_id,
|
||||
inserted_instance_block.blocked_at.is_some()
|
||||
)
|
||||
);
|
||||
|
||||
let timmy_messages = map_to_pm(
|
||||
&InboxCombinedQuery {
|
||||
unread_only: Some(true),
|
||||
..Default::default()
|
||||
}
|
||||
.list(pool, data.timmy.id, data.instance.id)
|
||||
.await?,
|
||||
);
|
||||
|
||||
assert_length!(0, &timmy_messages);
|
||||
|
||||
let timmy_unread =
|
||||
InboxCombinedViewInternal::get_unread_count(pool, data.timmy.id, data.instance.id, false)
|
||||
.await?;
|
||||
assert_eq!(0, timmy_unread);
|
||||
|
||||
cleanup(data, pool).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
use lemmy_db_schema::{
|
||||
newtypes::PaginationCursor,
|
||||
source::{
|
||||
combined::inbox::InboxCombined,
|
||||
comment::{Comment, CommentActions},
|
||||
comment_reply::CommentReply,
|
||||
community::{Community, CommunityActions},
|
||||
images::ImageDetails,
|
||||
instance::InstanceActions,
|
||||
person::{Person, PersonActions},
|
||||
person_comment_mention::PersonCommentMention,
|
||||
person_post_mention::PersonPostMention,
|
||||
post::{Post, PostActions},
|
||||
private_message::PrivateMessage,
|
||||
tag::TagsView,
|
||||
},
|
||||
InboxDataType,
|
||||
};
|
||||
use lemmy_db_views_private_message::PrivateMessageView;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
use {
|
||||
diesel::{Queryable, Selectable},
|
||||
lemmy_db_schema::{
|
||||
utils::queries::{
|
||||
creator_banned,
|
||||
creator_is_admin,
|
||||
local_user_can_mod,
|
||||
person1_select,
|
||||
post_tags_fragment,
|
||||
},
|
||||
utils::queries::{creator_banned_from_community, creator_is_moderator},
|
||||
Person1AliasAllColumnsTuple,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod api;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod impls;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
/// A combined inbox view
|
||||
pub struct InboxCombinedViewInternal {
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub inbox_combined: InboxCombined,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub comment_reply: Option<CommentReply>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub person_comment_mention: Option<PersonCommentMention>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub person_post_mention: Option<PersonPostMention>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub private_message: Option<PrivateMessage>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub comment: Option<Comment>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub post: Option<Post>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub community: Option<Community>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub item_creator: Person,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression_type = Person1AliasAllColumnsTuple,
|
||||
select_expression = person1_select()
|
||||
)
|
||||
)]
|
||||
pub item_recipient: Person,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub image_details: Option<ImageDetails>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub community_actions: Option<CommunityActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub instance_actions: Option<InstanceActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub post_actions: Option<PostActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub person_actions: Option<PersonActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub comment_actions: Option<CommentActions>,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_is_admin()
|
||||
)
|
||||
)]
|
||||
pub item_creator_is_admin: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = post_tags_fragment()
|
||||
)
|
||||
)]
|
||||
pub post_tags: TagsView,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = local_user_can_mod()
|
||||
)
|
||||
)]
|
||||
pub can_mod: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_banned()
|
||||
)
|
||||
)]
|
||||
pub creator_banned: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_is_moderator()
|
||||
)
|
||||
)]
|
||||
pub creator_is_moderator: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_banned_from_community()
|
||||
)
|
||||
)]
|
||||
pub creator_banned_from_community: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", 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),
|
||||
}
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// A person comment mention view.
|
||||
pub struct PersonCommentMentionView {
|
||||
pub person_comment_mention: PersonCommentMention,
|
||||
pub recipient: Person,
|
||||
pub comment: Comment,
|
||||
pub creator: Person,
|
||||
pub post: Post,
|
||||
pub community: Community,
|
||||
pub community_actions: Option<CommunityActions>,
|
||||
pub comment_actions: Option<CommentActions>,
|
||||
pub person_actions: Option<PersonActions>,
|
||||
pub instance_actions: Option<InstanceActions>,
|
||||
pub creator_is_admin: bool,
|
||||
pub can_mod: bool,
|
||||
pub creator_banned: bool,
|
||||
pub creator_is_moderator: bool,
|
||||
pub creator_banned_from_community: bool,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// A person post mention view.
|
||||
pub struct PersonPostMentionView {
|
||||
pub person_post_mention: PersonPostMention,
|
||||
pub recipient: Person,
|
||||
pub post: Post,
|
||||
pub creator: Person,
|
||||
pub community: Community,
|
||||
pub image_details: Option<ImageDetails>,
|
||||
pub community_actions: Option<CommunityActions>,
|
||||
pub person_actions: Option<PersonActions>,
|
||||
pub post_actions: Option<PostActions>,
|
||||
pub instance_actions: Option<InstanceActions>,
|
||||
pub post_tags: TagsView,
|
||||
pub creator_is_admin: bool,
|
||||
pub can_mod: bool,
|
||||
pub creator_banned: bool,
|
||||
pub creator_is_moderator: bool,
|
||||
pub creator_banned_from_community: bool,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// A comment reply view.
|
||||
pub struct CommentReplyView {
|
||||
pub comment_reply: CommentReply,
|
||||
pub recipient: Person,
|
||||
pub comment: Comment,
|
||||
pub creator: Person,
|
||||
pub post: Post,
|
||||
pub community: Community,
|
||||
pub community_actions: Option<CommunityActions>,
|
||||
pub comment_actions: Option<CommentActions>,
|
||||
pub person_actions: Option<PersonActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
pub instance_actions: Option<InstanceActions>,
|
||||
pub creator_is_admin: bool,
|
||||
pub post_tags: TagsView,
|
||||
pub can_mod: bool,
|
||||
pub creator_banned: bool,
|
||||
pub creator_is_moderator: bool,
|
||||
pub creator_banned_from_community: bool,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Get your inbox (replies, comment mentions, post mentions, and messages)
|
||||
pub struct ListInbox {
|
||||
pub type_: Option<InboxDataType>,
|
||||
pub unread_only: Option<bool>,
|
||||
pub page_cursor: Option<PaginationCursor>,
|
||||
pub page_back: Option<bool>,
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Get your inbox (replies, comment mentions, post mentions, and messages)
|
||||
pub struct ListInboxResponse {
|
||||
pub inbox: Vec<InboxCombinedView>,
|
||||
/// the pagination cursor to use to fetch the next page
|
||||
pub next_page: Option<PaginationCursor>,
|
||||
pub prev_page: Option<PaginationCursor>,
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "lemmy_db_views_inbox_combined"
|
||||
name = "lemmy_db_views_notification"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
|
@ -23,19 +23,16 @@ full = [
|
|||
"i-love-jesus",
|
||||
"lemmy_db_schema/full",
|
||||
"lemmy_db_schema_file/full",
|
||||
"lemmy_db_views_private_message/full",
|
||||
]
|
||||
ts-rs = [
|
||||
"dep:ts-rs",
|
||||
"lemmy_db_schema/ts-rs",
|
||||
"lemmy_db_views_private_message/ts-rs",
|
||||
]
|
||||
ts-rs = ["dep:ts-rs", "lemmy_db_schema/ts-rs"]
|
||||
|
||||
[dependencies]
|
||||
lemmy_db_views_private_message = { workspace = true }
|
||||
lemmy_db_schema = { workspace = true }
|
||||
lemmy_utils = { workspace = true, optional = true }
|
||||
lemmy_db_schema_file = { workspace = true }
|
||||
lemmy_db_views_private_message = { workspace = true }
|
||||
lemmy_db_views_post = { workspace = true }
|
||||
lemmy_db_views_comment = { workspace = true }
|
||||
diesel = { workspace = true, optional = true }
|
||||
diesel-async = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
|
@ -44,6 +41,3 @@ i-love-jesus = { workspace = true, optional = true }
|
|||
serde_with = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
serial_test = { workspace = true }
|
||||
tokio = { workspace = true }
|
|
@ -1,9 +1,4 @@
|
|||
use lemmy_db_schema::newtypes::{
|
||||
CommentReplyId,
|
||||
PersonCommentMentionId,
|
||||
PersonPostMentionId,
|
||||
PrivateMessageId,
|
||||
};
|
||||
use lemmy_db_schema::newtypes::{NotificationId, PrivateMessageId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -26,26 +21,8 @@ pub struct GetUnreadRegistrationApplicationCountResponse {
|
|||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Mark a comment reply as read.
|
||||
pub struct MarkCommentReplyAsRead {
|
||||
pub comment_reply_id: CommentReplyId,
|
||||
pub read: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Mark a person mention as read.
|
||||
pub struct MarkPersonCommentMentionAsRead {
|
||||
pub person_comment_mention_id: PersonCommentMentionId,
|
||||
pub read: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Mark a person mention as read.
|
||||
pub struct MarkPersonPostMentionAsRead {
|
||||
pub person_post_mention_id: PersonPostMentionId,
|
||||
pub struct MarkNotificationAsRead {
|
||||
pub notification_id: NotificationId,
|
||||
pub read: bool,
|
||||
}
|
||||
|
337
crates/db_views/notification/src/impls.rs
Normal file
337
crates/db_views/notification/src/impls.rs
Normal file
|
@ -0,0 +1,337 @@
|
|||
use crate::{CommentView, NotificationData, NotificationView, NotificationViewInternal};
|
||||
use diesel::{
|
||||
dsl::not,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
SelectableHelper,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use i_love_jesus::SortDirection;
|
||||
use lemmy_db_schema::{
|
||||
aliases,
|
||||
newtypes::PaginationCursor,
|
||||
source::{
|
||||
notification::{notification_keys, Notification},
|
||||
person::Person,
|
||||
},
|
||||
traits::PaginationCursorBuilder,
|
||||
utils::{
|
||||
get_conn,
|
||||
limit_fetch,
|
||||
paginate,
|
||||
queries::{
|
||||
community_join,
|
||||
creator_community_actions_join,
|
||||
creator_home_instance_actions_join,
|
||||
creator_local_instance_actions_join,
|
||||
creator_local_user_admin_join,
|
||||
image_details_join,
|
||||
my_comment_actions_join,
|
||||
my_community_actions_join,
|
||||
my_instance_actions_person_join,
|
||||
my_local_user_admin_join,
|
||||
my_person_actions_join,
|
||||
my_post_actions_join,
|
||||
},
|
||||
DbPool,
|
||||
},
|
||||
NotificationDataType,
|
||||
};
|
||||
use lemmy_db_schema_file::{
|
||||
enums::NotificationTypes,
|
||||
schema::{
|
||||
comment,
|
||||
instance_actions,
|
||||
notification,
|
||||
person,
|
||||
person_actions,
|
||||
post,
|
||||
private_message,
|
||||
},
|
||||
};
|
||||
use lemmy_db_views_post::PostView;
|
||||
use lemmy_db_views_private_message::PrivateMessageView;
|
||||
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
|
||||
|
||||
impl NotificationView {
|
||||
#[diesel::dsl::auto_type(no_type_alias)]
|
||||
fn joins(my_person: &Person) -> _ {
|
||||
let item_creator = person::id;
|
||||
let recipient_person = aliases::person1.field(person::id);
|
||||
|
||||
let item_creator_join = person::table.on(
|
||||
comment::creator_id
|
||||
.eq(item_creator)
|
||||
.or(
|
||||
notification::post_id
|
||||
.is_not_null()
|
||||
.and(post::creator_id.eq(item_creator)),
|
||||
)
|
||||
.or(private_message::creator_id.eq(item_creator)),
|
||||
);
|
||||
|
||||
let recipient_join = aliases::person1.on(notification::recipient_id.eq(recipient_person));
|
||||
|
||||
let comment_join = comment::table.on(
|
||||
notification::comment_id
|
||||
.eq(comment::id.nullable())
|
||||
// Filter out the deleted / removed
|
||||
.and(not(comment::deleted))
|
||||
.and(not(comment::removed)),
|
||||
);
|
||||
|
||||
let post_join = post::table.on(
|
||||
notification::post_id
|
||||
.eq(post::id.nullable())
|
||||
.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 = private_message::table.on(
|
||||
notification::private_message_id
|
||||
.eq(private_message::id.nullable())
|
||||
.and(not(private_message::deleted))
|
||||
.and(not(private_message::removed)),
|
||||
);
|
||||
|
||||
let my_community_actions_join: my_community_actions_join =
|
||||
my_community_actions_join(Some(my_person.id));
|
||||
let my_post_actions_join: my_post_actions_join = my_post_actions_join(Some(my_person.id));
|
||||
let my_comment_actions_join: my_comment_actions_join =
|
||||
my_comment_actions_join(Some(my_person.id));
|
||||
let my_local_user_admin_join: my_local_user_admin_join =
|
||||
my_local_user_admin_join(Some(my_person.id));
|
||||
let my_instance_actions_person_join: my_instance_actions_person_join =
|
||||
my_instance_actions_person_join(Some(my_person.id));
|
||||
let my_person_actions_join: my_person_actions_join = my_person_actions_join(Some(my_person.id));
|
||||
let creator_local_instance_actions_join: creator_local_instance_actions_join =
|
||||
creator_local_instance_actions_join(my_person.instance_id);
|
||||
|
||||
notification::table
|
||||
.left_join(private_message_join)
|
||||
.left_join(comment_join)
|
||||
.left_join(post_join)
|
||||
.left_join(community_join())
|
||||
.inner_join(item_creator_join)
|
||||
.inner_join(recipient_join)
|
||||
.left_join(image_details_join())
|
||||
.left_join(creator_community_actions_join())
|
||||
.left_join(my_local_user_admin_join)
|
||||
.left_join(creator_local_user_admin_join())
|
||||
.left_join(my_community_actions_join)
|
||||
.left_join(my_instance_actions_person_join)
|
||||
.left_join(creator_home_instance_actions_join())
|
||||
.left_join(creator_local_instance_actions_join)
|
||||
.left_join(my_post_actions_join)
|
||||
.left_join(my_person_actions_join)
|
||||
.left_join(my_comment_actions_join)
|
||||
}
|
||||
|
||||
/// Gets the number of unread mentions
|
||||
pub async fn get_unread_count(
|
||||
pool: &mut DbPool<'_>,
|
||||
my_person: &Person,
|
||||
show_bot_accounts: bool,
|
||||
) -> LemmyResult<i64> {
|
||||
use diesel::dsl::count;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let unread_filter = notification::read.eq(false);
|
||||
|
||||
let mut query = Self::joins(my_person)
|
||||
// Filter for your user
|
||||
.filter(notification::recipient_id.eq(my_person.id))
|
||||
// Filter unreads
|
||||
.filter(unread_filter)
|
||||
// Don't count replies from blocked users
|
||||
.filter(person_actions::blocked_at.is_null())
|
||||
.filter(instance_actions::blocked_at.is_null())
|
||||
.select(count(notification::id))
|
||||
.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
|
||||
.first::<i64>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl PaginationCursorBuilder for NotificationView {
|
||||
type CursorData = Notification;
|
||||
|
||||
fn to_cursor(&self) -> PaginationCursor {
|
||||
PaginationCursor(self.notification.id.0.to_string())
|
||||
}
|
||||
|
||||
async fn from_cursor(
|
||||
cursor: &PaginationCursor,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<Self::CursorData> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let id: i32 = cursor.0.parse()?;
|
||||
let query = notification::table
|
||||
.select(Self::CursorData::as_select())
|
||||
.filter(notification::id.eq(id));
|
||||
let token = query.first(conn).await?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NotificationQuery {
|
||||
pub type_: Option<NotificationDataType>,
|
||||
pub unread_only: Option<bool>,
|
||||
pub show_bot_accounts: Option<bool>,
|
||||
pub cursor_data: Option<Notification>,
|
||||
pub page_back: Option<bool>,
|
||||
pub limit: Option<i64>,
|
||||
pub no_limit: Option<bool>,
|
||||
}
|
||||
|
||||
impl NotificationQuery {
|
||||
pub async fn list(
|
||||
self,
|
||||
pool: &mut DbPool<'_>,
|
||||
my_person: &Person,
|
||||
) -> LemmyResult<Vec<NotificationView>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let mut query = NotificationView::joins(my_person)
|
||||
.select(NotificationViewInternal::as_select())
|
||||
.into_boxed();
|
||||
|
||||
if !self.no_limit.unwrap_or_default() {
|
||||
let limit = limit_fetch(self.limit)?;
|
||||
query = query.limit(limit);
|
||||
}
|
||||
|
||||
// Filters
|
||||
if self.unread_only.unwrap_or_default() {
|
||||
query = query
|
||||
// The recipient filter (IE only show replies to you)
|
||||
.filter(notification::recipient_id.eq(my_person.id))
|
||||
.filter(notification::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(
|
||||
notification::recipient_id.eq(my_person.id).or(
|
||||
notification::private_message_id.is_not_null().and(
|
||||
notification::recipient_id
|
||||
.eq(my_person.id)
|
||||
.or(person::id.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_at.is_null())
|
||||
.filter(instance_actions::blocked_at.is_null());
|
||||
|
||||
if let Some(type_) = self.type_ {
|
||||
query = match type_ {
|
||||
NotificationDataType::All => query,
|
||||
NotificationDataType::Reply => {
|
||||
query.filter(notification::kind.eq(NotificationTypes::Reply))
|
||||
}
|
||||
NotificationDataType::Mention => {
|
||||
query.filter(notification::kind.eq(NotificationTypes::Mention))
|
||||
}
|
||||
NotificationDataType::PrivateMessage => {
|
||||
query.filter(notification::kind.eq(NotificationTypes::PrivateMessage))
|
||||
}
|
||||
NotificationDataType::Subscribed => {
|
||||
query.filter(notification::kind.eq(NotificationTypes::Subscribed))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sorting by published
|
||||
let paginated_query = paginate(
|
||||
query,
|
||||
SortDirection::Desc,
|
||||
self.cursor_data,
|
||||
None,
|
||||
self.page_back,
|
||||
)
|
||||
.then_order_by(notification_keys::published_at)
|
||||
// Tie breaker
|
||||
.then_order_by(notification_keys::id);
|
||||
|
||||
let res = paginated_query
|
||||
.load::<NotificationViewInternal>(conn)
|
||||
.await?;
|
||||
|
||||
Ok(res.into_iter().filter_map(map_to_enum).collect())
|
||||
}
|
||||
}
|
||||
|
||||
fn map_to_enum(v: NotificationViewInternal) -> Option<NotificationView> {
|
||||
let data = if let (Some(comment), Some(post), Some(community)) =
|
||||
(v.comment, v.post.clone(), v.community.clone())
|
||||
{
|
||||
NotificationData::Comment(CommentView {
|
||||
comment,
|
||||
post,
|
||||
community,
|
||||
creator: v.creator,
|
||||
community_actions: v.community_actions,
|
||||
instance_actions: v.instance_actions,
|
||||
person_actions: v.person_actions,
|
||||
comment_actions: v.comment_actions,
|
||||
creator_is_admin: v.creator_is_admin,
|
||||
post_tags: v.post_tags,
|
||||
can_mod: v.can_mod,
|
||||
creator_banned: v.creator_banned,
|
||||
creator_is_moderator: v.creator_is_moderator,
|
||||
creator_banned_from_community: v.creator_banned_from_community,
|
||||
})
|
||||
} else if let (Some(post), Some(community)) = (v.post, v.community) {
|
||||
NotificationData::Post(PostView {
|
||||
post,
|
||||
community,
|
||||
creator: v.creator,
|
||||
image_details: v.image_details,
|
||||
community_actions: v.community_actions,
|
||||
instance_actions: v.instance_actions,
|
||||
post_actions: v.post_actions,
|
||||
person_actions: v.person_actions,
|
||||
creator_is_admin: v.creator_is_admin,
|
||||
tags: v.post_tags,
|
||||
can_mod: v.can_mod,
|
||||
creator_banned: v.creator_banned,
|
||||
creator_is_moderator: v.creator_is_moderator,
|
||||
creator_banned_from_community: v.creator_banned_from_community,
|
||||
})
|
||||
} else if let Some(private_message) = v.private_message {
|
||||
NotificationData::PrivateMessage(PrivateMessageView {
|
||||
private_message,
|
||||
creator: v.creator,
|
||||
recipient: v.recipient,
|
||||
})
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(NotificationView {
|
||||
notification: v.notification,
|
||||
data,
|
||||
})
|
||||
}
|
149
crates/db_views/notification/src/lib.rs
Normal file
149
crates/db_views/notification/src/lib.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
use lemmy_db_schema::{
|
||||
newtypes::PaginationCursor,
|
||||
source::{
|
||||
comment::{Comment, CommentActions},
|
||||
community::{Community, CommunityActions},
|
||||
images::ImageDetails,
|
||||
instance::InstanceActions,
|
||||
notification::Notification,
|
||||
person::{Person, PersonActions},
|
||||
post::{Post, PostActions},
|
||||
private_message::PrivateMessage,
|
||||
tag::TagsView,
|
||||
},
|
||||
NotificationDataType,
|
||||
};
|
||||
use lemmy_db_views_comment::CommentView;
|
||||
use lemmy_db_views_post::PostView;
|
||||
use lemmy_db_views_private_message::PrivateMessageView;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
#[cfg(feature = "full")]
|
||||
use {
|
||||
diesel::{Queryable, Selectable},
|
||||
lemmy_db_schema::{
|
||||
utils::queries::person1_select,
|
||||
utils::queries::{creator_banned, creator_is_admin, local_user_can_mod, post_tags_fragment},
|
||||
utils::queries::{creator_banned_from_community, creator_is_moderator},
|
||||
Person1AliasAllColumnsTuple,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod api;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod impls;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Selectable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
struct NotificationViewInternal {
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
notification: Notification,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
private_message: Option<PrivateMessage>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
comment: Option<Comment>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
post: Option<Post>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
community: Option<Community>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
creator: Person,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression_type = Person1AliasAllColumnsTuple,
|
||||
select_expression = person1_select()
|
||||
)
|
||||
)]
|
||||
recipient: Person,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
image_details: Option<ImageDetails>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
community_actions: Option<CommunityActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
instance_actions: Option<InstanceActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
post_actions: Option<PostActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
person_actions: Option<PersonActions>,
|
||||
#[cfg_attr(feature = "full", diesel(embed))]
|
||||
comment_actions: Option<CommentActions>,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_is_admin()
|
||||
)
|
||||
)]
|
||||
creator_is_admin: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = post_tags_fragment()
|
||||
)
|
||||
)]
|
||||
post_tags: TagsView,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = local_user_can_mod()
|
||||
)
|
||||
)]
|
||||
can_mod: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_banned()
|
||||
)
|
||||
)]
|
||||
creator_banned: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_is_moderator()
|
||||
)
|
||||
)]
|
||||
creator_is_moderator: bool,
|
||||
#[cfg_attr(feature = "full",
|
||||
diesel(
|
||||
select_expression = creator_banned_from_community()
|
||||
)
|
||||
)]
|
||||
creator_banned_from_community: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
pub struct NotificationView {
|
||||
pub notification: Notification,
|
||||
pub data: NotificationData,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||
#[serde(tag = "type_")]
|
||||
pub enum NotificationData {
|
||||
Comment(CommentView),
|
||||
Post(PostView),
|
||||
PrivateMessage(PrivateMessageView),
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Get your inbox (replies, comment mentions, post mentions, and messages)
|
||||
pub struct ListNotifications {
|
||||
pub type_: Option<NotificationDataType>,
|
||||
pub unread_only: Option<bool>,
|
||||
pub page_cursor: Option<PaginationCursor>,
|
||||
pub page_back: Option<bool>,
|
||||
pub limit: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Get your inbox (replies, comment mentions, post mentions, and messages)
|
||||
pub struct ListNotificationsResponse {
|
||||
pub notifications: Vec<NotificationView>,
|
||||
/// the pagination cursor to use to fetch the next page
|
||||
pub next_page: Option<PaginationCursor>,
|
||||
pub prev_page: Option<PaginationCursor>,
|
||||
}
|
|
@ -12,7 +12,7 @@ use lemmy_db_schema::{
|
|||
},
|
||||
PostFeatureType,
|
||||
};
|
||||
use lemmy_db_schema_file::enums::{ListingType, PostSortType};
|
||||
use lemmy_db_schema_file::enums::{ListingType, PostNotificationsMode, PostSortType};
|
||||
use lemmy_db_views_community::CommunityView;
|
||||
use lemmy_db_views_vote::VoteView;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -93,6 +93,15 @@ pub struct FeaturePost {
|
|||
pub feature_type: PostFeatureType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Change notification settings for a post
|
||||
pub struct UpdatePostNotifications {
|
||||
pub post_id: PostId,
|
||||
pub mode: PostNotificationsMode,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||
|
|
|
@ -745,7 +745,7 @@ pub struct UserSettingsBackup {
|
|||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||
/// Your exported data.
|
||||
pub struct ExportDataResponse {
|
||||
pub inbox: Vec<PostOrCommentOrPrivateMessage>,
|
||||
pub notifications: Vec<PostOrCommentOrPrivateMessage>,
|
||||
pub content: Vec<PostOrCommentOrPrivateMessage>,
|
||||
pub read_posts: Vec<Url>,
|
||||
pub liked: Vec<Url>,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{inbox_link, send_email, user_language};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::DbUrl,
|
||||
source::{comment::Comment, person::Person, post::Post},
|
||||
source::{comment::Comment, community::Community, person::Person, post::Post},
|
||||
};
|
||||
use lemmy_db_views_local_user::LocalUserView;
|
||||
use lemmy_utils::{
|
||||
|
@ -30,69 +30,96 @@ pub async fn send_mention_email(
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn send_comment_reply_email(
|
||||
parent_user_view: &LocalUserView,
|
||||
comment: &Comment,
|
||||
person: &Person,
|
||||
parent_comment: &Comment,
|
||||
pub async fn send_post_subscribed_email(
|
||||
user_view: &LocalUserView,
|
||||
post: &Post,
|
||||
comment: &Comment,
|
||||
link: DbUrl,
|
||||
settings: &Settings,
|
||||
) -> LemmyResult<()> {
|
||||
) {
|
||||
let inbox_link = inbox_link(settings);
|
||||
let lang = user_language(parent_user_view);
|
||||
let lang = user_language(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(
|
||||
comment.local_url(settings)?,
|
||||
&content,
|
||||
&inbox_link,
|
||||
&parent_comment.content,
|
||||
&post.name,
|
||||
&person.name,
|
||||
),
|
||||
user_view,
|
||||
&lang.notification_post_subscribed_subject(&post.name),
|
||||
&lang.notification_post_subscribed_body(&content, &link, inbox_link),
|
||||
settings,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_post_reply_email(
|
||||
pub async fn send_community_subscribed_email(
|
||||
user_view: &LocalUserView,
|
||||
post: &Post,
|
||||
community: &Community,
|
||||
link: DbUrl,
|
||||
settings: &Settings,
|
||||
) {
|
||||
let inbox_link = inbox_link(settings);
|
||||
let lang = user_language(user_view);
|
||||
let content = post
|
||||
.body
|
||||
.as_ref()
|
||||
.map(|b| markdown_to_html(b))
|
||||
.unwrap_or_default();
|
||||
send_email_to_user(
|
||||
user_view,
|
||||
&lang.notification_community_subscribed_subject(&post.name, &community.title),
|
||||
&lang.notification_community_subscribed_body(&content, &link, inbox_link),
|
||||
settings,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn send_reply_email(
|
||||
parent_user_view: &LocalUserView,
|
||||
comment: &Comment,
|
||||
person: &Person,
|
||||
parent_comment: &Option<Comment>,
|
||||
post: &Post,
|
||||
settings: &Settings,
|
||||
) -> LemmyResult<()> {
|
||||
let inbox_link = inbox_link(settings);
|
||||
let lang = user_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(
|
||||
comment.local_url(settings)?,
|
||||
&content,
|
||||
&inbox_link,
|
||||
&post.name,
|
||||
&person.name,
|
||||
),
|
||||
settings,
|
||||
)
|
||||
.await;
|
||||
let (subject, body) = if let Some(parent_comment) = parent_comment {
|
||||
(
|
||||
lang.notification_comment_reply_subject(&person.name),
|
||||
lang.notification_comment_reply_body(
|
||||
comment.local_url(settings)?,
|
||||
&content,
|
||||
&inbox_link,
|
||||
&parent_comment.content,
|
||||
&post.name,
|
||||
&person.name,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
lang.notification_post_reply_subject(&person.name),
|
||||
lang.notification_post_reply_body(
|
||||
comment.local_url(settings)?,
|
||||
&content,
|
||||
&inbox_link,
|
||||
&post.name,
|
||||
&person.name,
|
||||
),
|
||||
)
|
||||
};
|
||||
send_email_to_user(parent_user_view, &subject, &body, settings).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send_private_message_email(
|
||||
sender: &LocalUserView,
|
||||
sender: &Person,
|
||||
local_recipient: &LocalUserView,
|
||||
content: &str,
|
||||
settings: &Settings,
|
||||
) {
|
||||
let inbox_link = inbox_link(settings);
|
||||
let lang = user_language(local_recipient);
|
||||
let sender_name = &sender.person.name;
|
||||
let sender_name = &sender.name;
|
||||
let content = markdown_to_html(content);
|
||||
send_email_to_user(
|
||||
local_recipient,
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 7debe41492de3f04403c9c78ced9697be199e394
|
||||
Subproject commit 72c9cc342b339779cd6d61a8e3349aeff5cad2ff
|
|
@ -25,7 +25,7 @@ lemmy_db_views_community = { workspace = true, features = ["full"] }
|
|||
lemmy_db_views_post = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_local_image = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_local_user = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_inbox_combined = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_notification = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_modlog_combined = { workspace = true, features = ["full"] }
|
||||
lemmy_db_views_person_content_combined = { workspace = true, features = [
|
||||
"full",
|
||||
|
|
|
@ -11,8 +11,8 @@ use lemmy_db_schema::{
|
|||
PersonContentType,
|
||||
};
|
||||
use lemmy_db_schema_file::enums::{ListingType, PostSortType};
|
||||
use lemmy_db_views_inbox_combined::{impls::InboxCombinedQuery, InboxCombinedView};
|
||||
use lemmy_db_views_modlog_combined::{impls::ModlogCombinedQuery, ModlogCombinedView};
|
||||
use lemmy_db_views_notification::{impls::NotificationQuery, NotificationData, NotificationView};
|
||||
use lemmy_db_views_person_content_combined::impls::PersonContentCombinedQuery;
|
||||
use lemmy_db_views_post::{impls::PostQuery, PostView};
|
||||
use lemmy_db_views_site::SiteView;
|
||||
|
@ -321,22 +321,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_instance_id = site_view.site.instance_id;
|
||||
let local_user = local_user_view_from_jwt(jwt, context).await?;
|
||||
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 inbox = InboxCombinedQuery {
|
||||
let notifications = NotificationQuery {
|
||||
show_bot_accounts,
|
||||
..Default::default()
|
||||
}
|
||||
.list(&mut context.pool(), my_person_id, local_instance_id)
|
||||
.list(&mut context.pool(), &local_user.person)
|
||||
.await?;
|
||||
|
||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
||||
let items = create_reply_and_mention_items(inbox, context)?;
|
||||
let items = create_reply_and_mention_items(notifications, context)?;
|
||||
|
||||
let mut channel = Channel {
|
||||
namespaces: RSS_NAMESPACE.clone(),
|
||||
|
@ -387,49 +385,39 @@ async fn get_feed_modlog(context: &LemmyContext, jwt: &str) -> LemmyResult<Chann
|
|||
}
|
||||
|
||||
fn create_reply_and_mention_items(
|
||||
inbox: Vec<InboxCombinedView>,
|
||||
inbox: Vec<NotificationView>,
|
||||
context: &LemmyContext,
|
||||
) -> LemmyResult<Vec<Item>> {
|
||||
let reply_items: Vec<Item> = inbox
|
||||
.iter()
|
||||
.map(|r| match r {
|
||||
InboxCombinedView::CommentReply(v) => {
|
||||
let reply_url = v.comment.local_url(context.settings())?;
|
||||
.map(|v| match &v.data {
|
||||
NotificationData::Post(post) => {
|
||||
let mention_url = post.post.local_url(context.settings())?;
|
||||
build_item(
|
||||
&v.creator,
|
||||
&v.comment.published_at,
|
||||
&post.creator,
|
||||
&post.post.published_at,
|
||||
mention_url.as_str(),
|
||||
&post.post.body.clone().unwrap_or_default(),
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
NotificationData::Comment(comment) => {
|
||||
let reply_url = comment.comment.local_url(context.settings())?;
|
||||
build_item(
|
||||
&comment.creator,
|
||||
&comment.comment.published_at,
|
||||
reply_url.as_str(),
|
||||
&v.comment.content,
|
||||
&comment.comment.content,
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
InboxCombinedView::CommentMention(v) => {
|
||||
let mention_url = v.comment.local_url(context.settings())?;
|
||||
build_item(
|
||||
&v.creator,
|
||||
&v.comment.published_at,
|
||||
mention_url.as_str(),
|
||||
&v.comment.content,
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
InboxCombinedView::PostMention(v) => {
|
||||
let mention_url = v.post.local_url(context.settings())?;
|
||||
build_item(
|
||||
&v.creator,
|
||||
&v.post.published_at,
|
||||
mention_url.as_str(),
|
||||
&v.post.body.clone().unwrap_or_default(),
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
InboxCombinedView::PrivateMessage(v) => {
|
||||
NotificationData::PrivateMessage(pm) => {
|
||||
let inbox_url = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||
build_item(
|
||||
&v.creator,
|
||||
&v.private_message.published_at,
|
||||
&pm.creator,
|
||||
&pm.private_message.published_at,
|
||||
&inbox_url,
|
||||
&v.private_message.content,
|
||||
&pm.private_message.content,
|
||||
context.settings(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -106,10 +106,8 @@ pub enum LemmyErrorType {
|
|||
CouldntUpdateReadComments,
|
||||
CouldntHidePost,
|
||||
CouldntUpdateCommunity,
|
||||
CouldntCreatePersonCommentMention,
|
||||
CouldntUpdatePersonCommentMention,
|
||||
CouldntCreatePersonPostMention,
|
||||
CouldntUpdatePersonPostMention,
|
||||
CouldntCreateNotification,
|
||||
CouldntUpdateNotification,
|
||||
CouldntCreatePost,
|
||||
CouldntCreatePrivateMessage,
|
||||
CouldntUpdatePrivateMessage,
|
||||
|
@ -160,9 +158,6 @@ pub enum LemmyErrorType {
|
|||
CouldntParsePaginationToken,
|
||||
PluginError(String),
|
||||
InvalidFetchLimit,
|
||||
CouldntCreateCommentReply,
|
||||
CouldntUpdateCommentReply,
|
||||
CouldntMarkCommentReplyAsRead,
|
||||
CouldntCreateEmoji,
|
||||
CouldntUpdateEmoji,
|
||||
CouldntCreatePerson,
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
CREATE TABLE person_post_mention (
|
||||
id serial PRIMARY KEY,
|
||||
recipient_id int REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
post_id int REFERENCES post (id) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
read bool NOT NULL DEFAULT FALSE,
|
||||
published_at timestamptz DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE person_mention (
|
||||
id serial PRIMARY KEY,
|
||||
recipient_id int REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
comment_id int REFERENCES comment (id) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
read bool NOT NULL DEFAULT FALSE,
|
||||
published_at timestamptz DEFAULT now() NOT NULL,
|
||||
UNIQUE (recipient_id, comment_id)
|
||||
);
|
||||
|
||||
ALTER TABLE person_mention RENAME TO person_comment_mention;
|
||||
|
||||
CREATE TABLE comment_reply (
|
||||
id serial PRIMARY KEY,
|
||||
recipient_id int REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
comment_id int REFERENCES comment (id) ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||
read bool NOT NULL DEFAULT FALSE,
|
||||
published_at timestamptz DEFAULT now() NOT NULL,
|
||||
UNIQUE (recipient_id, comment_id)
|
||||
);
|
||||
|
||||
CREATE TABLE inbox_combined (
|
||||
id serial PRIMARY KEY,
|
||||
comment_reply_id int REFERENCES comment_reply (id) ON UPDATE CASCADE ON DELETE CASCADE UNIQUE,
|
||||
person_comment_mention_id int REFERENCES person_comment_mention (id) ON UPDATE CASCADE ON DELETE CASCADE UNIQUE,
|
||||
person_post_mention_id int REFERENCES person_post_mention (id) ON UPDATE CASCADE ON DELETE CASCADE UNIQUE,
|
||||
private_message_id int REFERENCES private_message (id) ON UPDATE CASCADE ON DELETE CASCADE UNIQUE,
|
||||
published_at timestamptz NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE private_message
|
||||
ADD COLUMN read bool DEFAULT FALSE NOT NULL;
|
||||
|
||||
-- copy back data to person_post_mention table
|
||||
INSERT INTO person_post_mention (recipient_id, post_id, read, published_at)
|
||||
SELECT
|
||||
recipient_id,
|
||||
post_id,
|
||||
read,
|
||||
published_at
|
||||
FROM
|
||||
notification
|
||||
WHERE
|
||||
kind = 'Mention'
|
||||
AND post_id IS NOT NULL;
|
||||
|
||||
INSERT INTO inbox_combined (person_post_mention_id, published_at)
|
||||
SELECT
|
||||
id,
|
||||
published_at
|
||||
FROM
|
||||
person_post_mention;
|
||||
|
||||
-- copy back data to person_comment_mention table
|
||||
INSERT INTO person_comment_mention (recipient_id, comment_id, read, published_at)
|
||||
SELECT
|
||||
recipient_id,
|
||||
comment_id,
|
||||
read,
|
||||
published_at
|
||||
FROM
|
||||
notification
|
||||
WHERE
|
||||
kind = 'Mention'
|
||||
AND comment_id IS NOT NULL;
|
||||
|
||||
-- copy back data to person_comment_mention table
|
||||
UPDATE
|
||||
private_message p
|
||||
SET
|
||||
read = n.read
|
||||
FROM
|
||||
notification n
|
||||
WHERE
|
||||
p.id = n.private_message_id;
|
||||
|
||||
INSERT INTO inbox_combined (person_comment_mention_id, published_at)
|
||||
SELECT
|
||||
id,
|
||||
published_at
|
||||
FROM
|
||||
person_comment_mention;
|
||||
|
||||
-- copy back data to comment_reply table
|
||||
INSERT INTO comment_reply (recipient_id, comment_id, read, published_at)
|
||||
SELECT
|
||||
recipient_id,
|
||||
comment_id,
|
||||
read,
|
||||
published_at
|
||||
FROM
|
||||
notification
|
||||
WHERE
|
||||
kind = 'Reply'
|
||||
AND comment_id IS NOT NULL;
|
||||
|
||||
INSERT INTO inbox_combined (comment_reply_id, published_at)
|
||||
SELECT
|
||||
id,
|
||||
published_at
|
||||
FROM
|
||||
comment_reply;
|
||||
|
||||
ALTER TABLE ONLY person_post_mention
|
||||
ADD CONSTRAINT person_post_mention_unique UNIQUE (recipient_id, post_id);
|
||||
|
||||
ALTER TABLE inbox_combined
|
||||
ADD CONSTRAINT inbox_combined_check CHECK (num_nonnulls (comment_reply_id, person_comment_mention_id, person_post_mention_id, private_message_id) = 1);
|
||||
|
||||
CREATE INDEX idx_comment_reply_comment ON comment_reply USING btree (comment_id);
|
||||
|
||||
CREATE INDEX idx_comment_reply_recipient ON comment_reply USING btree (recipient_id);
|
||||
|
||||
CREATE INDEX idx_comment_reply_published ON comment_reply USING btree (published_at DESC);
|
||||
|
||||
CREATE INDEX idx_inbox_combined_published_asc ON inbox_combined USING btree (reverse_timestamp_sort (published_at) DESC, id DESC);
|
||||
|
||||
CREATE INDEX idx_inbox_combined_published ON inbox_combined USING btree (published_at DESC, id DESC);
|
||||
|
||||
DROP TABLE notification;
|
||||
|
||||
DROP TYPE notification_type_enum;
|
||||
|
||||
ALTER TABLE community_actions
|
||||
DROP COLUMN notifications;
|
||||
|
||||
DROP TYPE community_notifications_mode_enum;
|
||||
|
||||
ALTER TABLE post_actions
|
||||
DROP COLUMN notifications;
|
||||
|
||||
DROP TYPE post_notifications_mode_enum;
|
||||
|
104
migrations/2025-07-17-103657_post-or-comment-notification/up.sql
Normal file
104
migrations/2025-07-17-103657_post-or-comment-notification/up.sql
Normal file
|
@ -0,0 +1,104 @@
|
|||
-- create new data types
|
||||
CREATE TYPE notification_type_enum AS enum (
|
||||
'Mention',
|
||||
'Reply',
|
||||
'Subscribed',
|
||||
'PrivateMessage'
|
||||
);
|
||||
|
||||
-- create notification table by renaming comment_reply, to avoid copying lots of data around
|
||||
ALTER TABLE comment_reply RENAME TO notification;
|
||||
|
||||
ALTER INDEX idx_comment_reply_comment RENAME TO idx_notification_comment;
|
||||
|
||||
ALTER INDEX idx_comment_reply_recipient RENAME TO idx_notification_recipient;
|
||||
|
||||
ALTER INDEX idx_comment_reply_published RENAME TO idx_notification_published;
|
||||
|
||||
ALTER SEQUENCE comment_reply_id_seq
|
||||
RENAME TO notification_id_seq;
|
||||
|
||||
ALTER TABLE notification RENAME CONSTRAINT comment_reply_comment_id_fkey TO notification_comment_id_fkey;
|
||||
|
||||
ALTER TABLE notification RENAME CONSTRAINT comment_reply_pkey TO notification_pkey;
|
||||
|
||||
ALTER TABLE notification
|
||||
DROP CONSTRAINT comment_reply_recipient_id_comment_id_key;
|
||||
|
||||
ALTER TABLE notification RENAME CONSTRAINT comment_reply_recipient_id_fkey TO notification_recipient_id_fkey;
|
||||
|
||||
ALTER TABLE notification
|
||||
ADD COLUMN kind notification_type_enum NOT NULL DEFAULT 'Reply',
|
||||
ALTER COLUMN comment_id DROP NOT NULL,
|
||||
ADD COLUMN post_id int REFERENCES post (id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
ADD COLUMN private_message_id int REFERENCES private_message (id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE notification
|
||||
ALTER COLUMN kind DROP DEFAULT;
|
||||
|
||||
-- copy data from person_post_mention table
|
||||
INSERT INTO notification (post_id, recipient_id, kind, read, published_at)
|
||||
SELECT
|
||||
post_id,
|
||||
recipient_id,
|
||||
'Mention',
|
||||
read,
|
||||
published_at
|
||||
FROM
|
||||
person_post_mention;
|
||||
|
||||
-- copy data from person_comment_mention table
|
||||
INSERT INTO notification (comment_id, recipient_id, kind, read, published_at)
|
||||
SELECT
|
||||
comment_id,
|
||||
recipient_id,
|
||||
'Mention',
|
||||
read,
|
||||
published_at
|
||||
FROM
|
||||
person_comment_mention;
|
||||
|
||||
-- copy data from private_message table
|
||||
INSERT INTO notification (private_message_id, recipient_id, kind, read, published_at)
|
||||
SELECT
|
||||
id,
|
||||
recipient_id,
|
||||
'PrivateMessage',
|
||||
read,
|
||||
published_at
|
||||
FROM
|
||||
private_message;
|
||||
|
||||
ALTER TABLE private_message
|
||||
DROP COLUMN read;
|
||||
|
||||
ALTER TABLE notification
|
||||
ADD CONSTRAINT notification_check CHECK (num_nonnulls (post_id, comment_id, private_message_id) = 1);
|
||||
|
||||
CREATE INDEX idx_notification_recipient_published ON notification (recipient_id, published_at);
|
||||
|
||||
CREATE INDEX idx_notification_post ON notification (post_id);
|
||||
|
||||
CREATE INDEX idx_notification_private_message ON notification (private_message_id);
|
||||
|
||||
DROP TABLE inbox_combined, person_post_mention, person_comment_mention;
|
||||
|
||||
CREATE TYPE post_notifications_mode_enum AS enum (
|
||||
'AllComments',
|
||||
'RepliesAndMentions',
|
||||
'Mute'
|
||||
);
|
||||
|
||||
ALTER TABLE post_actions
|
||||
ADD COLUMN notifications post_notifications_mode_enum;
|
||||
|
||||
CREATE TYPE community_notifications_mode_enum AS enum (
|
||||
'AllPostsAndComments',
|
||||
'AllPosts',
|
||||
'RepliesAndMentions',
|
||||
'Mute'
|
||||
);
|
||||
|
||||
ALTER TABLE community_actions
|
||||
ADD COLUMN notifications community_notifications_mode_enum;
|
||||
|
|
@ -20,6 +20,7 @@ use lemmy_api::{
|
|||
random::get_random_community,
|
||||
tag::{create_community_tag, delete_community_tag, update_community_tag},
|
||||
transfer::transfer_community,
|
||||
update_notifications::update_community_notifications,
|
||||
},
|
||||
local_user::{
|
||||
add_admin::add_admin,
|
||||
|
@ -41,11 +42,9 @@ use lemmy_api::{
|
|||
logout::logout,
|
||||
note_person::user_note_person,
|
||||
notifications::{
|
||||
list_inbox::list_inbox,
|
||||
list::list_notifications,
|
||||
mark_all_read::mark_all_notifications_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,
|
||||
mark_notification_read::mark_notification_as_read,
|
||||
unread_count::unread_count,
|
||||
},
|
||||
report_count::report_count,
|
||||
|
@ -67,8 +66,8 @@ use lemmy_api::{
|
|||
mark_many_read::mark_posts_as_read,
|
||||
mark_read::mark_post_as_read,
|
||||
save::save_post,
|
||||
update_notifications::update_post_notifications,
|
||||
},
|
||||
private_message::mark_read::mark_pm_as_read,
|
||||
reports::{
|
||||
comment_report::{create::create_comment_report, resolve::resolve_comment_report},
|
||||
community_report::{create::create_community_report, resolve::resolve_community_report},
|
||||
|
@ -247,6 +246,7 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimit) {
|
|||
.route("/tag", post().to(create_community_tag))
|
||||
.route("/tag", put().to(update_community_tag))
|
||||
.route("/tag", delete().to(delete_community_tag))
|
||||
.route("/notifications", post().to(update_community_notifications))
|
||||
.service(
|
||||
scope("/pending_follows")
|
||||
.route("/count", get().to(get_pending_follows_count))
|
||||
|
@ -294,7 +294,8 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimit) {
|
|||
.route("/like/list", get().to(list_post_likes))
|
||||
.route("/save", put().to(save_post))
|
||||
.route("/report", post().to(create_post_report))
|
||||
.route("/report/resolve", put().to(resolve_post_report)),
|
||||
.route("/report/resolve", put().to(resolve_post_report))
|
||||
.route("/notifications", post().to(update_post_notifications)),
|
||||
)
|
||||
// Comment
|
||||
.service(
|
||||
|
@ -310,7 +311,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimit) {
|
|||
.route("", put().to(update_comment))
|
||||
.route("/delete", post().to(delete_comment))
|
||||
.route("/remove", post().to(remove_comment))
|
||||
.route("/mark_as_read", post().to(mark_reply_as_read))
|
||||
.route("/distinguish", post().to(distinguish_comment))
|
||||
.route("/like", post().to(like_comment))
|
||||
.route("/like/list", get().to(list_comment_likes))
|
||||
|
@ -326,7 +326,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimit) {
|
|||
.route("", post().to(create_private_message))
|
||||
.route("", put().to(update_private_message))
|
||||
.route("/delete", post().to(delete_private_message))
|
||||
.route("/mark_as_read", post().to(mark_pm_as_read))
|
||||
.route("/report", post().to(create_pm_report))
|
||||
.route("/report/resolve", put().to(resolve_pm_report)),
|
||||
)
|
||||
|
@ -364,17 +363,10 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimit) {
|
|||
.route("", delete().to(delete_image))
|
||||
.route("/list", get().to(list_media)),
|
||||
)
|
||||
.route("/inbox", get().to(list_inbox))
|
||||
.route("/notifications", get().to(list_notifications))
|
||||
.route("/delete", post().to(delete_account))
|
||||
.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("/mark_as_read", post().to(mark_notification_as_read))
|
||||
.route("/report_count", get().to(report_count))
|
||||
.route("/unread_count", get().to(unread_count))
|
||||
.route("/list_logins", get().to(list_logins))
|
||||
|
|
Loading…
Reference in a new issue