From 75fe42a03cc0717ca595f867a6ce279e0fa2dce5 Mon Sep 17 00:00:00 2001 From: Jude Musyoki Date: Mon, 29 Aug 2022 23:36:29 +0200 Subject: [PATCH 1/4] added post mention notification type to schema --- .../migration.sql | 8 ++++++++ packages/schema/prisma/schema.prisma | 11 +++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 packages/schema/prisma/migrations/20220829213012_add_post_mentions_notifications/migration.sql diff --git a/packages/schema/prisma/migrations/20220829213012_add_post_mentions_notifications/migration.sql b/packages/schema/prisma/migrations/20220829213012_add_post_mentions_notifications/migration.sql new file mode 100644 index 000000000..d6ca2956e --- /dev/null +++ b/packages/schema/prisma/migrations/20220829213012_add_post_mentions_notifications/migration.sql @@ -0,0 +1,8 @@ +-- AlterEnum +ALTER TYPE "NotificationType" ADD VALUE 'NEW_MENTION'; + +-- AlterTable +ALTER TABLE "notifications" ADD COLUMN "post_mention_id" TEXT; + +-- AddForeignKey +ALTER TABLE "notifications" ADD CONSTRAINT "notifications_post_mention_id_fkey" FOREIGN KEY ("post_mention_id") REFERENCES "PostMention"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/packages/schema/prisma/schema.prisma b/packages/schema/prisma/schema.prisma index 98a1eaf64..2ce793c5f 100644 --- a/packages/schema/prisma/schema.prisma +++ b/packages/schema/prisma/schema.prisma @@ -102,7 +102,6 @@ model Acter { // If this has an associated User record User User? - // If this has an associated Activity record Activity Activity? @relation("acter") ActivitiesOrganized Activity[] @relation("organiser") @@ -174,6 +173,7 @@ model ActerType { name String @unique Acter Acter[] + @@map("acter_types") } @@ -199,6 +199,7 @@ model Activity { organiserId String? @map("organiser_id") Notification Notification[] + @@map("activities") } @@ -207,6 +208,7 @@ model ActivityType { name String @unique Activity Activity[] + @@map("activity_types") } @@ -232,6 +234,7 @@ model Post { PostMentions PostMention[] @relation("mention_on_post") Notification Notification[] + @@map("posts") } @@ -265,6 +268,8 @@ model PostMention { createdByUser User @relation("post_mention_by_user", fields: [createdByUserId], references: [id]) createdByUserId String @map("createdByUserId") + + Notification Notification[] } model Link { @@ -307,7 +312,6 @@ model InterestType { parentInterestTypeId String? @map("parent_interest_type_id") Interests Interest[] - @@unique(name: "nameUniqueForParentInterestType", fields: [parentInterestTypeId, name]) @@index([sortOrder]) @@map("interest_types") @@ -340,6 +344,7 @@ enum NotificationType { NEW_POST NEW_ACTIVITY NEW_MEMBER + NEW_MENTION } model Notification { @@ -367,6 +372,8 @@ model Notification { Activity Activity? @relation(fields: [activityId], references: [id]) activityId String? @map("activity_id") + PostMention PostMention? @relation(fields: [postMentionId], references: [id]) + postMentionId String? @map("post_mention_id") @@index([toActerId, viewedAt]) @@index([onActerId, toActerId, type]) From 28ff249a850accee7b8bff1588192d79d9c13754 Mon Sep 17 00:00:00 2001 From: Jude Musyoki Date: Tue, 30 Aug 2022 18:44:53 +0200 Subject: [PATCH 2/4] creatementions notification job --- apps/web/pages/api/jobs/notify/[type].ts | 5 ++ .../acter/layout/menu/items/index.tsx | 8 +- packages/lib/constants/notifications.ts | 1 + services/api/resolvers/index.ts | 7 ++ .../create-post-mention-notifications.ts | 64 ++++++++++++++++ .../jobs/post-mention-notifications/index.ts | 2 + .../template/index.tsx | 74 +++++++++++++++++++ .../template/post-mention-email-block.tsx | 44 +++++++++++ .../jobs/post-mention-notifications/types.ts | 9 +++ 9 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 services/jobs/post-mention-notifications/create-post-mention-notifications.ts create mode 100644 services/jobs/post-mention-notifications/index.ts create mode 100644 services/jobs/post-mention-notifications/template/index.tsx create mode 100644 services/jobs/post-mention-notifications/template/post-mention-email-block.tsx create mode 100644 services/jobs/post-mention-notifications/types.ts diff --git a/apps/web/pages/api/jobs/notify/[type].ts b/apps/web/pages/api/jobs/notify/[type].ts index 1b3d4be80..443ee614c 100644 --- a/apps/web/pages/api/jobs/notify/[type].ts +++ b/apps/web/pages/api/jobs/notify/[type].ts @@ -12,6 +12,7 @@ import { createNewMemberNotifications, NewMemberJoinNotification, } from '@acter/jobs/new-member-notifications' +import { createPostMentionNotifications } from '@acter/jobs/post-mention-notifications' import { createPostNotifications, PostJobVariables, @@ -51,6 +52,10 @@ const notificationTypeMap: Record< checks: (body: PostJobVariables) => !!body.id, fn: createPostNotifications, }, + [NotificationQueueType.NEW_MENTION]: { + checks: (body: PostJobVariables) => !!body.id, + fn: createPostMentionNotifications, + }, } const l = getLogger('notifyHandler') diff --git a/packages/components/acter/layout/menu/items/index.tsx b/packages/components/acter/layout/menu/items/index.tsx index c1fb0fd64..47efcc98b 100644 --- a/packages/components/acter/layout/menu/items/index.tsx +++ b/packages/components/acter/layout/menu/items/index.tsx @@ -14,7 +14,7 @@ import { userHasRoleOnActer } from '@acter/lib/user/user-has-role-on-acter' import { Acter, ActerConnectionRole, NotificationType } from '@acter/schema' const { ACTIVITIES, FORUM, MEMBERS, SETTINGS } = ActerMenuEnum -const { NEW_ACTIVITY, NEW_MEMBER, NEW_POST } = NotificationType +const { NEW_ACTIVITY, NEW_MEMBER, NEW_POST, NEW_MENTION } = NotificationType export interface ActerMenuItemProps { acter: Acter @@ -39,6 +39,12 @@ export const ActerMenuItems: FC = ({ acter }) => { path={FORUM} notifications={getNotifications(NEW_POST)} /> + ({ + getJobData: async (job) => { + const post = await prisma.post.findFirst({ + include: { + Acter: true, + Author: true, + }, + where: { + id: job.id, + }, + }) + return { post } + }, + getFollowing: async ({ post }) => { + return await prisma.acter.findFirst({ + include: { + ActerType: true, + Parent: { + include: { + ActerType: true, + }, + }, + }, + where: { + id: post.acterId, + }, + }) + }, + getFollowersWhere: ({ post }) => ({ + Follower: { + id: { + not: post.Author.id, + }, + }, + }), + getNotificationEmail: ({ data: { post }, notification }) => + createPostEmailNotification({ + post, + notification, + }), + getNotificationEmailSubject: ({ notification }) => + `New post on ${notification.OnActer.name} via Acter`, + getPost: ({ post }) => post as Post, + type: NotificationType.NEW_POST, + getNotificationUrlPath: (postId, following) => + following.ActerType.name === ActerTypes.ACTIVITY + ? `activities?activity=${slugify(following.name)}&post=${postId}` + : `forum/${postId}`, +}) diff --git a/services/jobs/post-mention-notifications/index.ts b/services/jobs/post-mention-notifications/index.ts new file mode 100644 index 000000000..c1dae31de --- /dev/null +++ b/services/jobs/post-mention-notifications/index.ts @@ -0,0 +1,2 @@ +export { createPostMentionNotifications } from './create-post-mention-notifications' +export type { PostJobVariables } from './types' diff --git a/services/jobs/post-mention-notifications/template/index.tsx b/services/jobs/post-mention-notifications/template/index.tsx new file mode 100644 index 000000000..8fdb31a94 --- /dev/null +++ b/services/jobs/post-mention-notifications/template/index.tsx @@ -0,0 +1,74 @@ +import { assert } from 'console' +import { + render, + MjmlButton, + MjmlColumn, + MjmlSection, + MjmlText, +} from 'mjml-react' + +import { CreateEmailReturn } from '@acter/lib/email' +import { getNotificationUrl } from '@acter/lib/notification/get-notification-url' +import { getArticle } from '@acter/lib/string/get-article' +import { Acter, Notification, Post } from '@acter/schema' + +import { EmailLayout } from '../../templates/layout' +import { PostMentionEmailBlock } from './post-mention-email-block' + +export type PostWithActerAndAuthor = Omit & { + Acter: ActerNameAndID + Author: ActerNameAndID +} + +type ActerNameAndID = Pick + +type CreatePostMentionEmailNotificationParams = { + notification: Notification + post: PostWithActerAndAuthor +} + +export const createPostEmailNotification = ({ + notification, + post, +}: CreatePostMentionEmailNotificationParams): CreateEmailReturn => { + assert(!!post.Acter?.name, 'Post Acter name required') + assert(!!post.Author?.name, 'Post Author name required') + assert(!!post.createdAt, 'Post created at required') + + const notificationUrl = getNotificationUrl(notification) + const postType = post.parentId ? 'comment' : 'post' + const { html } = render( + + + + + A new {postType} was created on {post.Acter.name}. + + + + + + + + Go To Post + + + + + ) + const { OnActer } = notification + const aAn = getArticle(OnActer.name) + const text = `A new ${postType} was created on ${aAn} ${OnActer.ActerType.name} you follow on Acter, ${OnActer.name}. To see it, visit: ${notificationUrl}` + + return { html, text } +} diff --git a/services/jobs/post-mention-notifications/template/post-mention-email-block.tsx b/services/jobs/post-mention-notifications/template/post-mention-email-block.tsx new file mode 100644 index 000000000..e20232b61 --- /dev/null +++ b/services/jobs/post-mention-notifications/template/post-mention-email-block.tsx @@ -0,0 +1,44 @@ +import React, { FC } from 'react' + +import Markdown from 'markdown-to-jsx' +import { MjmlColumn, MjmlSection, MjmlText } from 'mjml-react' + +import { DATE_TIME_FORMAT_LONG } from '@acter/lib/constants' +import { parseAndFormat } from '@acter/lib/datetime/parse-and-format' +import { Post } from '@acter/schema' + +export interface PostMentionEmailBlockProps { + post: Post + notificationUrl: string +} + +export const PostMentionEmailBlock: FC = ({ + post, + notificationUrl, +}) => { + const sentAt = parseAndFormat({ + dateString: post.createdAt, + formatString: DATE_TIME_FORMAT_LONG, + }) + return ( + <> + + + + On {sentAt} {post.Author.name}{' '} + mentioned you in the following post: + + + + + + +
+ {post.content} +
+
+
+
+ + ) +} diff --git a/services/jobs/post-mention-notifications/types.ts b/services/jobs/post-mention-notifications/types.ts new file mode 100644 index 000000000..00be136a8 --- /dev/null +++ b/services/jobs/post-mention-notifications/types.ts @@ -0,0 +1,9 @@ +import { PostWithActerAndAuthor } from './template' + +export interface PostJobVariables { + id: string +} + +export interface PostJobData { + post: PostWithActerAndAuthor +} From 5ed3af88153dc68a29307099a6108d4ff3a1d2f2 Mon Sep 17 00:00:00 2001 From: Jude Musyoki Date: Tue, 30 Aug 2022 19:42:24 +0200 Subject: [PATCH 3/4] fix notifications worker --- .../acter/layout/menu/items/index.tsx | 8 +---- .../create-post-mention-notifications.ts | 31 ++++++++++--------- .../jobs/post-mention-notifications/index.ts | 2 +- .../template/index.tsx | 8 ++--- .../jobs/post-mention-notifications/types.ts | 9 ++++-- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/components/acter/layout/menu/items/index.tsx b/packages/components/acter/layout/menu/items/index.tsx index 47efcc98b..c1fb0fd64 100644 --- a/packages/components/acter/layout/menu/items/index.tsx +++ b/packages/components/acter/layout/menu/items/index.tsx @@ -14,7 +14,7 @@ import { userHasRoleOnActer } from '@acter/lib/user/user-has-role-on-acter' import { Acter, ActerConnectionRole, NotificationType } from '@acter/schema' const { ACTIVITIES, FORUM, MEMBERS, SETTINGS } = ActerMenuEnum -const { NEW_ACTIVITY, NEW_MEMBER, NEW_POST, NEW_MENTION } = NotificationType +const { NEW_ACTIVITY, NEW_MEMBER, NEW_POST } = NotificationType export interface ActerMenuItemProps { acter: Acter @@ -39,12 +39,6 @@ export const ActerMenuItems: FC = ({ acter }) => { path={FORUM} notifications={getNotifications(NEW_POST)} /> - ({ getJobData: async (job) => { + const postMention = await prisma.postMention.findFirst({ + where: { + id: job.id, + }, + }) const post = await prisma.post.findFirst({ include: { Acter: true, Author: true, }, where: { - id: job.id, + id: postMention.postId, }, }) - return { post } + return { post, postMention } }, getFollowing: async ({ post }) => { return await prisma.acter.findFirst({ @@ -37,26 +42,24 @@ export const createPostMentionNotifications = createNotificationWorker< }, }, where: { - id: post.acterId, + id: post.Acter.id, }, }) }, - getFollowersWhere: ({ post }) => ({ + getFollowersWhere: ({ postMention }) => ({ Follower: { - id: { - not: post.Author.id, - }, + id: postMention.acterId, }, }), getNotificationEmail: ({ data: { post }, notification }) => - createPostEmailNotification({ + createPostMentionEmailNotification({ post, notification, }), getNotificationEmailSubject: ({ notification }) => - `New post on ${notification.OnActer.name} via Acter`, + `New mention on post on ${notification.OnActer.name} via Acter`, getPost: ({ post }) => post as Post, - type: NotificationType.NEW_POST, + type: NotificationType.NEW_MENTION, getNotificationUrlPath: (postId, following) => following.ActerType.name === ActerTypes.ACTIVITY ? `activities?activity=${slugify(following.name)}&post=${postId}` diff --git a/services/jobs/post-mention-notifications/index.ts b/services/jobs/post-mention-notifications/index.ts index c1dae31de..35f64cbb1 100644 --- a/services/jobs/post-mention-notifications/index.ts +++ b/services/jobs/post-mention-notifications/index.ts @@ -1,2 +1,2 @@ export { createPostMentionNotifications } from './create-post-mention-notifications' -export type { PostJobVariables } from './types' +export type { PostMentionJobVariables } from './types' diff --git a/services/jobs/post-mention-notifications/template/index.tsx b/services/jobs/post-mention-notifications/template/index.tsx index 8fdb31a94..dfb1e894c 100644 --- a/services/jobs/post-mention-notifications/template/index.tsx +++ b/services/jobs/post-mention-notifications/template/index.tsx @@ -22,15 +22,15 @@ export type PostWithActerAndAuthor = Omit & { type ActerNameAndID = Pick -type CreatePostMentionEmailNotificationParams = { +type CreatePostEmailNotificationParams = { notification: Notification post: PostWithActerAndAuthor } -export const createPostEmailNotification = ({ +export const createPostMentionEmailNotification = ({ notification, post, -}: CreatePostMentionEmailNotificationParams): CreateEmailReturn => { +}: CreatePostEmailNotificationParams): CreateEmailReturn => { assert(!!post.Acter?.name, 'Post Acter name required') assert(!!post.Author?.name, 'Post Author name required') assert(!!post.createdAt, 'Post created at required') @@ -42,7 +42,7 @@ export const createPostEmailNotification = ({ - A new {postType} was created on {post.Acter.name}. + A new {postType} created on {post.Acter.name} mentioned you. diff --git a/services/jobs/post-mention-notifications/types.ts b/services/jobs/post-mention-notifications/types.ts index 00be136a8..9613a595a 100644 --- a/services/jobs/post-mention-notifications/types.ts +++ b/services/jobs/post-mention-notifications/types.ts @@ -1,9 +1,12 @@ -import { PostWithActerAndAuthor } from './template' +import { PostMention } from '@acter/schema' -export interface PostJobVariables { +import { PostWithActerAndAuthor } from '../post-notifications/template' + +export interface PostMentionJobVariables { id: string } -export interface PostJobData { +export interface PostMentionJobData { + postMention: PostMention post: PostWithActerAndAuthor } From 4f281f3ae0a523a90096a362c109f7ee80be1baf Mon Sep 17 00:00:00 2001 From: Jude Musyoki Date: Tue, 30 Aug 2022 19:52:23 +0200 Subject: [PATCH 4/4] added mention notification to profile settings --- packages/components/user/profile/settings/index.tsx | 4 ++++ .../migration.sql | 2 ++ packages/schema/prisma/schema.prisma | 1 + 3 files changed, 7 insertions(+) create mode 100644 packages/schema/prisma/migrations/20220830174738_add_mentions_to_acter_notification_settings/migration.sql diff --git a/packages/components/user/profile/settings/index.tsx b/packages/components/user/profile/settings/index.tsx index c51822be1..3cc82b545 100644 --- a/packages/components/user/profile/settings/index.tsx +++ b/packages/components/user/profile/settings/index.tsx @@ -77,6 +77,10 @@ export const ProfileSettings: FC = () => { +