(params: {
videoId: string;
recipientId: string;
authorId: string;
comment: { id: string; content: string };
})
| 252 | } |
| 253 | |
| 254 | async function sendNewCommentEmail(params: { |
| 255 | videoId: string; |
| 256 | recipientId: string; |
| 257 | authorId: string; |
| 258 | comment: { id: string; content: string }; |
| 259 | }) { |
| 260 | try { |
| 261 | const database = db(); |
| 262 | |
| 263 | // Throttle to the same set of events that actually trigger an email: a |
| 264 | // top-level comment authored by someone other than the owner. Replies and |
| 265 | // the owner's own comments share comments.type === "text", so without these |
| 266 | // filters they'd poison the window and suppress legitimate comment emails. |
| 267 | // Top-level comments persist parentCommentId as "" (not NULL) — see |
| 268 | // Comment.CommentId.make("") in the share-page comment UI. |
| 269 | const throttleStart = new Date(Date.now() - COMMENT_EMAIL_THROTTLE_MS); |
| 270 | const [recentComment] = await database |
| 271 | .select({ id: comments.id }) |
| 272 | .from(comments) |
| 273 | .where( |
| 274 | and( |
| 275 | eq(comments.videoId, Video.VideoId.make(params.videoId)), |
| 276 | eq(comments.type, "text"), |
| 277 | or( |
| 278 | isNull(comments.parentCommentId), |
| 279 | eq(comments.parentCommentId, Comment.CommentId.make("")), |
| 280 | ), |
| 281 | ne(comments.authorId, User.UserId.make(params.recipientId)), |
| 282 | gte(comments.createdAt, throttleStart), |
| 283 | ne(comments.id, Comment.CommentId.make(params.comment.id)), |
| 284 | ), |
| 285 | ) |
| 286 | .limit(1); |
| 287 | |
| 288 | if (recentComment) return; |
| 289 | |
| 290 | const [recipient] = await database |
| 291 | .select({ email: users.email }) |
| 292 | .from(users) |
| 293 | .where(eq(users.id, User.UserId.make(params.recipientId))) |
| 294 | .limit(1); |
| 295 | |
| 296 | if (!recipient?.email) return; |
| 297 | |
| 298 | const [video] = await database |
| 299 | .select({ name: videos.name }) |
| 300 | .from(videos) |
| 301 | .where(eq(videos.id, Video.VideoId.make(params.videoId))) |
| 302 | .limit(1); |
| 303 | |
| 304 | const videoName = video?.name || "Untitled Video"; |
| 305 | |
| 306 | const [commenter] = await database |
| 307 | .select({ name: users.name }) |
| 308 | .from(users) |
| 309 | .where(eq(users.id, User.UserId.make(params.authorId))) |
| 310 | .limit(1); |
| 311 |
no test coverage detected