Notify mentioned users on market publish (#683)

* Add function to parse at mentions

* Notify mentioned users on market create

- refactor createNotification to accept list of recipients' ids
This commit is contained in:
Sinclair Chen 2022-08-04 15:35:55 -07:00 committed by GitHub
parent 912ccad530
commit edae709f5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 68 additions and 52 deletions

View File

@ -22,6 +22,7 @@ import { Image } from '@tiptap/extension-image'
import { Link } from '@tiptap/extension-link' import { Link } from '@tiptap/extension-link'
import { Mention } from '@tiptap/extension-mention' import { Mention } from '@tiptap/extension-mention'
import Iframe from './tiptap-iframe' import Iframe from './tiptap-iframe'
import { uniq } from 'lodash'
export function parseTags(text: string) { export function parseTags(text: string) {
const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi
@ -61,6 +62,15 @@ const checkAgainstQuery = (query: string, corpus: string) =>
export const searchInAny = (query: string, ...fields: string[]) => export const searchInAny = (query: string, ...fields: string[]) =>
fields.some((field) => checkAgainstQuery(query, field)) fields.some((field) => checkAgainstQuery(query, field))
/** @return user ids of all \@mentions */
export function parseMentions(data: JSONContent): string[] {
const mentions = data.content?.flatMap(parseMentions) ?? [] //dfs
if (data.type === 'mention' && data.attrs) {
mentions.push(data.attrs.id as string)
}
return uniq(mentions)
}
// can't just do [StarterKit, Image...] because it doesn't work with cjs imports // can't just do [StarterKit, Image...] because it doesn't work with cjs imports
export const exhibitExts = [ export const exhibitExts = [
Blockquote, Blockquote,

View File

@ -33,7 +33,7 @@ export const createNotification = async (
miscData?: { miscData?: {
contract?: Contract contract?: Contract
relatedSourceType?: notification_source_types relatedSourceType?: notification_source_types
relatedUserId?: string recipients?: string[]
slug?: string slug?: string
title?: string title?: string
} }
@ -41,7 +41,7 @@ export const createNotification = async (
const { const {
contract: sourceContract, contract: sourceContract,
relatedSourceType, relatedSourceType,
relatedUserId, recipients,
slug, slug,
title, title,
} = miscData ?? {} } = miscData ?? {}
@ -128,7 +128,7 @@ export const createNotification = async (
}) })
} }
const notifyRepliedUsers = async ( const notifyRepliedUser = (
userToReasonTexts: user_to_reason_texts, userToReasonTexts: user_to_reason_texts,
relatedUserId: string, relatedUserId: string,
relatedSourceType: notification_source_types relatedSourceType: notification_source_types
@ -145,7 +145,7 @@ export const createNotification = async (
} }
} }
const notifyFollowedUser = async ( const notifyFollowedUser = (
userToReasonTexts: user_to_reason_texts, userToReasonTexts: user_to_reason_texts,
followedUserId: string followedUserId: string
) => { ) => {
@ -155,21 +155,24 @@ export const createNotification = async (
} }
} }
const notifyTaggedUsers = async ( /** @deprecated parse from rich text instead */
userToReasonTexts: user_to_reason_texts, const parseMentions = async (source: string) => {
sourceText: string const mentions = source.match(/@\w+/g)
) => { if (!mentions) return []
const taggedUsers = sourceText.match(/@\w+/g) return Promise.all(
if (!taggedUsers) return mentions.map(
// await all get tagged users: async (username) => (await getUserByUsername(username.slice(1)))?.id
const users = await Promise.all( )
taggedUsers.map(async (username) => {
return await getUserByUsername(username.slice(1))
})
) )
users.forEach((taggedUser) => { }
if (taggedUser && shouldGetNotification(taggedUser.id, userToReasonTexts))
userToReasonTexts[taggedUser.id] = { const notifyTaggedUsers = (
userToReasonTexts: user_to_reason_texts,
userIds: (string | undefined)[]
) => {
userIds.forEach((id) => {
if (id && shouldGetNotification(id, userToReasonTexts))
userToReasonTexts[id] = {
reason: 'tagged_user', reason: 'tagged_user',
} }
}) })
@ -254,7 +257,7 @@ export const createNotification = async (
}) })
} }
const notifyUserAddedToGroup = async ( const notifyUserAddedToGroup = (
userToReasonTexts: user_to_reason_texts, userToReasonTexts: user_to_reason_texts,
relatedUserId: string relatedUserId: string
) => { ) => {
@ -276,11 +279,14 @@ export const createNotification = async (
const getUsersToNotify = async () => { const getUsersToNotify = async () => {
const userToReasonTexts: user_to_reason_texts = {} const userToReasonTexts: user_to_reason_texts = {}
// The following functions modify the userToReasonTexts object in place. // The following functions modify the userToReasonTexts object in place.
if (sourceType === 'follow' && relatedUserId) { if (sourceType === 'follow' && recipients?.[0]) {
await notifyFollowedUser(userToReasonTexts, relatedUserId) notifyFollowedUser(userToReasonTexts, recipients[0])
} else if (sourceType === 'group' && relatedUserId) { } else if (
if (sourceUpdateType === 'created') sourceType === 'group' &&
await notifyUserAddedToGroup(userToReasonTexts, relatedUserId) sourceUpdateType === 'created' &&
recipients
) {
recipients.forEach((r) => notifyUserAddedToGroup(userToReasonTexts, r))
} }
// The following functions need sourceContract to be defined. // The following functions need sourceContract to be defined.
@ -293,13 +299,10 @@ export const createNotification = async (
(sourceUpdateType === 'updated' || sourceUpdateType === 'resolved')) (sourceUpdateType === 'updated' || sourceUpdateType === 'resolved'))
) { ) {
if (sourceType === 'comment') { if (sourceType === 'comment') {
if (relatedUserId && relatedSourceType) if (recipients?.[0] && relatedSourceType)
await notifyRepliedUsers( notifyRepliedUser(userToReasonTexts, recipients[0], relatedSourceType)
userToReasonTexts, if (sourceText)
relatedUserId, notifyTaggedUsers(userToReasonTexts, await parseMentions(sourceText))
relatedSourceType
)
if (sourceText) await notifyTaggedUsers(userToReasonTexts, sourceText)
} }
await notifyContractCreator(userToReasonTexts, sourceContract) await notifyContractCreator(userToReasonTexts, sourceContract)
await notifyOtherAnswerersOnContract(userToReasonTexts, sourceContract) await notifyOtherAnswerersOnContract(userToReasonTexts, sourceContract)
@ -308,6 +311,7 @@ export const createNotification = async (
await notifyOtherCommentersOnContract(userToReasonTexts, sourceContract) await notifyOtherCommentersOnContract(userToReasonTexts, sourceContract)
} else if (sourceType === 'contract' && sourceUpdateType === 'created') { } else if (sourceType === 'contract' && sourceUpdateType === 'created') {
await notifyUsersFollowers(userToReasonTexts) await notifyUsersFollowers(userToReasonTexts)
notifyTaggedUsers(userToReasonTexts, recipients ?? [])
} else if (sourceType === 'contract' && sourceUpdateType === 'closed') { } else if (sourceType === 'contract' && sourceUpdateType === 'closed') {
await notifyContractCreator(userToReasonTexts, sourceContract, { await notifyContractCreator(userToReasonTexts, sourceContract, {
force: true, force: true,

View File

@ -68,9 +68,10 @@ export const onCreateCommentOnContract = functions
? 'answer' ? 'answer'
: undefined : undefined
const relatedUserId = comment.replyToCommentId const repliedUserId = comment.replyToCommentId
? comments.find((c) => c.id === comment.replyToCommentId)?.userId ? comments.find((c) => c.id === comment.replyToCommentId)?.userId
: answer?.userId : answer?.userId
const recipients = repliedUserId ? [repliedUserId] : []
await createNotification( await createNotification(
comment.id, comment.id,
@ -79,7 +80,7 @@ export const onCreateCommentOnContract = functions
commentCreator, commentCreator,
eventId, eventId,
comment.text, comment.text,
{ contract, relatedSourceType, relatedUserId } { contract, relatedSourceType, recipients }
) )
const recipientUserIds = uniq([ const recipientUserIds = uniq([

View File

@ -2,7 +2,7 @@ import * as functions from 'firebase-functions'
import { getUser } from './utils' import { getUser } from './utils'
import { createNotification } from './create-notification' import { createNotification } from './create-notification'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { richTextToString } from '../../common/util/parse' import { parseMentions, richTextToString } from '../../common/util/parse'
import { JSONContent } from '@tiptap/core' import { JSONContent } from '@tiptap/core'
export const onCreateContract = functions.firestore export const onCreateContract = functions.firestore
@ -14,13 +14,16 @@ export const onCreateContract = functions.firestore
const contractCreator = await getUser(contract.creatorId) const contractCreator = await getUser(contract.creatorId)
if (!contractCreator) throw new Error('Could not find contract creator') if (!contractCreator) throw new Error('Could not find contract creator')
const desc = contract.description as JSONContent
const mentioned = parseMentions(desc)
await createNotification( await createNotification(
contract.id, contract.id,
'contract', 'contract',
'created', 'created',
contractCreator, contractCreator,
eventId, eventId,
richTextToString(contract.description as JSONContent), richTextToString(desc),
{ contract } { contract, recipients: mentioned }
) )
}) })

View File

@ -12,19 +12,17 @@ export const onCreateGroup = functions.firestore
const groupCreator = await getUser(group.creatorId) const groupCreator = await getUser(group.creatorId)
if (!groupCreator) throw new Error('Could not find group creator') if (!groupCreator) throw new Error('Could not find group creator')
// create notifications for all members of the group // create notifications for all members of the group
for (const memberId of group.memberIds) { await createNotification(
await createNotification( group.id,
group.id, 'group',
'group', 'created',
'created', groupCreator,
groupCreator, eventId,
eventId, group.about,
group.about, {
{ recipients: group.memberIds,
relatedUserId: memberId, slug: group.slug,
slug: group.slug, title: group.name,
title: group.name, }
} )
)
}
}) })

View File

@ -30,7 +30,7 @@ export const onFollowUser = functions.firestore
followingUser, followingUser,
eventId, eventId,
'', '',
{ relatedUserId: follow.userId } { recipients: [follow.userId] }
) )
}) })