mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-09-02 11:13:51 +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_follower",
|
||||||
"lemmy_db_views_community_moderator",
|
"lemmy_db_views_community_moderator",
|
||||||
"lemmy_db_views_community_person_ban",
|
"lemmy_db_views_community_person_ban",
|
||||||
"lemmy_db_views_inbox_combined",
|
|
||||||
"lemmy_db_views_local_image",
|
"lemmy_db_views_local_image",
|
||||||
"lemmy_db_views_local_user",
|
"lemmy_db_views_local_user",
|
||||||
"lemmy_db_views_modlog_combined",
|
"lemmy_db_views_modlog_combined",
|
||||||
|
"lemmy_db_views_notification",
|
||||||
"lemmy_db_views_person",
|
"lemmy_db_views_person",
|
||||||
"lemmy_db_views_person_content_combined",
|
"lemmy_db_views_person_content_combined",
|
||||||
"lemmy_db_views_person_liked_combined",
|
"lemmy_db_views_person_liked_combined",
|
||||||
|
@ -3143,10 +3143,10 @@ dependencies = [
|
||||||
"lemmy_db_views_community_follower",
|
"lemmy_db_views_community_follower",
|
||||||
"lemmy_db_views_community_moderator",
|
"lemmy_db_views_community_moderator",
|
||||||
"lemmy_db_views_custom_emoji",
|
"lemmy_db_views_custom_emoji",
|
||||||
"lemmy_db_views_inbox_combined",
|
|
||||||
"lemmy_db_views_local_image",
|
"lemmy_db_views_local_image",
|
||||||
"lemmy_db_views_local_user",
|
"lemmy_db_views_local_user",
|
||||||
"lemmy_db_views_modlog_combined",
|
"lemmy_db_views_modlog_combined",
|
||||||
|
"lemmy_db_views_notification",
|
||||||
"lemmy_db_views_person",
|
"lemmy_db_views_person",
|
||||||
"lemmy_db_views_person_content_combined",
|
"lemmy_db_views_person_content_combined",
|
||||||
"lemmy_db_views_person_liked_combined",
|
"lemmy_db_views_person_liked_combined",
|
||||||
|
@ -3209,6 +3209,7 @@ dependencies = [
|
||||||
"actix-web-httpauth",
|
"actix-web-httpauth",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"derive-new",
|
||||||
"diesel_ltree",
|
"diesel_ltree",
|
||||||
"either",
|
"either",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
|
@ -3227,6 +3228,7 @@ dependencies = [
|
||||||
"lemmy_db_views_community_person_ban",
|
"lemmy_db_views_community_person_ban",
|
||||||
"lemmy_db_views_local_image",
|
"lemmy_db_views_local_image",
|
||||||
"lemmy_db_views_local_user",
|
"lemmy_db_views_local_user",
|
||||||
|
"lemmy_db_views_notification",
|
||||||
"lemmy_db_views_person",
|
"lemmy_db_views_person",
|
||||||
"lemmy_db_views_post",
|
"lemmy_db_views_post",
|
||||||
"lemmy_db_views_private_message",
|
"lemmy_db_views_private_message",
|
||||||
|
@ -3316,6 +3318,7 @@ dependencies = [
|
||||||
"lemmy_db_views_community_moderator",
|
"lemmy_db_views_community_moderator",
|
||||||
"lemmy_db_views_community_person_ban",
|
"lemmy_db_views_community_person_ban",
|
||||||
"lemmy_db_views_local_user",
|
"lemmy_db_views_local_user",
|
||||||
|
"lemmy_db_views_private_message",
|
||||||
"lemmy_db_views_site",
|
"lemmy_db_views_site",
|
||||||
"lemmy_utils",
|
"lemmy_utils",
|
||||||
"moka",
|
"moka",
|
||||||
|
@ -3500,25 +3503,6 @@ dependencies = [
|
||||||
"url",
|
"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]]
|
[[package]]
|
||||||
name = "lemmy_db_views_local_image"
|
name = "lemmy_db_views_local_image"
|
||||||
version = "1.0.0-alpha.5"
|
version = "1.0.0-alpha.5"
|
||||||
|
@ -3572,6 +3556,24 @@ dependencies = [
|
||||||
"ts-rs",
|
"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]]
|
[[package]]
|
||||||
name = "lemmy_db_views_person"
|
name = "lemmy_db_views_person"
|
||||||
version = "1.0.0-alpha.5"
|
version = "1.0.0-alpha.5"
|
||||||
|
@ -3882,10 +3884,10 @@ dependencies = [
|
||||||
"lemmy_db_schema",
|
"lemmy_db_schema",
|
||||||
"lemmy_db_schema_file",
|
"lemmy_db_schema_file",
|
||||||
"lemmy_db_views_community",
|
"lemmy_db_views_community",
|
||||||
"lemmy_db_views_inbox_combined",
|
|
||||||
"lemmy_db_views_local_image",
|
"lemmy_db_views_local_image",
|
||||||
"lemmy_db_views_local_user",
|
"lemmy_db_views_local_user",
|
||||||
"lemmy_db_views_modlog_combined",
|
"lemmy_db_views_modlog_combined",
|
||||||
|
"lemmy_db_views_notification",
|
||||||
"lemmy_db_views_person_content_combined",
|
"lemmy_db_views_person_content_combined",
|
||||||
"lemmy_db_views_post",
|
"lemmy_db_views_post",
|
||||||
"lemmy_db_views_site",
|
"lemmy_db_views_site",
|
||||||
|
|
|
@ -65,7 +65,7 @@ members = [
|
||||||
"crates/db_views/community_follower",
|
"crates/db_views/community_follower",
|
||||||
"crates/db_views/community_person_ban",
|
"crates/db_views/community_person_ban",
|
||||||
"crates/db_views/custom_emoji",
|
"crates/db_views/custom_emoji",
|
||||||
"crates/db_views/inbox_combined",
|
"crates/db_views/notification",
|
||||||
"crates/db_views/modlog_combined",
|
"crates/db_views/modlog_combined",
|
||||||
"crates/db_views/person_content_combined",
|
"crates/db_views/person_content_combined",
|
||||||
"crates/db_views/person_saved_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_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_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_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_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_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" }
|
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",
|
"eslint-plugin-prettier": "^5.4.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"joi": "^17.13.3",
|
"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",
|
"prettier": "^3.5.3",
|
||||||
"ts-jest": "^29.3.2",
|
"ts-jest": "^29.3.2",
|
||||||
"tsoa": "^6.6.0",
|
"tsoa": "^6.6.0",
|
||||||
|
|
|
@ -36,8 +36,8 @@ importers:
|
||||||
specifier: ^17.13.3
|
specifier: ^17.13.3
|
||||||
version: 17.13.3
|
version: 17.13.3
|
||||||
lemmy-js-client:
|
lemmy-js-client:
|
||||||
specifier: 1.0.0-rename-rate-limit-columns.1
|
specifier: 1.0.0-post-notifications.5
|
||||||
version: 1.0.0-rename-rate-limit-columns.1
|
version: 1.0.0-post-notifications.5
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.5.3
|
specifier: ^3.5.3
|
||||||
version: 3.5.3
|
version: 3.5.3
|
||||||
|
@ -1594,8 +1594,8 @@ packages:
|
||||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
lemmy-js-client@1.0.0-rename-rate-limit-columns.1:
|
lemmy-js-client@1.0.0-post-notifications.5:
|
||||||
resolution: {integrity: sha512-zlVJ4zkoI/7hNm6x7vr+Su2cRjAr8PQCA9j0GeK1UCMEIBLLSltknuRPC79VJY2sUhRAuR2JwTR0JtZ75SH2XQ==}
|
resolution: {integrity: sha512-2P0KPCordLRfuGTcgsU3pHSFJlVN5t91e04yhpUf5fZT7iTdlEctFQFtURsvfYPNYK/sdvsucqYbnpbbHJUCTA==}
|
||||||
|
|
||||||
leven@3.1.0:
|
leven@3.1.0:
|
||||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||||
|
@ -4404,7 +4404,7 @@ snapshots:
|
||||||
|
|
||||||
kleur@3.0.3: {}
|
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:
|
dependencies:
|
||||||
'@tsoa/runtime': 6.6.0
|
'@tsoa/runtime': 6.6.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
|
|
@ -36,16 +36,14 @@ import {
|
||||||
saveUserSettings,
|
saveUserSettings,
|
||||||
listReports,
|
listReports,
|
||||||
listPersonContent,
|
listPersonContent,
|
||||||
listInbox,
|
listNotifications,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import {
|
import {
|
||||||
CommentReplyView,
|
|
||||||
CommentReportView,
|
CommentReportView,
|
||||||
CommentView,
|
CommentView,
|
||||||
CommunityView,
|
CommunityView,
|
||||||
DistinguishComment,
|
DistinguishComment,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
PersonCommentMentionView,
|
|
||||||
ReportCombinedView,
|
ReportCombinedView,
|
||||||
SaveUserSettings,
|
SaveUserSettings,
|
||||||
} from "lemmy-js-client";
|
} 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
|
// check inbox of replies on alpha, fetching read/unread both
|
||||||
let alphaRepliesRes = await waitUntil(
|
let alphaRepliesRes = await waitUntil(
|
||||||
() => listInbox(alpha, "CommentReply"),
|
() => listNotifications(alpha, "Reply"),
|
||||||
r => r.inbox.length > 0,
|
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();
|
expect(alphaReply).toBeDefined();
|
||||||
if (!alphaReply) throw Error();
|
if (!alphaReply) throw Error();
|
||||||
expect(alphaReply.comment.content).toBeDefined();
|
const alphaReplyData = alphaReply.data as CommentView;
|
||||||
expect(alphaReply.community.local).toBe(false);
|
expect(alphaReplyData.comment!.content).toBeDefined();
|
||||||
expect(alphaReply.creator.local).toBe(false);
|
expect(alphaReplyData.community!.local).toBe(false);
|
||||||
expect(alphaReply.comment.score).toBe(1);
|
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?
|
// 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.
|
// this is a new notification, getReplies fetch was for read/unread both, confirm it is unread.
|
||||||
expect(alphaReply.comment_reply.read).toBe(false);
|
expect(alphaReply.notification.read).toBe(false);
|
||||||
assertCommentFederation(alphaReply, replyRes.comment_view);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Bot reply notifications are filtered when bots are hidden", async () => {
|
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);
|
alphaUnreadCountRes = await getUnreadCount(alpha);
|
||||||
expect(alphaUnreadCountRes.count).toBe(1);
|
expect(alphaUnreadCountRes.count).toBe(1);
|
||||||
|
|
||||||
let alphaUnreadRepliesRes = await listInbox(alpha, "CommentReply", true);
|
let alphaUnreadRepliesRes = await listNotifications(alpha, "Reply", true);
|
||||||
expect(alphaUnreadRepliesRes.inbox.length).toBe(1);
|
expect(alphaUnreadRepliesRes.notifications.length).toBe(1);
|
||||||
expect((alphaUnreadRepliesRes.inbox[0] as CommentReplyView).comment.id).toBe(
|
expect(alphaUnreadRepliesRes.notifications[0].notification.comment_id).toBe(
|
||||||
commentRes.comment_view.comment.id,
|
commentRes.comment_view.comment.id,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -526,17 +526,18 @@ test("Mention beta from alpha comment", async () => {
|
||||||
assertCommentFederation(betaRootComment, commentRes.comment_view);
|
assertCommentFederation(betaRootComment, commentRes.comment_view);
|
||||||
|
|
||||||
let mentionsRes = await waitUntil(
|
let mentionsRes = await waitUntil(
|
||||||
() => listInbox(beta, "CommentMention"),
|
() => listNotifications(beta, "Mention"),
|
||||||
m => !!m.inbox[0],
|
m => !!m.notifications[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
const firstMention = mentionsRes.inbox[0] as PersonCommentMentionView;
|
const firstMention = mentionsRes.notifications[0];
|
||||||
expect(firstMention.comment.content).toBeDefined();
|
let firstMentionData = firstMention.data as CommentView;
|
||||||
expect(firstMention.community.local).toBe(true);
|
expect(firstMentionData.comment!.content).toBeDefined();
|
||||||
expect(firstMention.creator.local).toBe(false);
|
expect(firstMentionData.community!.local).toBe(true);
|
||||||
expect(firstMention.comment.score).toBe(1);
|
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
|
// 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,
|
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
|
// Make sure beta has mentions
|
||||||
let relevantMention = (await waitUntil(
|
let relevantMention = await waitUntil(
|
||||||
() =>
|
() =>
|
||||||
listInbox(beta, "CommentMention").then(m =>
|
listNotifications(beta, "Mention").then(m =>
|
||||||
m.inbox.find(
|
m.notifications.find(m => {
|
||||||
m =>
|
let data = m.data as CommentView;
|
||||||
m.type_ == "CommentMention" &&
|
return (
|
||||||
m.comment.ap_id === commentRes.comment_view.comment.ap_id,
|
m.notification.kind == "Mention" &&
|
||||||
),
|
data.comment.ap_id === commentRes.comment_view.comment.ap_id
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
e => !!e,
|
e => !!e,
|
||||||
)) as PersonCommentMentionView | undefined;
|
);
|
||||||
if (!relevantMention) throw Error("could not find mention");
|
if (!relevantMention) throw Error("could not find mention");
|
||||||
expect(relevantMention.comment.content).toBe(commentContent);
|
let relevantMentionData = relevantMention.data as CommentView;
|
||||||
expect(relevantMention.community.local).toBe(false);
|
expect(relevantMentionData.comment!.content).toBe(commentContent);
|
||||||
expect(relevantMention.creator.local).toBe(false);
|
expect(relevantMentionData.community!.local).toBe(false);
|
||||||
|
expect(relevantMentionData.creator.local).toBe(false);
|
||||||
// TODO this is failing because fetchInReplyTos aren't getting score
|
// TODO this is failing because fetchInReplyTos aren't getting score
|
||||||
// expect(mentionsRes.mentions[0].score).toBe(1);
|
// 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);
|
unreadCount = await getUnreadCount(beta);
|
||||||
expect(unreadCount.count).toBe(0);
|
expect(unreadCount.count).toBe(0);
|
||||||
|
|
||||||
let replies = await listInbox(beta, "CommentReply", true);
|
let replies = await listNotifications(beta, "Reply", true);
|
||||||
expect(replies.inbox.length).toBe(0);
|
expect(replies.notifications.length).toBe(0);
|
||||||
|
|
||||||
// Unblock the community
|
// Unblock the community
|
||||||
blockRes = await blockCommunity(beta, newCommunityId, false);
|
blockRes = await blockCommunity(beta, newCommunityId, false);
|
||||||
|
|
|
@ -45,7 +45,6 @@ import {
|
||||||
FollowMultiCommunity,
|
FollowMultiCommunity,
|
||||||
GetPosts,
|
GetPosts,
|
||||||
LemmyError,
|
LemmyError,
|
||||||
MultiCommunity,
|
|
||||||
MultiCommunityView,
|
MultiCommunityView,
|
||||||
ReportCombinedView,
|
ReportCombinedView,
|
||||||
ResolveCommunityReport,
|
ResolveCommunityReport,
|
||||||
|
|
|
@ -38,7 +38,7 @@ import {
|
||||||
createCommunity,
|
createCommunity,
|
||||||
listReports,
|
listReports,
|
||||||
getMyUser,
|
getMyUser,
|
||||||
listInbox,
|
listNotifications,
|
||||||
getModlog,
|
getModlog,
|
||||||
getCommunity,
|
getCommunity,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
@ -48,7 +48,6 @@ import {
|
||||||
AddModToCommunity,
|
AddModToCommunity,
|
||||||
EditSite,
|
EditSite,
|
||||||
EditPost,
|
EditPost,
|
||||||
PersonPostMentionView,
|
|
||||||
PostReport,
|
PostReport,
|
||||||
PostReportView,
|
PostReportView,
|
||||||
ReportCombinedView,
|
ReportCombinedView,
|
||||||
|
@ -942,16 +941,15 @@ test("Mention beta from alpha post body", async () => {
|
||||||
await assertPostFederation(betaPost, postOnAlphaRes.post_view);
|
await assertPostFederation(betaPost, postOnAlphaRes.post_view);
|
||||||
|
|
||||||
let mentionsRes = await waitUntil(
|
let mentionsRes = await waitUntil(
|
||||||
() => listInbox(beta, "PostMention"),
|
() => listNotifications(beta, "Mention"),
|
||||||
m => !!m.inbox[0],
|
m => !!m.notifications[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
const firstMention = mentionsRes.inbox[0] as PersonPostMentionView;
|
const firstMention = mentionsRes.notifications[0].data as PostView;
|
||||||
expect(firstMention.post.body).toBeDefined();
|
expect(firstMention.post!.body).toBeDefined();
|
||||||
expect(firstMention.community.local).toBe(true);
|
expect(firstMention.community!.local).toBe(true);
|
||||||
expect(firstMention.creator.local).toBe(false);
|
expect(firstMention.creator.local).toBe(false);
|
||||||
expect(firstMention.post.score).toBe(1);
|
expect(firstMention.post!.score).toBe(1);
|
||||||
expect(firstMention.person_post_mention.post_id).toBe(betaPost.post.id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Rewrite markdown links", async () => {
|
test("Rewrite markdown links", async () => {
|
||||||
|
|
|
@ -4,14 +4,13 @@ import {
|
||||||
alpha,
|
alpha,
|
||||||
beta,
|
beta,
|
||||||
setupLogins,
|
setupLogins,
|
||||||
followBeta,
|
|
||||||
createPrivateMessage,
|
createPrivateMessage,
|
||||||
editPrivateMessage,
|
editPrivateMessage,
|
||||||
deletePrivateMessage,
|
deletePrivateMessage,
|
||||||
waitUntil,
|
waitUntil,
|
||||||
reportPrivateMessage,
|
reportPrivateMessage,
|
||||||
unfollows,
|
unfollows,
|
||||||
listInbox,
|
listNotifications,
|
||||||
resolvePerson,
|
resolvePerson,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
|
@ -37,10 +36,10 @@ test("Create a private message", async () => {
|
||||||
expect(pmRes.private_message_view.recipient.local).toBe(false);
|
expect(pmRes.private_message_view.recipient.local).toBe(false);
|
||||||
|
|
||||||
let betaPms = await waitUntil(
|
let betaPms = await waitUntil(
|
||||||
() => listInbox(beta, "PrivateMessage"),
|
() => listNotifications(beta, "PrivateMessage"),
|
||||||
e => !!e.inbox[0],
|
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.content).toBeDefined();
|
||||||
expect(firstPm.private_message.local).toBe(false);
|
expect(firstPm.private_message.local).toBe(false);
|
||||||
expect(firstPm.creator.local).toBe(false);
|
expect(firstPm.creator.local).toBe(false);
|
||||||
|
@ -60,25 +59,24 @@ test("Update a private message", async () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
let betaPms = await waitUntil(
|
let betaPms = await waitUntil(
|
||||||
() => listInbox(beta, "PrivateMessage"),
|
() => listNotifications(beta, "PrivateMessage"),
|
||||||
p =>
|
p =>
|
||||||
p.inbox[0].type_ == "PrivateMessage" &&
|
p.notifications[0].data.type_ == "PrivateMessage" &&
|
||||||
p.inbox[0].private_message.content === updatedContent,
|
p.notifications[0].data.private_message.content === updatedContent,
|
||||||
);
|
|
||||||
expect((betaPms.inbox[0] as PrivateMessageView).private_message.content).toBe(
|
|
||||||
updatedContent,
|
|
||||||
);
|
);
|
||||||
|
let pm = betaPms.notifications[0].data as PrivateMessageView;
|
||||||
|
expect(pm.private_message.content).toBe(updatedContent);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Delete a private message", async () => {
|
test("Delete a private message", async () => {
|
||||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||||
let betaPms1 = await waitUntil(
|
let betaPms1 = await waitUntil(
|
||||||
() => listInbox(beta, "PrivateMessage"),
|
() => listNotifications(beta, "PrivateMessage"),
|
||||||
m =>
|
m =>
|
||||||
!!m.inbox.find(
|
!!m.notifications.find(
|
||||||
e =>
|
e =>
|
||||||
e.type_ == "PrivateMessage" &&
|
e.data.type_ == "PrivateMessage" &&
|
||||||
e.private_message.ap_id ===
|
e.data.private_message.ap_id ===
|
||||||
pmRes.private_message_view.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.
|
// even though they are in the actual database.
|
||||||
// no reason to show them
|
// no reason to show them
|
||||||
let betaPms2 = await waitUntil(
|
let betaPms2 = await waitUntil(
|
||||||
() => listInbox(beta, "PrivateMessage"),
|
() => listNotifications(beta, "PrivateMessage"),
|
||||||
p => p.inbox.length === betaPms1.inbox.length - 1,
|
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
|
// Undelete
|
||||||
let undeletedPmRes = await deletePrivateMessage(
|
let undeletedPmRes = await deletePrivateMessage(
|
||||||
|
@ -109,25 +107,25 @@ test("Delete a private message", async () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
let betaPms3 = await waitUntil(
|
let betaPms3 = await waitUntil(
|
||||||
() => listInbox(beta, "PrivateMessage"),
|
() => listNotifications(beta, "PrivateMessage"),
|
||||||
p => p.inbox.length === betaPms1.inbox.length,
|
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 () => {
|
test("Create a private message report", async () => {
|
||||||
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
let pmRes = await createPrivateMessage(alpha, recipient_id);
|
||||||
let betaPms1 = await waitUntil(
|
let betaPms1 = await waitUntil(
|
||||||
() => listInbox(beta, "PrivateMessage"),
|
() => listNotifications(beta, "PrivateMessage"),
|
||||||
m =>
|
m =>
|
||||||
!!m.inbox.find(
|
!!m.notifications.find(
|
||||||
e =>
|
e =>
|
||||||
e.type_ == "PrivateMessage" &&
|
e.data.type_ == "PrivateMessage" &&
|
||||||
e.private_message.ap_id ===
|
e.data.private_message.ap_id ===
|
||||||
pmRes.private_message_view.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();
|
expect(betaPm).toBeDefined();
|
||||||
|
|
||||||
// Make sure that only the recipient can report it, so this should fail
|
// Make sure that only the recipient can report it, so this should fail
|
||||||
|
|
|
@ -24,14 +24,14 @@ import {
|
||||||
ListPersonContentResponse,
|
ListPersonContentResponse,
|
||||||
ListPersonContent,
|
ListPersonContent,
|
||||||
PersonContentType,
|
PersonContentType,
|
||||||
ListInboxResponse,
|
|
||||||
ListInbox,
|
|
||||||
InboxDataType,
|
InboxDataType,
|
||||||
GetModlogResponse,
|
GetModlogResponse,
|
||||||
GetModlog,
|
GetModlog,
|
||||||
CommunityView,
|
CommunityView,
|
||||||
CommentView,
|
CommentView,
|
||||||
PersonView,
|
PersonView,
|
||||||
|
ListNotifications,
|
||||||
|
ListNotificationsResponse,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
import { CreatePost } from "lemmy-js-client/dist/types/CreatePost";
|
||||||
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
|
import { DeletePost } from "lemmy-js-client/dist/types/DeletePost";
|
||||||
|
@ -384,16 +384,16 @@ export async function getUnreadCount(
|
||||||
return api.getUnreadCount();
|
return api.getUnreadCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listInbox(
|
export async function listNotifications(
|
||||||
api: LemmyHttp,
|
api: LemmyHttp,
|
||||||
type_?: InboxDataType,
|
type_?: InboxDataType,
|
||||||
unread_only: boolean = false,
|
unread_only: boolean = false,
|
||||||
): Promise<ListInboxResponse> {
|
): Promise<ListNotificationsResponse> {
|
||||||
let form: ListInbox = {
|
let form: ListNotifications = {
|
||||||
unread_only,
|
unread_only,
|
||||||
type_,
|
type_,
|
||||||
};
|
};
|
||||||
return api.listInbox(form);
|
return api.listNotifications(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveComment(
|
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_local_user = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_person = { workspace = true, features = ["full"] }
|
lemmy_db_views_person = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_local_image = { 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_modlog_combined = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_person_saved_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"] }
|
lemmy_db_views_person_liked_combined = { workspace = true, features = ["full"] }
|
||||||
|
|
|
@ -69,8 +69,5 @@ pub async fn distinguish_comment(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Json(CommentResponse {
|
Ok(Json(CommentResponse { comment_view }))
|
||||||
comment_view,
|
|
||||||
recipient_ids: Vec::new(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,9 @@ use lemmy_api_utils::{
|
||||||
utils::{check_bot_account, check_community_user_action, check_local_vote_mode},
|
utils::{check_bot_account, check_community_user_action, check_local_vote_mode},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{LocalUserId, PostOrCommentId},
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
comment::{CommentActions, CommentLikeForm},
|
comment::{CommentActions, CommentLikeForm},
|
||||||
comment_reply::CommentReply,
|
|
||||||
person::PersonActions,
|
person::PersonActions,
|
||||||
},
|
},
|
||||||
traits::Likeable,
|
traits::Likeable,
|
||||||
|
@ -35,8 +34,6 @@ pub async fn like_comment(
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let my_person_id = local_user_view.person.id;
|
let my_person_id = local_user_view.person.id;
|
||||||
|
|
||||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
|
||||||
|
|
||||||
check_local_vote_mode(
|
check_local_vote_mode(
|
||||||
data.score,
|
data.score,
|
||||||
PostOrCommentId::Comment(comment_id),
|
PostOrCommentId::Comment(comment_id),
|
||||||
|
@ -63,16 +60,6 @@ pub async fn like_comment(
|
||||||
)
|
)
|
||||||
.await?;
|
.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);
|
let mut like_form = CommentLikeForm::new(my_person_id, data.comment_id, data.score);
|
||||||
|
|
||||||
// Remove any likes first
|
// Remove any likes first
|
||||||
|
@ -119,7 +106,6 @@ pub async fn like_comment(
|
||||||
context.deref(),
|
context.deref(),
|
||||||
comment_id,
|
comment_id,
|
||||||
Some(local_user_view),
|
Some(local_user_view),
|
||||||
recipient_ids,
|
|
||||||
local_instance_id,
|
local_instance_id,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
|
|
@ -34,8 +34,5 @@ pub async fn save_comment(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Json(CommentResponse {
|
Ok(Json(CommentResponse { comment_view }))
|
||||||
comment_view,
|
|
||||||
recipient_ids: Vec::new(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,3 +7,4 @@ pub mod pending_follows;
|
||||||
pub mod random;
|
pub mod random;
|
||||||
pub mod tag;
|
pub mod tag;
|
||||||
pub mod transfer;
|
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 community;
|
||||||
pub mod local_user;
|
pub mod local_user;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
|
||||||
pub mod reports;
|
pub mod reports;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
pub mod sitemap;
|
pub mod sitemap;
|
||||||
|
|
|
@ -3,8 +3,8 @@ use actix_web::web::Json;
|
||||||
use lemmy_api_utils::context::LemmyContext;
|
use lemmy_api_utils::context::LemmyContext;
|
||||||
use lemmy_db_schema::source::local_user::LocalUser;
|
use lemmy_db_schema::source::local_user::LocalUser;
|
||||||
use lemmy_db_views_community_moderator::CommunityModeratorView;
|
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_local_user::LocalUserView;
|
||||||
|
use lemmy_db_views_notification::{impls::NotificationQuery, NotificationData};
|
||||||
use lemmy_db_views_person_content_combined::{
|
use lemmy_db_views_person_content_combined::{
|
||||||
impls::PersonContentCombinedQuery,
|
impls::PersonContentCombinedQuery,
|
||||||
PersonContentCombinedView,
|
PersonContentCombinedView,
|
||||||
|
@ -46,18 +46,18 @@ pub async fn export_data(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let inbox = InboxCombinedQuery {
|
let notifications = NotificationQuery {
|
||||||
no_limit: Some(true),
|
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?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|u| match u {
|
.map(|u| match u.data {
|
||||||
InboxCombinedView::CommentReply(cr) => Comment(cr.comment),
|
NotificationData::Post(p) => Post(p.post),
|
||||||
InboxCombinedView::CommentMention(cm) => Comment(cm.comment),
|
NotificationData::Comment(c) => Comment(c.comment),
|
||||||
InboxCombinedView::PostMention(pm) => Post(pm.post),
|
NotificationData::PrivateMessage(pm) => PrivateMessage(pm.private_message),
|
||||||
InboxCombinedView::PrivateMessage(pm) => PrivateMessage(pm.private_message),
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ pub async fn export_data(
|
||||||
let settings = user_backup_list_to_user_settings_backup(local_user_view, lists);
|
let settings = user_backup_list_to_user_settings_backup(local_user_view, lists);
|
||||||
|
|
||||||
Ok(Json(ExportDataResponse {
|
Ok(Json(ExportDataResponse {
|
||||||
inbox,
|
notifications,
|
||||||
content,
|
content,
|
||||||
liked,
|
liked,
|
||||||
read_posts,
|
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 actix_web::web::{Data, Json};
|
||||||
use lemmy_api_utils::context::LemmyContext;
|
use lemmy_api_utils::context::LemmyContext;
|
||||||
use lemmy_db_schema::source::{
|
use lemmy_db_schema::source::notification::Notification;
|
||||||
comment_reply::CommentReply,
|
|
||||||
person_comment_mention::PersonCommentMention,
|
|
||||||
person_post_mention::PersonPostMention,
|
|
||||||
private_message::PrivateMessage,
|
|
||||||
};
|
|
||||||
use lemmy_db_views_local_user::LocalUserView;
|
use lemmy_db_views_local_user::LocalUserView;
|
||||||
use lemmy_db_views_site::api::SuccessResponse;
|
use lemmy_db_views_site::api::SuccessResponse;
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
@ -14,19 +9,7 @@ pub async fn mark_all_notifications_read(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<SuccessResponse>> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
let person_id = local_user_view.person.id;
|
Notification::mark_all_as_read(&mut context.pool(), local_user_view.person.id).await?;
|
||||||
|
|
||||||
// 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?;
|
|
||||||
|
|
||||||
Ok(Json(SuccessResponse::default()))
|
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_all_read;
|
||||||
pub mod mark_comment_mention_read;
|
pub mod mark_notification_read;
|
||||||
pub mod mark_post_mention_read;
|
|
||||||
pub mod mark_reply_read;
|
|
||||||
pub mod unread_count;
|
pub mod unread_count;
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_utils::context::LemmyContext;
|
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_local_user::LocalUserView;
|
||||||
|
use lemmy_db_views_notification::{api::GetUnreadCountResponse, NotificationView};
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
pub async fn unread_count(
|
pub async fn unread_count(
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<GetUnreadCountResponse>> {
|
) -> 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 show_bot_accounts = local_user_view.local_user.show_bot_accounts;
|
||||||
|
let count = NotificationView::get_unread_count(
|
||||||
let count = InboxCombinedViewInternal::get_unread_count(
|
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
person_id,
|
&local_user_view.person,
|
||||||
local_instance_id,
|
|
||||||
show_bot_accounts,
|
show_bot_accounts,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -7,3 +7,4 @@ pub mod lock;
|
||||||
pub mod mark_many_read;
|
pub mod mark_many_read;
|
||||||
pub mod mark_read;
|
pub mod mark_read;
|
||||||
pub mod save;
|
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,
|
utils::DbPool,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema_file::enums::RegistrationMode;
|
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_local_user::LocalUserView;
|
||||||
|
use lemmy_db_views_notification::api::GetUnreadRegistrationApplicationCountResponse;
|
||||||
use lemmy_db_views_registration_applications::api::{
|
use lemmy_db_views_registration_applications::api::{
|
||||||
ApproveRegistrationApplication,
|
ApproveRegistrationApplication,
|
||||||
ListRegistrationApplicationsResponse,
|
ListRegistrationApplicationsResponse,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_utils::{context::LemmyContext, utils::is_admin};
|
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_local_user::LocalUserView;
|
||||||
|
use lemmy_db_views_notification::api::GetUnreadRegistrationApplicationCountResponse;
|
||||||
use lemmy_db_views_registration_applications::RegistrationApplicationView;
|
use lemmy_db_views_registration_applications::RegistrationApplicationView;
|
||||||
use lemmy_db_views_site::SiteView;
|
use lemmy_db_views_site::SiteView;
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
|
@ -27,7 +27,7 @@ ts-rs = [
|
||||||
"lemmy_db_views_community_follower/ts-rs",
|
"lemmy_db_views_community_follower/ts-rs",
|
||||||
"lemmy_db_views_community_moderator/ts-rs",
|
"lemmy_db_views_community_moderator/ts-rs",
|
||||||
"lemmy_db_views_custom_emoji/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_image/ts-rs",
|
||||||
"lemmy_db_views_local_user/ts-rs",
|
"lemmy_db_views_local_user/ts-rs",
|
||||||
"lemmy_db_views_modlog_combined/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_follower.workspace = true
|
||||||
lemmy_db_views_community_moderator.workspace = true
|
lemmy_db_views_community_moderator.workspace = true
|
||||||
lemmy_db_views_custom_emoji.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_image.workspace = true
|
||||||
lemmy_db_views_local_user.workspace = true
|
lemmy_db_views_local_user.workspace = true
|
||||||
lemmy_db_views_modlog_combined.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 custom_emoji;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod federation;
|
pub mod federation;
|
||||||
pub mod inbox;
|
|
||||||
pub mod language;
|
pub mod language;
|
||||||
pub mod media;
|
pub mod media;
|
||||||
pub mod modlog;
|
pub mod modlog;
|
||||||
|
pub mod notification;
|
||||||
pub mod oauth;
|
pub mod oauth;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod plugin;
|
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 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_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_person::api::{AddAdmin, AddAdminResponse};
|
||||||
pub use lemmy_db_views_registration_applications::api::{
|
pub use lemmy_db_views_registration_applications::api::{
|
||||||
ApproveRegistrationApplication,
|
ApproveRegistrationApplication,
|
||||||
|
|
|
@ -2,8 +2,9 @@ use crate::community_use_pending;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
build_response::{build_comment_response, send_local_notifs},
|
build_response::build_comment_response,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::NotifyData,
|
||||||
plugins::{plugin_hook_after, plugin_hook_before},
|
plugins::{plugin_hook_after, plugin_hook_before},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{
|
utils::{
|
||||||
|
@ -19,11 +20,9 @@ use lemmy_api_utils::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::validate_post_language,
|
impls::actor_language::validate_post_language,
|
||||||
newtypes::PostOrCommentId,
|
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentActions, CommentInsertForm, CommentLikeForm},
|
comment::{Comment, CommentActions, CommentInsertForm, CommentLikeForm},
|
||||||
comment_reply::{CommentReply, CommentReplyUpdateForm},
|
notification::Notification,
|
||||||
person_comment_mention::{PersonCommentMention, PersonCommentMentionUpdateForm},
|
|
||||||
},
|
},
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
};
|
};
|
||||||
|
@ -33,7 +32,7 @@ use lemmy_db_views_post::PostView;
|
||||||
use lemmy_db_views_site::SiteView;
|
use lemmy_db_views_site::SiteView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorType, LemmyResult},
|
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(
|
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?;
|
Comment::create(&mut context.pool(), &comment_form, parent_path.as_ref()).await?;
|
||||||
plugin_hook_after("after_create_local_comment", &inserted_comment)?;
|
plugin_hook_after("after_create_local_comment", &inserted_comment)?;
|
||||||
|
|
||||||
let inserted_comment_id = inserted_comment.id;
|
NotifyData::new(
|
||||||
|
&post,
|
||||||
// Scan the comment for user mentions, add those rows
|
Some(&inserted_comment),
|
||||||
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),
|
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
do_send_email,
|
&post_view.community,
|
||||||
&context,
|
!local_site.disable_email_notifications,
|
||||||
Some(&local_user_view),
|
|
||||||
local_instance_id,
|
|
||||||
)
|
)
|
||||||
|
.send(&context)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// You like your own comment by default
|
// You like your own comment by default
|
||||||
|
@ -153,30 +146,10 @@ pub async fn create_comment(
|
||||||
// then mark the parent as read.
|
// then mark the parent as read.
|
||||||
// Then we don't have to do it manually after we respond to a comment.
|
// Then we don't have to do it manually after we respond to a comment.
|
||||||
if let Some(parent) = parent_opt {
|
if let Some(parent) = parent_opt {
|
||||||
let person_id = local_user_view.person.id;
|
let notif = Notification::read_by_comment_id(&mut context.pool(), parent.id).await;
|
||||||
let parent_id = parent.id;
|
if let Ok(notif) = notif {
|
||||||
let comment_reply =
|
let person_id = local_user_view.person.id;
|
||||||
CommentReply::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await;
|
Notification::mark_read_by_id_and_person(&mut context.pool(), notif.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?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +158,6 @@ pub async fn create_comment(
|
||||||
&context,
|
&context,
|
||||||
inserted_comment.id,
|
inserted_comment.id,
|
||||||
Some(local_user_view),
|
Some(local_user_view),
|
||||||
recipient_ids,
|
|
||||||
local_instance_id,
|
local_instance_id,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
build_response::{build_comment_response, send_local_notifs},
|
build_response::build_comment_response,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::check_community_user_action,
|
utils::check_community_user_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PostOrCommentId,
|
|
||||||
source::comment::{Comment, CommentUpdateForm},
|
source::comment::{Comment, CommentUpdateForm},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
@ -62,16 +61,6 @@ pub async fn delete_comment(
|
||||||
)
|
)
|
||||||
.await?;
|
.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;
|
let updated_comment_id = updated_comment.id;
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
|
@ -88,7 +77,6 @@ pub async fn delete_comment(
|
||||||
&context,
|
&context,
|
||||||
updated_comment_id,
|
updated_comment_id,
|
||||||
Some(local_user_view),
|
Some(local_user_view),
|
||||||
recipient_ids,
|
|
||||||
local_instance_id,
|
local_instance_id,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
|
|
@ -21,13 +21,6 @@ pub async fn get_comment(
|
||||||
check_private_instance(&local_user_view, &local_site)?;
|
check_private_instance(&local_user_view, &local_site)?;
|
||||||
|
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
build_comment_response(
|
build_comment_response(&context, data.id, local_user_view, local_instance_id).await?,
|
||||||
&context,
|
|
||||||
data.id,
|
|
||||||
local_user_view,
|
|
||||||
vec![],
|
|
||||||
local_instance_id,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
build_response::{build_comment_response, send_local_notifs},
|
build_response::build_comment_response,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::check_community_mod_action,
|
utils::check_community_mod_action,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::PostOrCommentId,
|
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
comment_report::CommentReport,
|
comment_report::CommentReport,
|
||||||
|
@ -84,16 +83,6 @@ pub async fn remove_comment(
|
||||||
};
|
};
|
||||||
ModRemoveComment::create(&mut context.pool(), &form).await?;
|
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;
|
let updated_comment_id = updated_comment.id;
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
|
@ -111,7 +100,6 @@ pub async fn remove_comment(
|
||||||
&context,
|
&context,
|
||||||
updated_comment_id,
|
updated_comment_id,
|
||||||
Some(local_user_view),
|
Some(local_user_view),
|
||||||
recipient_ids,
|
|
||||||
local_instance_id,
|
local_instance_id,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
|
|
@ -2,15 +2,15 @@ use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
build_response::{build_comment_response, send_local_notifs},
|
build_response::build_comment_response,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::NotifyData,
|
||||||
plugins::{plugin_hook_after, plugin_hook_before},
|
plugins::{plugin_hook_after, plugin_hook_before},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_community_user_action, get_url_blocklist, process_markdown_opt, slur_regex},
|
utils::{check_community_user_action, get_url_blocklist, process_markdown_opt, slur_regex},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::validate_post_language,
|
impls::actor_language::validate_post_language,
|
||||||
newtypes::PostOrCommentId,
|
|
||||||
source::comment::{Comment, CommentUpdateForm},
|
source::comment::{Comment, CommentUpdateForm},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
@ -21,7 +21,7 @@ use lemmy_db_views_comment::{
|
||||||
use lemmy_db_views_local_user::LocalUserView;
|
use lemmy_db_views_local_user::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorType, LemmyResult},
|
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(
|
pub async fn update_comment(
|
||||||
|
@ -79,17 +79,14 @@ pub async fn update_comment(
|
||||||
plugin_hook_after("after_update_local_comment", &updated_comment)?;
|
plugin_hook_after("after_update_local_comment", &updated_comment)?;
|
||||||
|
|
||||||
// Do the mentions / recipients
|
// Do the mentions / recipients
|
||||||
let updated_comment_content = updated_comment.content.clone();
|
NotifyData::new(
|
||||||
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
&orig_comment.post,
|
||||||
let recipient_ids = send_local_notifs(
|
Some(&updated_comment),
|
||||||
mentions,
|
|
||||||
PostOrCommentId::Comment(comment_id),
|
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
&orig_comment.community,
|
||||||
false,
|
false,
|
||||||
&context,
|
|
||||||
Some(&local_user_view),
|
|
||||||
local_instance_id,
|
|
||||||
)
|
)
|
||||||
|
.send(&context)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
|
@ -102,7 +99,6 @@ pub async fn update_comment(
|
||||||
&context,
|
&context,
|
||||||
updated_comment.id,
|
updated_comment.id,
|
||||||
Some(local_user_view),
|
Some(local_user_view),
|
||||||
recipient_ids,
|
|
||||||
local_instance_id,
|
local_instance_id,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
|
|
|
@ -3,8 +3,9 @@ use crate::community_use_pending;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
build_response::{build_post_response, send_local_notifs},
|
build_response::build_post_response,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::NotifyData,
|
||||||
plugins::{plugin_hook_after, plugin_hook_before},
|
plugins::{plugin_hook_after, plugin_hook_before},
|
||||||
request::generate_post_link_metadata,
|
request::generate_post_link_metadata,
|
||||||
send_activity::SendActivityData,
|
send_activity::SendActivityData,
|
||||||
|
@ -21,7 +22,6 @@ use lemmy_api_utils::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::validate_post_language,
|
impls::actor_language::validate_post_language,
|
||||||
newtypes::PostOrCommentId,
|
|
||||||
source::post::{Post, PostActions, PostInsertForm, PostLikeForm, PostReadForm},
|
source::post::{Post, PostActions, PostInsertForm, PostLikeForm, PostReadForm},
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
utils::diesel_url_create,
|
utils::diesel_url_create,
|
||||||
|
@ -34,7 +34,6 @@ use lemmy_db_views_site::SiteView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::LemmyResult,
|
error::LemmyResult,
|
||||||
utils::{
|
utils::{
|
||||||
mention::scrape_text_for_mentions,
|
|
||||||
slurs::check_slurs,
|
slurs::check_slurs,
|
||||||
validation::{
|
validation::{
|
||||||
is_url_blocked,
|
is_url_blocked,
|
||||||
|
@ -169,23 +168,18 @@ pub async fn create_post(
|
||||||
// They like their own post by default
|
// They like their own post by default
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let post_id = inserted_post.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);
|
let like_form = PostLikeForm::new(post_id, person_id, 1);
|
||||||
|
|
||||||
PostActions::like(&mut context.pool(), &like_form).await?;
|
PostActions::like(&mut context.pool(), &like_form).await?;
|
||||||
|
|
||||||
// Scan the post body for user mentions, add those rows
|
NotifyData::new(
|
||||||
let mentions = scrape_text_for_mentions(&inserted_post.body.clone().unwrap_or_default());
|
&inserted_post,
|
||||||
let do_send_email = !local_site.disable_email_notifications;
|
None,
|
||||||
send_local_notifs(
|
|
||||||
mentions,
|
|
||||||
PostOrCommentId::Post(inserted_post.id),
|
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
do_send_email,
|
community,
|
||||||
&context,
|
!local_site.disable_email_notifications,
|
||||||
Some(&local_user_view),
|
|
||||||
local_instance_id,
|
|
||||||
)
|
)
|
||||||
|
.send(&context)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let read_form = PostReadForm::new(post_id, person_id);
|
let read_form = PostReadForm::new(post_id, person_id);
|
||||||
|
|
|
@ -3,8 +3,9 @@ use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
build_response::{build_post_response, send_local_notifs},
|
build_response::build_post_response,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::NotifyData,
|
||||||
plugins::{plugin_hook_after, plugin_hook_before},
|
plugins::{plugin_hook_after, plugin_hook_before},
|
||||||
request::generate_post_link_metadata,
|
request::generate_post_link_metadata,
|
||||||
send_activity::SendActivityData,
|
send_activity::SendActivityData,
|
||||||
|
@ -20,7 +21,6 @@ use lemmy_api_utils::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::validate_post_language,
|
impls::actor_language::validate_post_language,
|
||||||
newtypes::PostOrCommentId,
|
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
|
@ -38,7 +38,6 @@ use lemmy_db_views_site::SiteView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorType, LemmyResult},
|
error::{LemmyErrorType, LemmyResult},
|
||||||
utils::{
|
utils::{
|
||||||
mention::scrape_text_for_mentions,
|
|
||||||
slurs::check_slurs,
|
slurs::check_slurs,
|
||||||
validation::{
|
validation::{
|
||||||
is_url_blocked,
|
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?;
|
let updated_post = Post::update(&mut context.pool(), post_id, &post_form).await?;
|
||||||
plugin_hook_after("after_update_local_post", &post_form)?;
|
plugin_hook_after("after_update_local_post", &post_form)?;
|
||||||
|
|
||||||
// Scan the post body for user mentions, add those rows
|
NotifyData::new(
|
||||||
let mentions = scrape_text_for_mentions(&updated_post.body.clone().unwrap_or_default());
|
&updated_post,
|
||||||
send_local_notifs(
|
None,
|
||||||
mentions,
|
|
||||||
PostOrCommentId::Post(updated_post.id),
|
|
||||||
&local_user_view.person,
|
&local_user_view.person,
|
||||||
|
&orig_post.community,
|
||||||
false,
|
false,
|
||||||
&context,
|
|
||||||
Some(&local_user_view),
|
|
||||||
local_instance_id,
|
|
||||||
)
|
)
|
||||||
|
.send(&context)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// send out federation/webmention if necessary
|
// send out federation/webmention if necessary
|
||||||
|
|
|
@ -2,6 +2,7 @@ use activitypub_federation::config::Data;
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::notify_private_message,
|
||||||
plugins::{plugin_hook_after, plugin_hook_before},
|
plugins::{plugin_hook_after, plugin_hook_before},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{check_private_messages_enabled, get_url_blocklist, process_markdown, slur_regex},
|
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},
|
api::{CreatePrivateMessage, PrivateMessageResponse},
|
||||||
PrivateMessageView,
|
PrivateMessageView,
|
||||||
};
|
};
|
||||||
use lemmy_email::notifications::send_private_message_email;
|
|
||||||
use lemmy_utils::{error::LemmyResult, utils::validation::is_valid_body_field};
|
use lemmy_utils::{error::LemmyResult, utils::validation::is_valid_body_field};
|
||||||
|
|
||||||
pub async fn create_private_message(
|
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?;
|
let view = PrivateMessageView::read(&mut context.pool(), inserted_private_message.id).await?;
|
||||||
|
|
||||||
// Send email to the local recipient, if one exists
|
notify_private_message(&view, true, &context).await?;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::CreatePrivateMessage(view.clone()),
|
SendActivityData::CreatePrivateMessage(view.clone()),
|
||||||
|
|
|
@ -3,6 +3,7 @@ use actix_web::web::Json;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::notify_private_message,
|
||||||
plugins::{plugin_hook_after, plugin_hook_before},
|
plugins::{plugin_hook_after, plugin_hook_before},
|
||||||
send_activity::{ActivityChannel, SendActivityData},
|
send_activity::{ActivityChannel, SendActivityData},
|
||||||
utils::{get_url_blocklist, process_markdown, slur_regex},
|
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?;
|
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
|
||||||
|
|
||||||
|
notify_private_message(&view, false, &context).await?;
|
||||||
|
|
||||||
ActivityChannel::submit_activity(
|
ActivityChannel::submit_activity(
|
||||||
SendActivityData::UpdatePrivateMessage(view.clone()),
|
SendActivityData::UpdatePrivateMessage(view.clone()),
|
||||||
&context,
|
&context,
|
||||||
|
|
|
@ -77,8 +77,10 @@ webpage = { version = "2.0", default-features = false, features = ["serde"] }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
jsonwebtoken = { version = "9.3.1" }
|
jsonwebtoken = { version = "9.3.1" }
|
||||||
either.workspace = true
|
either.workspace = true
|
||||||
|
derive-new.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
|
lemmy_db_views_notification = { workspace = true, features = ["full"] }
|
||||||
diesel_ltree = { workspace = true }
|
diesel_ltree = { workspace = true }
|
||||||
|
|
|
@ -1,38 +1,19 @@
|
||||||
use crate::{
|
use crate::{context::LemmyContext, utils::is_mod_or_admin};
|
||||||
context::LemmyContext,
|
|
||||||
utils::{check_person_instance_community_block, is_mod_or_admin},
|
|
||||||
};
|
|
||||||
use actix_web::web::Json;
|
use actix_web::web::Json;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentId, CommunityId, InstanceId, LocalUserId, PostId, PostOrCommentId},
|
newtypes::{CommentId, CommunityId, InstanceId, PostId},
|
||||||
source::{
|
source::actor_language::CommunityLanguage,
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views_comment::{api::CommentResponse, CommentView};
|
use lemmy_db_views_comment::{api::CommentResponse, CommentView};
|
||||||
use lemmy_db_views_community::{api::CommunityResponse, CommunityView};
|
use lemmy_db_views_community::{api::CommunityResponse, CommunityView};
|
||||||
use lemmy_db_views_local_user::LocalUserView;
|
use lemmy_db_views_local_user::LocalUserView;
|
||||||
use lemmy_db_views_post::{api::PostResponse, PostView};
|
use lemmy_db_views_post::{api::PostResponse, PostView};
|
||||||
use lemmy_email::notifications::{
|
use lemmy_utils::error::LemmyResult;
|
||||||
send_comment_reply_email,
|
|
||||||
send_mention_email,
|
|
||||||
send_post_reply_email,
|
|
||||||
};
|
|
||||||
use lemmy_utils::{error::LemmyResult, utils::mention::MentionData};
|
|
||||||
|
|
||||||
pub async fn build_comment_response(
|
pub async fn build_comment_response(
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
comment_id: CommentId,
|
comment_id: CommentId,
|
||||||
local_user_view: Option<LocalUserView>,
|
local_user_view: Option<LocalUserView>,
|
||||||
recipient_ids: Vec<LocalUserId>,
|
|
||||||
local_instance_id: InstanceId,
|
local_instance_id: InstanceId,
|
||||||
) -> LemmyResult<CommentResponse> {
|
) -> LemmyResult<CommentResponse> {
|
||||||
let local_user = local_user_view.map(|l| l.local_user);
|
let local_user = local_user_view.map(|l| l.local_user);
|
||||||
|
@ -43,10 +24,7 @@ pub async fn build_comment_response(
|
||||||
local_instance_id,
|
local_instance_id,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(CommentResponse {
|
Ok(CommentResponse { comment_view })
|
||||||
comment_view,
|
|
||||||
recipient_ids,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build_community_response(
|
pub async fn build_community_response(
|
||||||
|
@ -93,221 +71,3 @@ pub async fn build_post_response(
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Json(PostResponse { post_view }))
|
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 build_response;
|
||||||
pub mod claims;
|
pub mod claims;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
pub mod notify;
|
||||||
pub mod plugins;
|
pub mod plugins;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod send_activity;
|
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,
|
ModRemovePostForm,
|
||||||
},
|
},
|
||||||
oauth_account::OAuthAccount,
|
oauth_account::OAuthAccount,
|
||||||
person::{Person, PersonActions, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
post::{Post, PostActions, PostReadCommentsForm},
|
post::{Post, PostActions, PostReadCommentsForm},
|
||||||
private_message::PrivateMessage,
|
private_message::PrivateMessage,
|
||||||
registration_application::RegistrationApplication,
|
registration_application::RegistrationApplication,
|
||||||
site::Site,
|
site::Site,
|
||||||
},
|
},
|
||||||
traits::{Blockable, Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
utils::DbPool,
|
utils::DbPool,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema_file::enums::{FederationMode, RegistrationMode};
|
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(
|
pub async fn check_local_vote_mode(
|
||||||
score: i16,
|
score: i16,
|
||||||
post_or_comment_id: PostOrCommentId,
|
post_or_comment_id: PostOrCommentId,
|
||||||
|
|
|
@ -15,12 +15,12 @@ name = "lemmy_apub"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
full = []
|
full = []
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_db_views_comment = { workspace = true, features = ["full"] }
|
lemmy_db_views_comment = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_community = { workspace = true, features = ["full"] }
|
lemmy_db_views_community = { workspace = true, features = ["full"] }
|
||||||
|
|
|
@ -14,8 +14,8 @@ use activitypub_federation::{
|
||||||
traits::{Activity, Actor, Object},
|
traits::{Activity, Actor, Object},
|
||||||
};
|
};
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
build_response::send_local_notifs,
|
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::NotifyData,
|
||||||
utils::{check_is_mod_or_admin, check_post_deleted_or_removed},
|
utils::{check_is_mod_or_admin, check_post_deleted_or_removed},
|
||||||
};
|
};
|
||||||
use lemmy_apub_objects::{
|
use lemmy_apub_objects::{
|
||||||
|
@ -27,7 +27,7 @@ use lemmy_apub_objects::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{PersonId, PostOrCommentId},
|
newtypes::PersonId,
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
comment::{Comment, CommentActions, CommentLikeForm},
|
comment::{Comment, CommentActions, CommentLikeForm},
|
||||||
|
@ -38,10 +38,7 @@ use lemmy_db_schema::{
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_site::SiteView;
|
use lemmy_db_views_site::SiteView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
error::{LemmyError, LemmyResult},
|
|
||||||
utils::mention::scrape_text_for_mentions,
|
|
||||||
};
|
|
||||||
use serde_json::{from_value, to_value};
|
use serde_json::{from_value, to_value};
|
||||||
use url::Url;
|
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
|
// Need to do this check here instead of Note::from_json because we need the person who
|
||||||
// send the activity, not the comment author.
|
// send the activity, not the comment author.
|
||||||
let existing_comment = self.object.id.dereference_local(context).await.ok();
|
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)) =
|
if let (Some(distinguished), Some(existing_comment)) =
|
||||||
(self.object.distinguished, existing_comment)
|
(self.object.distinguished, existing_comment)
|
||||||
{
|
{
|
||||||
if distinguished != existing_comment.distinguished {
|
if distinguished != existing_comment.distinguished {
|
||||||
let creator = self.actor.dereference(context).await?;
|
let creator = self.actor.dereference(context).await?;
|
||||||
let (post, _) = self.object.get_parents(context).await?;
|
|
||||||
check_is_mod_or_admin(
|
check_is_mod_or_admin(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
creator.id,
|
creator.id,
|
||||||
|
@ -172,19 +169,10 @@ impl Activity for CreateOrUpdateNote {
|
||||||
// anyway.
|
// anyway.
|
||||||
// TODO: for compatibility with other projects, it would be much better to read this from cc or
|
// TODO: for compatibility with other projects, it would be much better to read this from cc or
|
||||||
// tags
|
// tags
|
||||||
let mentions = scrape_text_for_mentions(&comment.content);
|
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||||
// TODO: this fails in local community comment as CommentView::read() returns nothing
|
NotifyData::new(&post.0, Some(&comment.0), &actor, &community, do_send_email)
|
||||||
// without passing LocalUser
|
.send(context)
|
||||||
send_local_notifs(
|
.await?;
|
||||||
mentions,
|
|
||||||
PostOrCommentId::Comment(comment.id),
|
|
||||||
&actor,
|
|
||||||
do_send_email,
|
|
||||||
context,
|
|
||||||
None,
|
|
||||||
local_instance_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use activitypub_federation::{
|
||||||
protocol::verification::{verify_domains_match, verify_urls_match},
|
protocol::verification::{verify_domains_match, verify_urls_match},
|
||||||
traits::{Activity, Object},
|
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::{
|
use lemmy_apub_objects::{
|
||||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||||
utils::{
|
utils::{
|
||||||
|
@ -21,7 +21,7 @@ use lemmy_apub_objects::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{PersonId, PostOrCommentId},
|
newtypes::PersonId,
|
||||||
source::{
|
source::{
|
||||||
activity::ActivitySendTargets,
|
activity::ActivitySendTargets,
|
||||||
community::Community,
|
community::Community,
|
||||||
|
@ -31,10 +31,7 @@ use lemmy_db_schema::{
|
||||||
traits::{Crud, Likeable},
|
traits::{Crud, Likeable},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_site::SiteView;
|
use lemmy_db_views_site::SiteView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
error::{LemmyError, LemmyResult},
|
|
||||||
utils::mention::scrape_text_for_mentions,
|
|
||||||
};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
impl CreateOrUpdatePage {
|
impl CreateOrUpdatePage {
|
||||||
|
@ -110,7 +107,6 @@ impl Activity for CreateOrUpdatePage {
|
||||||
|
|
||||||
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
|
async fn receive(self, context: &Data<LemmyContext>) -> LemmyResult<()> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
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?;
|
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;
|
self.kind == CreateOrUpdateType::Create && !site_view.local_site.disable_email_notifications;
|
||||||
let actor = self.actor.dereference(context).await?;
|
let actor = self.actor.dereference(context).await?;
|
||||||
|
|
||||||
// Send the post body mentions
|
let community = Community::read(&mut context.pool(), post.community_id).await?;
|
||||||
let mentions = scrape_text_for_mentions(&post.body.clone().unwrap_or_default());
|
NotifyData::new(&post.0, None, &actor, &community, do_send_email)
|
||||||
send_local_notifs(
|
.send(context)
|
||||||
mentions,
|
.await?;
|
||||||
PostOrCommentId::Post(post.id),
|
|
||||||
&actor,
|
|
||||||
do_send_email,
|
|
||||||
context,
|
|
||||||
None,
|
|
||||||
local_instance_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,17 +15,18 @@ name = "lemmy_apub_objects"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
full = []
|
full = []
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_db_views_community_moderator = { workspace = true, features = ["full"] }
|
lemmy_db_views_community_moderator = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_community_person_ban = { 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_local_user = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_site = { 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_utils = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_schema = { workspace = true, features = ["full"] }
|
lemmy_db_schema = { workspace = true, features = ["full"] }
|
||||||
lemmy_api_utils = { workspace = true, features = ["full"] }
|
lemmy_api_utils = { workspace = true, features = ["full"] }
|
||||||
|
|
|
@ -17,6 +17,7 @@ use activitypub_federation::{
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use lemmy_api_utils::{
|
use lemmy_api_utils::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
notify::notify_private_message,
|
||||||
plugins::{plugin_hook_after, plugin_hook_before},
|
plugins::{plugin_hook_after, plugin_hook_before},
|
||||||
utils::{check_private_messages_enabled, get_url_blocklist, process_markdown, slur_regex},
|
utils::{check_private_messages_enabled, get_url_blocklist, process_markdown, slur_regex},
|
||||||
};
|
};
|
||||||
|
@ -29,6 +30,7 @@ use lemmy_db_schema::{
|
||||||
traits::{Blockable, Crud},
|
traits::{Blockable, Crud},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_local_user::LocalUserView;
|
use lemmy_db_views_local_user::LocalUserView;
|
||||||
|
use lemmy_db_views_private_message::PrivateMessageView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorType, LemmyResult},
|
error::{LemmyError, LemmyErrorType, LemmyResult},
|
||||||
utils::markdown::markdown_to_html,
|
utils::markdown::markdown_to_html,
|
||||||
|
@ -158,7 +160,6 @@ impl Object for ApubPrivateMessage {
|
||||||
published_at: note.published,
|
published_at: note.published,
|
||||||
updated_at: note.updated,
|
updated_at: note.updated,
|
||||||
deleted: Some(false),
|
deleted: Some(false),
|
||||||
read: None,
|
|
||||||
ap_id: Some(note.id.into()),
|
ap_id: Some(note.id.into()),
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
};
|
};
|
||||||
|
@ -166,6 +167,8 @@ impl Object for ApubPrivateMessage {
|
||||||
let timestamp = note.updated.or(note.published).unwrap_or_else(Utc::now);
|
let timestamp = note.updated.or(note.published).unwrap_or_else(Utc::now);
|
||||||
let pm = DbPrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?;
|
let pm = DbPrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?;
|
||||||
plugin_hook_after("after_receive_federated_private_message", &pm)?;
|
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())
|
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_async::RunQueryDsl;
|
||||||
use diesel_uplete::{uplete, UpleteCount};
|
use diesel_uplete::{uplete, UpleteCount};
|
||||||
use lemmy_db_schema_file::{
|
use lemmy_db_schema_file::{
|
||||||
enums::{CommunityFollowerState, CommunityVisibility, ListingType},
|
enums::{CommunityFollowerState, CommunityNotificationsMode, CommunityVisibility, ListingType},
|
||||||
schema::{comment, community, community_actions, instance, post},
|
schema::{comment, community, community_actions, instance, local_user, post},
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
|
@ -455,6 +455,61 @@ impl CommunityActions {
|
||||||
.await
|
.await
|
||||||
.map_err(|_e: Arc<LemmyError>| LemmyErrorType::NotFound.into())
|
.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 {
|
impl Bannable for CommunityActions {
|
||||||
|
|
|
@ -2,7 +2,6 @@ pub mod activity;
|
||||||
pub mod actor_language;
|
pub mod actor_language;
|
||||||
pub mod captcha_answer;
|
pub mod captcha_answer;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod comment_reply;
|
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_report;
|
pub mod community_report;
|
||||||
|
@ -22,12 +21,11 @@ pub mod local_user;
|
||||||
pub mod login_token;
|
pub mod login_token;
|
||||||
pub mod mod_log;
|
pub mod mod_log;
|
||||||
pub mod multi_community;
|
pub mod multi_community;
|
||||||
|
pub mod notification;
|
||||||
pub mod oauth_account;
|
pub mod oauth_account;
|
||||||
pub mod oauth_provider;
|
pub mod oauth_provider;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod person_comment_mention;
|
|
||||||
pub mod person_post_mention;
|
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_report;
|
pub mod post_report;
|
||||||
pub mod post_tag;
|
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,
|
SITEMAP_LIMIT,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use ::url::Url;
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
dsl::{count, insert_into, not, update},
|
dsl::{count, insert_into, not, update},
|
||||||
|
@ -39,11 +38,15 @@ use diesel::{
|
||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use diesel_uplete::{uplete, UpleteCount};
|
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::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
|
||||||
settings::structs::Settings,
|
settings::structs::Settings,
|
||||||
};
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
impl Crud for Post {
|
impl Crud for Post {
|
||||||
type InsertForm = PostInsertForm;
|
type InsertForm = PostInsertForm;
|
||||||
|
@ -542,9 +545,7 @@ impl PostActions {
|
||||||
.map(|post_id| (PostReadForm::new(*post_id, person_id)))
|
.map(|post_id| (PostReadForm::new(*post_id, person_id)))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl PostActions {
|
|
||||||
pub async fn read(
|
pub async fn read(
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
post_id: PostId,
|
post_id: PostId,
|
||||||
|
@ -570,6 +571,45 @@ impl PostActions {
|
||||||
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
|
.ok_or(LemmyErrorType::CouldntParsePaginationToken)?;
|
||||||
Self::read(pool, PostId(*post_id), PersonId(*person_id)).await
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -63,22 +63,6 @@ impl PrivateMessage {
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePrivateMessage)
|
.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(
|
pub async fn read_from_apub_id(
|
||||||
pool: &mut DbPool<'_>,
|
pool: &mut DbPool<'_>,
|
||||||
object_id: Url,
|
object_id: Url,
|
||||||
|
@ -161,7 +145,6 @@ mod tests {
|
||||||
creator_id: inserted_creator.id,
|
creator_id: inserted_creator.id,
|
||||||
recipient_id: inserted_recipient.id,
|
recipient_id: inserted_recipient.id,
|
||||||
deleted: false,
|
deleted: false,
|
||||||
read: false,
|
|
||||||
updated_at: None,
|
updated_at: None,
|
||||||
published_at: inserted_private_message.published_at,
|
published_at: inserted_private_message.published_at,
|
||||||
ap_id: Url::parse(&format!(
|
ap_id: Url::parse(&format!(
|
||||||
|
@ -195,15 +178,6 @@ mod tests {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.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_creator.id).await?;
|
||||||
Person::delete(pool, inserted_recipient.id).await?;
|
Person::delete(pool, inserted_recipient.id).await?;
|
||||||
Instance::delete(pool, inserted_instance.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, updated_private_message);
|
||||||
assert_eq!(expected_private_message, inserted_private_message);
|
assert_eq!(expected_private_message, inserted_private_message);
|
||||||
assert!(deleted_private_message.deleted);
|
assert!(deleted_private_message.deleted);
|
||||||
assert!(marked_read_private_message.read);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,12 +117,12 @@ pub enum ModlogActionType {
|
||||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||||
#[cfg_attr(feature = "ts-rs", ts(export))]
|
#[cfg_attr(feature = "ts-rs", ts(export))]
|
||||||
/// A list of possible types for the inbox.
|
/// A list of possible types for the inbox.
|
||||||
pub enum InboxDataType {
|
pub enum NotificationDataType {
|
||||||
All,
|
All,
|
||||||
CommentReply,
|
Reply,
|
||||||
CommentMention,
|
Mention,
|
||||||
PostMention,
|
|
||||||
PrivateMessage,
|
PrivateMessage,
|
||||||
|
Subscribed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
#[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 = "full", derive(DieselNewType))]
|
||||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
||||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||||
/// The person comment mention id.
|
pub struct NotificationId(pub i32);
|
||||||
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);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||||
|
@ -145,13 +137,6 @@ pub struct SiteId(pub i32);
|
||||||
/// The language id.
|
/// The language id.
|
||||||
pub struct LanguageId(pub i32);
|
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(
|
#[derive(
|
||||||
Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default, Ord, PartialOrd,
|
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))]
|
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||||
pub struct ModlogCombinedId(i32);
|
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)]
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||||
/// The search combined id
|
/// 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 modlog;
|
||||||
pub mod person_content;
|
pub mod person_content;
|
||||||
pub mod person_liked;
|
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,
|
source::placeholder_apub_url,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
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::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
@ -210,6 +214,7 @@ pub struct CommunityActions {
|
||||||
pub received_ban_at: Option<DateTime<Utc>>,
|
pub received_ban_at: Option<DateTime<Utc>>,
|
||||||
/// When their ban expires.
|
/// When their ban expires.
|
||||||
pub ban_expires_at: Option<DateTime<Utc>>,
|
pub ban_expires_at: Option<DateTime<Utc>>,
|
||||||
|
pub notifications: Option<CommunityNotificationsMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, derive_new::new)]
|
#[derive(Clone, derive_new::new)]
|
||||||
|
|
|
@ -7,7 +7,6 @@ pub mod actor_language;
|
||||||
pub mod captcha_answer;
|
pub mod captcha_answer;
|
||||||
pub mod combined;
|
pub mod combined;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod comment_reply;
|
|
||||||
pub mod comment_report;
|
pub mod comment_report;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod community_report;
|
pub mod community_report;
|
||||||
|
@ -28,12 +27,11 @@ pub mod local_user;
|
||||||
pub mod login_token;
|
pub mod login_token;
|
||||||
pub mod mod_log;
|
pub mod mod_log;
|
||||||
pub mod multi_community;
|
pub mod multi_community;
|
||||||
|
pub mod notification;
|
||||||
pub mod oauth_account;
|
pub mod oauth_account;
|
||||||
pub mod oauth_provider;
|
pub mod oauth_provider;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod person_comment_mention;
|
|
||||||
pub mod person_post_mention;
|
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_report;
|
pub mod post_report;
|
||||||
pub mod post_tag;
|
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 crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use lemmy_db_schema_file::enums::PostNotificationsMode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
@ -201,6 +202,7 @@ pub struct PostActions {
|
||||||
pub like_score: Option<i16>,
|
pub like_score: Option<i16>,
|
||||||
/// When the post was hidden.
|
/// When the post was hidden.
|
||||||
pub hidden_at: Option<DateTime<Utc>>,
|
pub hidden_at: Option<DateTime<Utc>>,
|
||||||
|
pub notifications: Option<PostNotificationsMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, derive_new::new)]
|
#[derive(Clone, derive_new::new)]
|
||||||
|
|
|
@ -26,7 +26,6 @@ pub struct PrivateMessage {
|
||||||
pub recipient_id: PersonId,
|
pub recipient_id: PersonId,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub deleted: bool,
|
pub deleted: bool,
|
||||||
pub read: bool,
|
|
||||||
pub published_at: DateTime<Utc>,
|
pub published_at: DateTime<Utc>,
|
||||||
pub updated_at: Option<DateTime<Utc>>,
|
pub updated_at: Option<DateTime<Utc>>,
|
||||||
pub ap_id: DbUrl,
|
pub ap_id: DbUrl,
|
||||||
|
@ -47,8 +46,6 @@ pub struct PrivateMessageInsertForm {
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
pub deleted: Option<bool>,
|
pub deleted: Option<bool>,
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
pub read: Option<bool>,
|
|
||||||
#[new(default)]
|
|
||||||
pub published_at: Option<DateTime<Utc>>,
|
pub published_at: Option<DateTime<Utc>>,
|
||||||
#[new(default)]
|
#[new(default)]
|
||||||
pub updated_at: Option<DateTime<Utc>>,
|
pub updated_at: Option<DateTime<Utc>>,
|
||||||
|
@ -64,7 +61,6 @@ pub struct PrivateMessageInsertForm {
|
||||||
pub struct PrivateMessageUpdateForm {
|
pub struct PrivateMessageUpdateForm {
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
pub deleted: Option<bool>,
|
pub deleted: Option<bool>,
|
||||||
pub read: Option<bool>,
|
|
||||||
pub published_at: Option<DateTime<Utc>>,
|
pub published_at: Option<DateTime<Utc>>,
|
||||||
pub updated_at: Option<Option<DateTime<Utc>>>,
|
pub updated_at: Option<Option<DateTime<Utc>>>,
|
||||||
pub ap_id: Option<DbUrl>,
|
pub ap_id: Option<DbUrl>,
|
||||||
|
|
|
@ -226,3 +226,59 @@ pub enum VoteShow {
|
||||||
ShowForOthers,
|
ShowForOthers,
|
||||||
Hide,
|
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"))]
|
#[diesel(postgres_type(name = "community_follower_state"))]
|
||||||
pub struct CommunityFollowerState;
|
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)]
|
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||||
#[diesel(postgres_type(name = "community_visibility"))]
|
#[diesel(postgres_type(name = "community_visibility"))]
|
||||||
pub struct CommunityVisibility;
|
pub struct CommunityVisibility;
|
||||||
|
@ -29,10 +33,18 @@ pub mod sql_types {
|
||||||
#[diesel(postgres_type(name = "ltree"))]
|
#[diesel(postgres_type(name = "ltree"))]
|
||||||
pub struct 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)]
|
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||||
#[diesel(postgres_type(name = "post_listing_mode_enum"))]
|
#[diesel(postgres_type(name = "post_listing_mode_enum"))]
|
||||||
pub struct PostListingModeEnum;
|
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)]
|
#[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)]
|
||||||
#[diesel(postgres_type(name = "post_sort_type_enum"))]
|
#[diesel(postgres_type(name = "post_sort_type_enum"))]
|
||||||
pub struct PostSortTypeEnum;
|
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! {
|
diesel::table! {
|
||||||
comment_report (id) {
|
comment_report (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -238,6 +240,7 @@ diesel::table! {
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
use diesel::sql_types::*;
|
use diesel::sql_types::*;
|
||||||
use super::sql_types::CommunityFollowerState;
|
use super::sql_types::CommunityFollowerState;
|
||||||
|
use super::sql_types::CommunityNotificationsModeEnum;
|
||||||
|
|
||||||
community_actions (person_id, community_id) {
|
community_actions (person_id, community_id) {
|
||||||
community_id -> Int4,
|
community_id -> Int4,
|
||||||
|
@ -249,6 +252,7 @@ diesel::table! {
|
||||||
became_moderator_at -> Nullable<Timestamptz>,
|
became_moderator_at -> Nullable<Timestamptz>,
|
||||||
received_ban_at -> Nullable<Timestamptz>,
|
received_ban_at -> Nullable<Timestamptz>,
|
||||||
ban_expires_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! {
|
diesel::table! {
|
||||||
instance (id) {
|
instance (id) {
|
||||||
id -> Int4,
|
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! {
|
diesel::table! {
|
||||||
oauth_account (oauth_provider_id, local_user_id) {
|
oauth_account (oauth_provider_id, local_user_id) {
|
||||||
local_user_id -> Int4,
|
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! {
|
diesel::table! {
|
||||||
person_content_combined (id) {
|
person_content_combined (id) {
|
||||||
id -> Int4,
|
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! {
|
diesel::table! {
|
||||||
person_saved_combined (id) {
|
person_saved_combined (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -934,6 +923,9 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
use super::sql_types::PostNotificationsModeEnum;
|
||||||
|
|
||||||
post_actions (person_id, post_id) {
|
post_actions (person_id, post_id) {
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
person_id -> Int4,
|
person_id -> Int4,
|
||||||
|
@ -944,6 +936,7 @@ diesel::table! {
|
||||||
liked_at -> Nullable<Timestamptz>,
|
liked_at -> Nullable<Timestamptz>,
|
||||||
like_score -> Nullable<Int2>,
|
like_score -> Nullable<Int2>,
|
||||||
hidden_at -> Nullable<Timestamptz>,
|
hidden_at -> Nullable<Timestamptz>,
|
||||||
|
notifications -> Nullable<PostNotificationsModeEnum>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -980,7 +973,6 @@ diesel::table! {
|
||||||
recipient_id -> Int4,
|
recipient_id -> Int4,
|
||||||
content -> Text,
|
content -> Text,
|
||||||
deleted -> Bool,
|
deleted -> Bool,
|
||||||
read -> Bool,
|
|
||||||
published_at -> Timestamptz,
|
published_at -> Timestamptz,
|
||||||
updated_at -> Nullable<Timestamptz>,
|
updated_at -> Nullable<Timestamptz>,
|
||||||
#[max_length = 255]
|
#[max_length = 255]
|
||||||
|
@ -1145,8 +1137,6 @@ diesel::joinable!(comment -> person (creator_id));
|
||||||
diesel::joinable!(comment -> post (post_id));
|
diesel::joinable!(comment -> post (post_id));
|
||||||
diesel::joinable!(comment_actions -> comment (comment_id));
|
diesel::joinable!(comment_actions -> comment (comment_id));
|
||||||
diesel::joinable!(comment_actions -> person (person_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!(comment_report -> comment (comment_id));
|
||||||
diesel::joinable!(community -> instance (instance_id));
|
diesel::joinable!(community -> instance (instance_id));
|
||||||
diesel::joinable!(community_actions -> community (community_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_allowlist -> instance (instance_id));
|
||||||
diesel::joinable!(federation_blocklist -> instance (instance_id));
|
diesel::joinable!(federation_blocklist -> instance (instance_id));
|
||||||
diesel::joinable!(federation_queue_state -> 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 -> instance (instance_id));
|
||||||
diesel::joinable!(instance_actions -> person (person_id));
|
diesel::joinable!(instance_actions -> person (person_id));
|
||||||
diesel::joinable!(local_image -> 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_entry -> multi_community (multi_community_id));
|
||||||
diesel::joinable!(multi_community_follow -> 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!(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 -> local_user (local_user_id));
|
||||||
diesel::joinable!(oauth_account -> oauth_provider (oauth_provider_id));
|
diesel::joinable!(oauth_account -> oauth_provider (oauth_provider_id));
|
||||||
diesel::joinable!(password_reset_request -> local_user (local_user_id));
|
diesel::joinable!(password_reset_request -> local_user (local_user_id));
|
||||||
diesel::joinable!(person -> instance (instance_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 -> comment (comment_id));
|
||||||
diesel::joinable!(person_content_combined -> post (post_id));
|
diesel::joinable!(person_content_combined -> post (post_id));
|
||||||
diesel::joinable!(person_liked_combined -> comment (comment_id));
|
diesel::joinable!(person_liked_combined -> comment (comment_id));
|
||||||
diesel::joinable!(person_liked_combined -> person (person_id));
|
diesel::joinable!(person_liked_combined -> person (person_id));
|
||||||
diesel::joinable!(person_liked_combined -> post (post_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 -> comment (comment_id));
|
||||||
diesel::joinable!(person_saved_combined -> person (person_id));
|
diesel::joinable!(person_saved_combined -> person (person_id));
|
||||||
diesel::joinable!(person_saved_combined -> post (post_id));
|
diesel::joinable!(person_saved_combined -> post (post_id));
|
||||||
|
@ -1265,7 +1251,6 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
captcha_answer,
|
captcha_answer,
|
||||||
comment,
|
comment,
|
||||||
comment_actions,
|
comment_actions,
|
||||||
comment_reply,
|
|
||||||
comment_report,
|
comment_report,
|
||||||
community,
|
community,
|
||||||
community_actions,
|
community_actions,
|
||||||
|
@ -1278,7 +1263,6 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
federation_blocklist,
|
federation_blocklist,
|
||||||
federation_queue_state,
|
federation_queue_state,
|
||||||
image_details,
|
image_details,
|
||||||
inbox_combined,
|
|
||||||
instance,
|
instance,
|
||||||
instance_actions,
|
instance_actions,
|
||||||
language,
|
language,
|
||||||
|
@ -1305,15 +1289,14 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
multi_community,
|
multi_community,
|
||||||
multi_community_entry,
|
multi_community_entry,
|
||||||
multi_community_follow,
|
multi_community_follow,
|
||||||
|
notification,
|
||||||
oauth_account,
|
oauth_account,
|
||||||
oauth_provider,
|
oauth_provider,
|
||||||
password_reset_request,
|
password_reset_request,
|
||||||
person,
|
person,
|
||||||
person_actions,
|
person_actions,
|
||||||
person_comment_mention,
|
|
||||||
person_content_combined,
|
person_content_combined,
|
||||||
person_liked_combined,
|
person_liked_combined,
|
||||||
person_post_mention,
|
|
||||||
person_saved_combined,
|
person_saved_combined,
|
||||||
post,
|
post,
|
||||||
post_actions,
|
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_community');
|
||||||
CALL r.create_modlog_combined_trigger ('mod_remove_post');
|
CALL r.create_modlog_combined_trigger ('mod_remove_post');
|
||||||
CALL r.create_modlog_combined_trigger ('mod_transfer_community');
|
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
|
-- Prevent using delete instead of uplete on action tables
|
||||||
CREATE FUNCTION r.require_uplete ()
|
CREATE FUNCTION r.require_uplete ()
|
||||||
RETURNS TRIGGER
|
RETURNS TRIGGER
|
||||||
|
|
|
@ -519,7 +519,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_test_data(conn: &mut PgConnection) -> LemmyResult<()> {
|
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
|
// Check users
|
||||||
let users: Vec<(i32, String, Option<String>, String, String)> = person::table
|
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
|
assert_eq!(comments[1].6, 0); // Zero upvotes
|
||||||
|
|
||||||
// Check comment replies
|
// Check comment replies
|
||||||
let replies: Vec<(i32, i32)> = comment_reply::table
|
let replies: Vec<(Option<i32>, i32)> = notification::table
|
||||||
.select((comment_reply::comment_id, comment_reply::recipient_id))
|
.select((notification::comment_id, notification::recipient_id))
|
||||||
.order_by(comment_reply::comment_id)
|
.order_by(notification::comment_id)
|
||||||
.load(conn)
|
.load(conn)
|
||||||
.map_err(|e| anyhow!("Failed to read comment replies: {}", e))?;
|
.map_err(|e| anyhow!("Failed to read comment replies: {}", e))?;
|
||||||
|
|
||||||
assert_eq!(replies.len(), 2);
|
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[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);
|
assert_eq!(replies[1].1, TEST_USER_ID_2);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
use crate::{CommentSlimView, CommentView};
|
use crate::{CommentSlimView, CommentView};
|
||||||
use lemmy_db_schema::newtypes::{
|
use lemmy_db_schema::newtypes::{CommentId, CommunityId, LanguageId, PaginationCursor, PostId};
|
||||||
CommentId,
|
|
||||||
CommunityId,
|
|
||||||
LanguageId,
|
|
||||||
LocalUserId,
|
|
||||||
PaginationCursor,
|
|
||||||
PostId,
|
|
||||||
};
|
|
||||||
use lemmy_db_schema_file::enums::{CommentSortType, ListingType};
|
use lemmy_db_schema_file::enums::{CommentSortType, ListingType};
|
||||||
use lemmy_db_views_vote::VoteView;
|
use lemmy_db_views_vote::VoteView;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -19,7 +12,6 @@ use serde_with::skip_serializing_none;
|
||||||
/// A comment response.
|
/// A comment response.
|
||||||
pub struct CommentResponse {
|
pub struct CommentResponse {
|
||||||
pub comment_view: CommentView,
|
pub comment_view: CommentView,
|
||||||
pub recipient_ids: Vec<LocalUserId>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_db_schema::{
|
||||||
source::site::Site,
|
source::site::Site,
|
||||||
CommunitySortType,
|
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_community_moderator::CommunityModeratorView;
|
||||||
use lemmy_db_views_person::PersonView;
|
use lemmy_db_views_person::PersonView;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -364,3 +364,12 @@ pub struct FollowMultiCommunity {
|
||||||
pub multi_community_id: MultiCommunityId,
|
pub multi_community_id: MultiCommunityId,
|
||||||
pub follow: bool,
|
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]
|
[package]
|
||||||
name = "lemmy_db_views_inbox_combined"
|
name = "lemmy_db_views_notification"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
description.workspace = true
|
description.workspace = true
|
||||||
|
@ -23,19 +23,16 @@ full = [
|
||||||
"i-love-jesus",
|
"i-love-jesus",
|
||||||
"lemmy_db_schema/full",
|
"lemmy_db_schema/full",
|
||||||
"lemmy_db_schema_file/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]
|
[dependencies]
|
||||||
lemmy_db_views_private_message = { workspace = true }
|
|
||||||
lemmy_db_schema = { workspace = true }
|
lemmy_db_schema = { workspace = true }
|
||||||
lemmy_utils = { workspace = true, optional = true }
|
lemmy_utils = { workspace = true, optional = true }
|
||||||
lemmy_db_schema_file = { workspace = 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 = { workspace = true, optional = true }
|
||||||
diesel-async = { workspace = true, optional = true }
|
diesel-async = { workspace = true, optional = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
@ -44,6 +41,3 @@ i-love-jesus = { workspace = true, optional = true }
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = { workspace = true }
|
|
||||||
serial_test = { workspace = true }
|
|
||||||
tokio = { workspace = true }
|
|
|
@ -1,9 +1,4 @@
|
||||||
use lemmy_db_schema::newtypes::{
|
use lemmy_db_schema::newtypes::{NotificationId, PrivateMessageId};
|
||||||
CommentReplyId,
|
|
||||||
PersonCommentMentionId,
|
|
||||||
PersonPostMentionId,
|
|
||||||
PrivateMessageId,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[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", derive(ts_rs::TS))]
|
||||||
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||||
/// Mark a comment reply as read.
|
/// Mark a comment reply as read.
|
||||||
pub struct MarkCommentReplyAsRead {
|
pub struct MarkNotificationAsRead {
|
||||||
pub comment_reply_id: CommentReplyId,
|
pub notification_id: NotificationId,
|
||||||
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 read: bool,
|
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,
|
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_community::CommunityView;
|
||||||
use lemmy_db_views_vote::VoteView;
|
use lemmy_db_views_vote::VoteView;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -93,6 +93,15 @@ pub struct FeaturePost {
|
||||||
pub feature_type: PostFeatureType,
|
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]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
|
#[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))]
|
#[cfg_attr(feature = "ts-rs", ts(optional_fields, export))]
|
||||||
/// Your exported data.
|
/// Your exported data.
|
||||||
pub struct ExportDataResponse {
|
pub struct ExportDataResponse {
|
||||||
pub inbox: Vec<PostOrCommentOrPrivateMessage>,
|
pub notifications: Vec<PostOrCommentOrPrivateMessage>,
|
||||||
pub content: Vec<PostOrCommentOrPrivateMessage>,
|
pub content: Vec<PostOrCommentOrPrivateMessage>,
|
||||||
pub read_posts: Vec<Url>,
|
pub read_posts: Vec<Url>,
|
||||||
pub liked: Vec<Url>,
|
pub liked: Vec<Url>,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{inbox_link, send_email, user_language};
|
use crate::{inbox_link, send_email, user_language};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::DbUrl,
|
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_db_views_local_user::LocalUserView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
|
@ -30,69 +30,96 @@ pub async fn send_mention_email(
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_comment_reply_email(
|
pub async fn send_post_subscribed_email(
|
||||||
parent_user_view: &LocalUserView,
|
user_view: &LocalUserView,
|
||||||
comment: &Comment,
|
|
||||||
person: &Person,
|
|
||||||
parent_comment: &Comment,
|
|
||||||
post: &Post,
|
post: &Post,
|
||||||
|
comment: &Comment,
|
||||||
|
link: DbUrl,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
) -> LemmyResult<()> {
|
) {
|
||||||
let inbox_link = inbox_link(settings);
|
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);
|
let content = markdown_to_html(&comment.content);
|
||||||
send_email_to_user(
|
send_email_to_user(
|
||||||
parent_user_view,
|
user_view,
|
||||||
&lang.notification_comment_reply_subject(&person.name),
|
&lang.notification_post_subscribed_subject(&post.name),
|
||||||
&lang.notification_comment_reply_body(
|
&lang.notification_post_subscribed_body(&content, &link, inbox_link),
|
||||||
comment.local_url(settings)?,
|
|
||||||
&content,
|
|
||||||
&inbox_link,
|
|
||||||
&parent_comment.content,
|
|
||||||
&post.name,
|
|
||||||
&person.name,
|
|
||||||
),
|
|
||||||
settings,
|
settings,
|
||||||
)
|
)
|
||||||
.await;
|
.await
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
parent_user_view: &LocalUserView,
|
||||||
comment: &Comment,
|
comment: &Comment,
|
||||||
person: &Person,
|
person: &Person,
|
||||||
|
parent_comment: &Option<Comment>,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
let inbox_link = inbox_link(settings);
|
let inbox_link = inbox_link(settings);
|
||||||
let lang = user_language(parent_user_view);
|
let lang = user_language(parent_user_view);
|
||||||
let content = markdown_to_html(&comment.content);
|
let content = markdown_to_html(&comment.content);
|
||||||
send_email_to_user(
|
let (subject, body) = if let Some(parent_comment) = parent_comment {
|
||||||
parent_user_view,
|
(
|
||||||
&lang.notification_post_reply_subject(&person.name),
|
lang.notification_comment_reply_subject(&person.name),
|
||||||
&lang.notification_post_reply_body(
|
lang.notification_comment_reply_body(
|
||||||
comment.local_url(settings)?,
|
comment.local_url(settings)?,
|
||||||
&content,
|
&content,
|
||||||
&inbox_link,
|
&inbox_link,
|
||||||
&post.name,
|
&parent_comment.content,
|
||||||
&person.name,
|
&post.name,
|
||||||
),
|
&person.name,
|
||||||
settings,
|
),
|
||||||
)
|
)
|
||||||
.await;
|
} 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_private_message_email(
|
pub async fn send_private_message_email(
|
||||||
sender: &LocalUserView,
|
sender: &Person,
|
||||||
local_recipient: &LocalUserView,
|
local_recipient: &LocalUserView,
|
||||||
content: &str,
|
content: &str,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
) {
|
) {
|
||||||
let inbox_link = inbox_link(settings);
|
let inbox_link = inbox_link(settings);
|
||||||
let lang = user_language(local_recipient);
|
let lang = user_language(local_recipient);
|
||||||
let sender_name = &sender.person.name;
|
let sender_name = &sender.name;
|
||||||
let content = markdown_to_html(content);
|
let content = markdown_to_html(content);
|
||||||
send_email_to_user(
|
send_email_to_user(
|
||||||
local_recipient,
|
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_post = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_local_image = { 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_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_modlog_combined = { workspace = true, features = ["full"] }
|
||||||
lemmy_db_views_person_content_combined = { workspace = true, features = [
|
lemmy_db_views_person_content_combined = { workspace = true, features = [
|
||||||
"full",
|
"full",
|
||||||
|
|
|
@ -11,8 +11,8 @@ use lemmy_db_schema::{
|
||||||
PersonContentType,
|
PersonContentType,
|
||||||
};
|
};
|
||||||
use lemmy_db_schema_file::enums::{ListingType, PostSortType};
|
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_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_person_content_combined::impls::PersonContentCombinedQuery;
|
||||||
use lemmy_db_views_post::{impls::PostQuery, PostView};
|
use lemmy_db_views_post::{impls::PostQuery, PostView};
|
||||||
use lemmy_db_views_site::SiteView;
|
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> {
|
async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult<Channel> {
|
||||||
let site_view = SiteView::read_local(&mut context.pool()).await?;
|
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 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);
|
let show_bot_accounts = Some(local_user.local_user.show_bot_accounts);
|
||||||
|
|
||||||
check_private_instance(&Some(local_user.clone()), &site_view.local_site)?;
|
check_private_instance(&Some(local_user.clone()), &site_view.local_site)?;
|
||||||
|
|
||||||
let inbox = InboxCombinedQuery {
|
let notifications = NotificationQuery {
|
||||||
show_bot_accounts,
|
show_bot_accounts,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.list(&mut context.pool(), my_person_id, local_instance_id)
|
.list(&mut context.pool(), &local_user.person)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
|
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 {
|
let mut channel = Channel {
|
||||||
namespaces: RSS_NAMESPACE.clone(),
|
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(
|
fn create_reply_and_mention_items(
|
||||||
inbox: Vec<InboxCombinedView>,
|
inbox: Vec<NotificationView>,
|
||||||
context: &LemmyContext,
|
context: &LemmyContext,
|
||||||
) -> LemmyResult<Vec<Item>> {
|
) -> LemmyResult<Vec<Item>> {
|
||||||
let reply_items: Vec<Item> = inbox
|
let reply_items: Vec<Item> = inbox
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| match r {
|
.map(|v| match &v.data {
|
||||||
InboxCombinedView::CommentReply(v) => {
|
NotificationData::Post(post) => {
|
||||||
let reply_url = v.comment.local_url(context.settings())?;
|
let mention_url = post.post.local_url(context.settings())?;
|
||||||
build_item(
|
build_item(
|
||||||
&v.creator,
|
&post.creator,
|
||||||
&v.comment.published_at,
|
&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(),
|
reply_url.as_str(),
|
||||||
&v.comment.content,
|
&comment.comment.content,
|
||||||
context.settings(),
|
context.settings(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
InboxCombinedView::CommentMention(v) => {
|
NotificationData::PrivateMessage(pm) => {
|
||||||
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) => {
|
|
||||||
let inbox_url = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
let inbox_url = format!("{}/inbox", context.settings().get_protocol_and_hostname());
|
||||||
build_item(
|
build_item(
|
||||||
&v.creator,
|
&pm.creator,
|
||||||
&v.private_message.published_at,
|
&pm.private_message.published_at,
|
||||||
&inbox_url,
|
&inbox_url,
|
||||||
&v.private_message.content,
|
&pm.private_message.content,
|
||||||
context.settings(),
|
context.settings(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,10 +106,8 @@ pub enum LemmyErrorType {
|
||||||
CouldntUpdateReadComments,
|
CouldntUpdateReadComments,
|
||||||
CouldntHidePost,
|
CouldntHidePost,
|
||||||
CouldntUpdateCommunity,
|
CouldntUpdateCommunity,
|
||||||
CouldntCreatePersonCommentMention,
|
CouldntCreateNotification,
|
||||||
CouldntUpdatePersonCommentMention,
|
CouldntUpdateNotification,
|
||||||
CouldntCreatePersonPostMention,
|
|
||||||
CouldntUpdatePersonPostMention,
|
|
||||||
CouldntCreatePost,
|
CouldntCreatePost,
|
||||||
CouldntCreatePrivateMessage,
|
CouldntCreatePrivateMessage,
|
||||||
CouldntUpdatePrivateMessage,
|
CouldntUpdatePrivateMessage,
|
||||||
|
@ -160,9 +158,6 @@ pub enum LemmyErrorType {
|
||||||
CouldntParsePaginationToken,
|
CouldntParsePaginationToken,
|
||||||
PluginError(String),
|
PluginError(String),
|
||||||
InvalidFetchLimit,
|
InvalidFetchLimit,
|
||||||
CouldntCreateCommentReply,
|
|
||||||
CouldntUpdateCommentReply,
|
|
||||||
CouldntMarkCommentReplyAsRead,
|
|
||||||
CouldntCreateEmoji,
|
CouldntCreateEmoji,
|
||||||
CouldntUpdateEmoji,
|
CouldntUpdateEmoji,
|
||||||
CouldntCreatePerson,
|
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,
|
random::get_random_community,
|
||||||
tag::{create_community_tag, delete_community_tag, update_community_tag},
|
tag::{create_community_tag, delete_community_tag, update_community_tag},
|
||||||
transfer::transfer_community,
|
transfer::transfer_community,
|
||||||
|
update_notifications::update_community_notifications,
|
||||||
},
|
},
|
||||||
local_user::{
|
local_user::{
|
||||||
add_admin::add_admin,
|
add_admin::add_admin,
|
||||||
|
@ -41,11 +42,9 @@ use lemmy_api::{
|
||||||
logout::logout,
|
logout::logout,
|
||||||
note_person::user_note_person,
|
note_person::user_note_person,
|
||||||
notifications::{
|
notifications::{
|
||||||
list_inbox::list_inbox,
|
list::list_notifications,
|
||||||
mark_all_read::mark_all_notifications_read,
|
mark_all_read::mark_all_notifications_read,
|
||||||
mark_comment_mention_read::mark_comment_mention_as_read,
|
mark_notification_read::mark_notification_as_read,
|
||||||
mark_post_mention_read::mark_post_mention_as_read,
|
|
||||||
mark_reply_read::mark_reply_as_read,
|
|
||||||
unread_count::unread_count,
|
unread_count::unread_count,
|
||||||
},
|
},
|
||||||
report_count::report_count,
|
report_count::report_count,
|
||||||
|
@ -67,8 +66,8 @@ use lemmy_api::{
|
||||||
mark_many_read::mark_posts_as_read,
|
mark_many_read::mark_posts_as_read,
|
||||||
mark_read::mark_post_as_read,
|
mark_read::mark_post_as_read,
|
||||||
save::save_post,
|
save::save_post,
|
||||||
|
update_notifications::update_post_notifications,
|
||||||
},
|
},
|
||||||
private_message::mark_read::mark_pm_as_read,
|
|
||||||
reports::{
|
reports::{
|
||||||
comment_report::{create::create_comment_report, resolve::resolve_comment_report},
|
comment_report::{create::create_comment_report, resolve::resolve_comment_report},
|
||||||
community_report::{create::create_community_report, resolve::resolve_community_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", post().to(create_community_tag))
|
||||||
.route("/tag", put().to(update_community_tag))
|
.route("/tag", put().to(update_community_tag))
|
||||||
.route("/tag", delete().to(delete_community_tag))
|
.route("/tag", delete().to(delete_community_tag))
|
||||||
|
.route("/notifications", post().to(update_community_notifications))
|
||||||
.service(
|
.service(
|
||||||
scope("/pending_follows")
|
scope("/pending_follows")
|
||||||
.route("/count", get().to(get_pending_follows_count))
|
.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("/like/list", get().to(list_post_likes))
|
||||||
.route("/save", put().to(save_post))
|
.route("/save", put().to(save_post))
|
||||||
.route("/report", post().to(create_post_report))
|
.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
|
// Comment
|
||||||
.service(
|
.service(
|
||||||
|
@ -310,7 +311,6 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route("", put().to(update_comment))
|
.route("", put().to(update_comment))
|
||||||
.route("/delete", post().to(delete_comment))
|
.route("/delete", post().to(delete_comment))
|
||||||
.route("/remove", post().to(remove_comment))
|
.route("/remove", post().to(remove_comment))
|
||||||
.route("/mark_as_read", post().to(mark_reply_as_read))
|
|
||||||
.route("/distinguish", post().to(distinguish_comment))
|
.route("/distinguish", post().to(distinguish_comment))
|
||||||
.route("/like", post().to(like_comment))
|
.route("/like", post().to(like_comment))
|
||||||
.route("/like/list", get().to(list_comment_likes))
|
.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("", post().to(create_private_message))
|
||||||
.route("", put().to(update_private_message))
|
.route("", put().to(update_private_message))
|
||||||
.route("/delete", post().to(delete_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", post().to(create_pm_report))
|
||||||
.route("/report/resolve", put().to(resolve_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("", delete().to(delete_image))
|
||||||
.route("/list", get().to(list_media)),
|
.route("/list", get().to(list_media)),
|
||||||
)
|
)
|
||||||
.route("/inbox", get().to(list_inbox))
|
.route("/notifications", get().to(list_notifications))
|
||||||
.route("/delete", post().to(delete_account))
|
.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/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("/report_count", get().to(report_count))
|
||||||
.route("/unread_count", get().to(unread_count))
|
.route("/unread_count", get().to(unread_count))
|
||||||
.route("/list_logins", get().to(list_logins))
|
.route("/list_logins", get().to(list_logins))
|
||||||
|
|
Loading…
Reference in a new issue