Files
Compass/backend/api/src/create-comment.ts
2025-09-17 15:51:19 +02:00

130 lines
3.8 KiB
TypeScript

import { APIError, APIHandler } from 'api/helpers/endpoint'
import { type JSONContent } from '@tiptap/core'
import { getPrivateUser, getUser } from 'shared/utils'
import {
createSupabaseDirectClient,
SupabaseDirectClient,
} from 'shared/supabase/init'
import { getNotificationDestinationsForUser } from 'common/user-notification-preferences'
import { Notification } from 'common/notifications'
import { insertNotificationToSupabase } from 'shared/supabase/notifications'
import { User } from 'common/user'
import { richTextToString } from 'common/util/parse'
import * as crypto from 'crypto'
import { sendNewEndorsementEmail } from 'email/functions/helpers'
import { type Row } from 'common/supabase/utils'
import { broadcastUpdatedComment } from 'shared/websockets/helpers'
import { convertComment } from 'common/supabase/comment'
export const MAX_COMMENT_JSON_LENGTH = 20000
export const createComment: APIHandler<'create-comment'> = async (
{ userId, content: submittedContent, replyToCommentId },
auth
) => {
const { creator, content } = await validateComment(
userId,
auth.uid,
submittedContent
)
const onUser = await getUser(userId)
if (!onUser) throw new APIError(404, 'User not found')
const pg = createSupabaseDirectClient()
const comment = await pg.one<Row<'profile_comments'>>(
`insert into profile_comments (user_id, user_name, user_username, user_avatar_url, on_user_id, content, reply_to_comment_id)
values ($1, $2, $3, $4, $5, $6, $7) returning *`,
[
creator.id,
creator.name,
creator.username,
creator.avatarUrl,
userId,
content,
replyToCommentId,
]
)
if (onUser.id !== creator.id)
await createNewCommentOnProfileNotification(
onUser,
creator,
richTextToString(content),
comment.id,
pg
)
broadcastUpdatedComment(convertComment(comment))
return { status: 'success' }
}
const validateComment = async (
userId: string,
creatorId: string,
content: JSONContent
) => {
const creator = await getUser(creatorId)
if (!creator) throw new APIError(401, 'Your account was not found')
if (creator.isBannedFromPosting) throw new APIError(403, 'You are banned')
const otherUser = await getPrivateUser(userId)
if (!otherUser) throw new APIError(404, 'Other user not found')
if (otherUser.blockedUserIds.includes(creatorId)) {
throw new APIError(404, 'User has blocked you')
}
if (JSON.stringify(content).length > MAX_COMMENT_JSON_LENGTH) {
throw new APIError(
400,
`Comment is too long; should be less than ${MAX_COMMENT_JSON_LENGTH} as a JSON string.`
)
}
return { content, creator }
}
const createNewCommentOnProfileNotification = async (
onUser: User,
creator: User,
sourceText: string,
commentId: number,
pg: SupabaseDirectClient
) => {
const privateUser = await getPrivateUser(onUser.id)
if (!privateUser) return
const id = crypto.randomUUID()
const reason = 'new_endorsement'
const { sendToBrowser, sendToMobile, sendToEmail } =
getNotificationDestinationsForUser(privateUser, reason)
const notification: Notification = {
id,
userId: privateUser.id,
reason,
createdTime: Date.now(),
isSeen: false,
sourceId: commentId.toString(),
sourceType: 'comment_on_profile',
sourceUpdateType: 'created',
sourceUserName: creator.name,
sourceUserUsername: creator.username,
sourceUserAvatarUrl: creator.avatarUrl,
sourceText: sourceText,
sourceSlug: onUser.username,
}
if (sendToBrowser) {
await insertNotificationToSupabase(notification, pg)
}
if (sendToMobile) {
// await createPushNotification(
// notification,
// privateUser,
// `${creator.name} commented on your profile`,
// sourceText
// )
}
if (sendToEmail) {
await sendNewEndorsementEmail(privateUser, creator, onUser, sourceText)
}
}