Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Judemusyoki/sc 693 mentions send a notification to the mentioned #835

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/web/pages/api/jobs/notify/[type].ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
createNewMemberNotifications,
NewMemberJoinNotification,
} from '@acter/jobs/new-member-notifications'
import { createPostMentionNotifications } from '@acter/jobs/post-mention-notifications'
import {
createPostNotifications,
PostJobVariables,
Expand Down Expand Up @@ -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')
Expand Down
4 changes: 4 additions & 0 deletions packages/components/user/profile/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export const ProfileSettings: FC = () => {
<SettingsRadio
label={t('allActivities')}
value={ActerNotificationSettings.ALL_ACTIVITY}
/>
<SettingsRadio
label='Mentions'
value={ActerNotificationSettings.MENTIONS}
/>
<SettingsRadio
label={t('none')}
Expand Down
1 change: 1 addition & 0 deletions packages/lib/constants/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export enum NotificationQueueType {
NEW_INVITE = 'invite-notify',
NEW_MEMBER = 'new-member',
NEW_POST = 'post-notify',
NEW_MENTION = 'post-mention-notify',
}

export enum NotificationJobState {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "ActerNotificationSettings" ADD VALUE 'MENTIONS';
12 changes: 10 additions & 2 deletions packages/schema/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum ActerJoinSettings {

enum ActerNotificationSettings {
ALL_ACTIVITY
MENTIONS
NONE
}

Expand Down Expand Up @@ -102,7 +103,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")
Expand Down Expand Up @@ -174,6 +174,7 @@ model ActerType {
name String @unique

Acter Acter[]

@@map("acter_types")
}

Expand All @@ -199,6 +200,7 @@ model Activity {
organiserId String? @map("organiser_id")

Notification Notification[]

@@map("activities")
}

Expand All @@ -207,6 +209,7 @@ model ActivityType {
name String @unique

Activity Activity[]

@@map("activity_types")
}

Expand All @@ -232,6 +235,7 @@ model Post {
PostMentions PostMention[] @relation("mention_on_post")

Notification Notification[]

@@map("posts")
}

Expand Down Expand Up @@ -265,6 +269,8 @@ model PostMention {

createdByUser User @relation("post_mention_by_user", fields: [createdByUserId], references: [id])
createdByUserId String @map("createdByUserId")

Notification Notification[]
}

model Link {
Expand Down Expand Up @@ -307,7 +313,6 @@ model InterestType {
parentInterestTypeId String? @map("parent_interest_type_id")
Interests Interest[]


@@unique(name: "nameUniqueForParentInterestType", fields: [parentInterestTypeId, name])
@@index([sortOrder])
@@map("interest_types")
Expand Down Expand Up @@ -340,6 +345,7 @@ enum NotificationType {
NEW_POST
NEW_ACTIVITY
NEW_MEMBER
NEW_MENTION
}

model Notification {
Expand Down Expand Up @@ -367,6 +373,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])
Expand Down
7 changes: 7 additions & 0 deletions services/api/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export const resolversEnhanceMap: ResolversEnhanceMap = {
),
],
},
PostMention: {
createPostMention: [
UseMiddleware(
QueueNotificationsMiddleware(NotificationQueueType.NEW_MENTION)
),
],
},
}

//eslint-disable-next-line @typescript-eslint/ban-types
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'reflect-metadata'

import slugify from 'slugify'

import { ActerTypes } from '@acter/lib/constants'
import { createNotificationWorker } from '@acter/lib/notification/create-notification-worker'
import { NotificationType, Post } from '@acter/schema'
import { prisma } from '@acter/schema/prisma'

import { createPostMentionEmailNotification } from './template'
import { PostMentionJobVariables, PostMentionJobData } from './types'

export const createPostMentionNotifications = createNotificationWorker<
PostMentionJobVariables,
PostMentionJobData
>({
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: postMention.postId,
},
})
return { post, postMention }
},
getFollowing: async ({ post }) => {
return await prisma.acter.findFirst({
include: {
ActerType: true,
Parent: {
include: {
ActerType: true,
},
},
},
where: {
id: post.Acter.id,
},
})
},
getFollowersWhere: ({ postMention }) => ({
Follower: {
id: postMention.acterId,
},
}),
getNotificationEmail: ({ data: { post }, notification }) =>
createPostMentionEmailNotification({
post,
notification,
}),
getNotificationEmailSubject: ({ notification }) =>
`New mention on post on ${notification.OnActer.name} via Acter`,
getPost: ({ post }) => post as Post,
type: NotificationType.NEW_MENTION,
getNotificationUrlPath: (postId, following) =>
following.ActerType.name === ActerTypes.ACTIVITY
? `activities?activity=${slugify(following.name)}&post=${postId}`
: `forum/${postId}`,
})
2 changes: 2 additions & 0 deletions services/jobs/post-mention-notifications/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { createPostMentionNotifications } from './create-post-mention-notifications'
export type { PostMentionJobVariables } from './types'
74 changes: 74 additions & 0 deletions services/jobs/post-mention-notifications/template/index.tsx
Original file line number Diff line number Diff line change
@@ -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<Post, 'Acter' | 'Author'> & {
Acter: ActerNameAndID
Author: ActerNameAndID
}

type ActerNameAndID = Pick<Acter, 'id' | 'name'>

type CreatePostEmailNotificationParams = {
notification: Notification
post: PostWithActerAndAuthor
}

export const createPostMentionEmailNotification = ({
notification,
post,
}: 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')

const notificationUrl = getNotificationUrl(notification)
const postType = post.parentId ? 'comment' : 'post'
const { html } = render(
<EmailLayout>
<MjmlSection backgroundColor="#fff">
<MjmlColumn>
<MjmlText fontFamily="Montserrat, Arial, non-serif">
A new {postType} created on {post.Acter.name} mentioned you.
</MjmlText>
</MjmlColumn>
</MjmlSection>
<PostMentionEmailBlock
post={post as Post}
notificationUrl={notificationUrl}
/>
<MjmlSection backgroundColor="#fff">
<MjmlColumn>
<MjmlButton
color="#fff"
backgroundColor="#1EB001"
border="1px solid #1EB001"
borderRadius="5px"
padding="12px 25px"
href={notificationUrl}
>
Go To Post
</MjmlButton>
</MjmlColumn>
</MjmlSection>
</EmailLayout>
)
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 }
}
Original file line number Diff line number Diff line change
@@ -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<PostMentionEmailBlockProps> = ({
post,
notificationUrl,
}) => {
const sentAt = parseAndFormat({
dateString: post.createdAt,
formatString: DATE_TIME_FORMAT_LONG,
})
return (
<>
<MjmlSection backgroundColor="#fff">
<MjmlColumn>
<MjmlText fontFamily="Montserrat, Arial, non-serif">
On {sentAt} {post.Author.name}{' '}
<a href={notificationUrl}>mentioned you in the following post</a>:
</MjmlText>
</MjmlColumn>
</MjmlSection>
<MjmlSection backgroundColor="#fff">
<MjmlColumn>
<MjmlText fontFamily="Montserrat, Arial, non-serif">
<div className="post-content">
<Markdown>{post.content}</Markdown>
</div>
</MjmlText>
</MjmlColumn>
</MjmlSection>
</>
)
}
12 changes: 12 additions & 0 deletions services/jobs/post-mention-notifications/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PostMention } from '@acter/schema'

import { PostWithActerAndAuthor } from '../post-notifications/template'

export interface PostMentionJobVariables {
id: string
}

export interface PostMentionJobData {
postMention: PostMention
post: PostWithActerAndAuthor
}