1
+ import { type NextRequest } from "next/server" ;
2
+ import { getCurrentUser } from "@cap/database/auth/session" ;
3
+ import { videos , comments , users } from "@cap/database/schema" ;
4
+ import { db } from "@cap/database" ;
5
+ import { eq , and , gt , ne } from "drizzle-orm" ;
6
+ import { sendEmail } from "@cap/database/emails/config" ;
7
+ import { NewComment } from "@cap/database/emails/new-comment" ;
8
+ import { clientEnv } from "@cap/env" ;
9
+
10
+ // Cache to store the last email sent time for each user
11
+ const lastEmailSentCache = new Map < string , Date > ( ) ;
12
+
13
+ export async function POST ( request : NextRequest ) {
14
+ console . log ( "Processing new comment email notification" ) ;
15
+ const { commentId } = await request . json ( ) ;
16
+
17
+ if ( ! commentId ) {
18
+ console . error ( "Missing required field: commentId" ) ;
19
+ return Response . json (
20
+ { error : "Missing required fields: commentId" } ,
21
+ { status : 400 }
22
+ ) ;
23
+ }
24
+
25
+ try {
26
+ console . log ( `Fetching comment details for commentId: ${ commentId } ` ) ;
27
+ // Get the comment details
28
+ const commentDetails = await db
29
+ . select ( {
30
+ id : comments . id ,
31
+ content : comments . content ,
32
+ type : comments . type ,
33
+ videoId : comments . videoId ,
34
+ authorId : comments . authorId ,
35
+ createdAt : comments . createdAt ,
36
+ } )
37
+ . from ( comments )
38
+ . where ( eq ( comments . id , commentId ) )
39
+ . limit ( 1 ) ;
40
+
41
+ if ( ! commentDetails || commentDetails . length === 0 ) {
42
+ console . error ( `Comment not found for commentId: ${ commentId } ` ) ;
43
+ return Response . json (
44
+ { error : "Comment not found" } ,
45
+ { status : 404 }
46
+ ) ;
47
+ }
48
+
49
+ const comment = commentDetails [ 0 ] ;
50
+ if ( comment ) {
51
+ console . log ( `Found comment: ${ comment . id } , type: ${ comment . type } , videoId: ${ comment . videoId } ` ) ;
52
+ }
53
+
54
+ // Only send email notifications for text comments
55
+ if ( ! comment || comment . type !== "text" || ! comment . videoId || ! comment . content ) {
56
+ console . log ( "Skipping email notification - invalid comment data or non-text comment" ) ;
57
+ return Response . json (
58
+ { success : false , reason : "Invalid comment data" } ,
59
+ { status : 200 }
60
+ ) ;
61
+ }
62
+
63
+ console . log ( `Fetching video details for videoId: ${ comment . videoId } ` ) ;
64
+ // Get the video details
65
+ const videoDetails = await db
66
+ . select ( {
67
+ id : videos . id ,
68
+ name : videos . name ,
69
+ ownerId : videos . ownerId ,
70
+ } )
71
+ . from ( videos )
72
+ . where ( eq ( videos . id , comment . videoId ) )
73
+ . limit ( 1 ) ;
74
+
75
+ if ( ! videoDetails || videoDetails . length === 0 ) {
76
+ console . error ( `Video not found for videoId: ${ comment . videoId } ` ) ;
77
+ return Response . json (
78
+ { error : "Video not found" } ,
79
+ { status : 404 }
80
+ ) ;
81
+ }
82
+
83
+ const video = videoDetails [ 0 ] ;
84
+ if ( video ) {
85
+ console . log ( `Found video: ${ video . id } , name: ${ video . name } , ownerId: ${ video . ownerId } ` ) ;
86
+ }
87
+
88
+ if ( ! video || ! video . ownerId || ! video . id || ! video . name ) {
89
+ console . error ( "Invalid video data" ) ;
90
+ return Response . json (
91
+ { error : "Invalid video data" } ,
92
+ { status : 500 }
93
+ ) ;
94
+ }
95
+
96
+ console . log ( `Fetching owner details for userId: ${ video . ownerId } ` ) ;
97
+ // Get the video owner's email
98
+ const ownerDetails = await db
99
+ . select ( {
100
+ id : users . id ,
101
+ email : users . email ,
102
+ } )
103
+ . from ( users )
104
+ . where ( eq ( users . id , video . ownerId ) )
105
+ . limit ( 1 ) ;
106
+
107
+ if ( ! ownerDetails || ! ownerDetails . length || ! ownerDetails [ 0 ] || ! ownerDetails [ 0 ] . email ) {
108
+ console . error ( `Video owner not found for userId: ${ video . ownerId } ` ) ;
109
+ return Response . json (
110
+ { error : "Video owner not found" } ,
111
+ { status : 404 }
112
+ ) ;
113
+ }
114
+
115
+ const owner = ownerDetails [ 0 ] ;
116
+ console . log ( `Found owner: ${ owner . id } , email: ${ owner . email } ` ) ;
117
+
118
+ if ( ! owner || ! owner . email || ! owner . id ) {
119
+ console . error ( "Invalid owner data" ) ;
120
+ return Response . json (
121
+ { error : "Invalid owner data" } ,
122
+ { status : 500 }
123
+ ) ;
124
+ }
125
+
126
+ // Get the commenter's name
127
+ let commenterName = "Anonymous" ;
128
+ if ( comment . authorId ) {
129
+ console . log ( `Fetching commenter details for userId: ${ comment . authorId } ` ) ;
130
+ const commenterDetails = await db
131
+ . select ( {
132
+ id : users . id ,
133
+ name : users . name ,
134
+ } )
135
+ . from ( users )
136
+ . where ( eq ( users . id , comment . authorId ) )
137
+ . limit ( 1 ) ;
138
+
139
+ if ( commenterDetails && commenterDetails . length > 0 && commenterDetails [ 0 ] && commenterDetails [ 0 ] . name ) {
140
+ commenterName = commenterDetails [ 0 ] . name ;
141
+ console . log ( `Found commenter name: ${ commenterName } ` ) ;
142
+ } else {
143
+ console . log ( "Commenter details not found, using 'Anonymous'" ) ;
144
+ }
145
+ } else {
146
+ console . log ( "No authorId provided, using 'Anonymous'" ) ;
147
+ }
148
+
149
+ // Check if we've sent an email to this user in the last 15 minutes
150
+ const now = new Date ( ) ;
151
+ const lastEmailSent = lastEmailSentCache . get ( owner . id ) ;
152
+
153
+ if ( lastEmailSent ) {
154
+ const fifteenMinutesAgo = new Date ( now . getTime ( ) - 15 * 60 * 1000 ) ;
155
+
156
+ if ( lastEmailSent > fifteenMinutesAgo ) {
157
+ console . log ( `Rate limiting email to user ${ owner . id } - last email sent at ${ lastEmailSent . toISOString ( ) } ` ) ;
158
+ return Response . json (
159
+ { success : false , reason : "Email rate limited" } ,
160
+ { status : 200 }
161
+ ) ;
162
+ }
163
+ }
164
+
165
+ // Also check the database for recent comments that might have triggered emails
166
+ // This handles cases where the server restarts and the cache is cleared
167
+ const fifteenMinutesAgo = new Date ( now . getTime ( ) - 15 * 60 * 1000 ) ;
168
+ console . log ( `Checking for recent comments since ${ fifteenMinutesAgo . toISOString ( ) } ` ) ;
169
+ const recentComments = await db
170
+ . select ( {
171
+ id : comments . id ,
172
+ } )
173
+ . from ( comments )
174
+ . where (
175
+ and (
176
+ eq ( comments . videoId , comment . videoId ) ,
177
+ eq ( comments . type , "text" ) ,
178
+ gt ( comments . createdAt , fifteenMinutesAgo ) ,
179
+ ne ( comments . id , commentId ) // Exclude the current comment
180
+ )
181
+ )
182
+ . limit ( 1 ) ;
183
+
184
+ // If there are recent comments (other than this one), don't send another email
185
+ if ( recentComments && recentComments . length > 0 && recentComments [ 0 ] ) {
186
+ console . log ( `Found recent comment ${ recentComments [ 0 ] . id } , skipping email notification` ) ;
187
+ return Response . json (
188
+ { success : false , reason : "Recent comment found" } ,
189
+ { status : 200 }
190
+ ) ;
191
+ }
192
+
193
+ // Generate the video URL
194
+ const videoUrl = clientEnv . NEXT_PUBLIC_IS_CAP
195
+ ? `https://cap.link/${ video . id } `
196
+ : `${ clientEnv . NEXT_PUBLIC_WEB_URL } /s/${ video . id } ` ;
197
+ console . log ( `Generated video URL: ${ videoUrl } ` ) ;
198
+
199
+ // Send the email
200
+ console . log ( `Sending email to ${ owner . email } about comment on video "${ video . name } "` ) ;
201
+
202
+ try {
203
+ const emailResult = await sendEmail ( {
204
+ email : owner . email ,
205
+ subject : `New comment on your Cap: ${ video . name } ` ,
206
+ react : NewComment ( {
207
+ email : owner . email ,
208
+ url : videoUrl ,
209
+ videoName : video . name ,
210
+ commenterName,
211
+ commentContent : comment . content ,
212
+ } ) ,
213
+ marketing : true ,
214
+ } ) ;
215
+
216
+ console . log ( "Email send result:" , emailResult ) ;
217
+ console . log ( "Email sent successfully" ) ;
218
+
219
+ // Update the cache
220
+ lastEmailSentCache . set ( owner . id , now ) ;
221
+ console . log ( `Updated email cache for user ${ owner . id } ` ) ;
222
+
223
+ return Response . json ( { success : true } , { status : 200 } ) ;
224
+ } catch ( emailError ) {
225
+ console . error ( "Error sending email via Resend:" , emailError ) ;
226
+ return Response . json (
227
+ { error : "Failed to send email" , details : String ( emailError ) } ,
228
+ { status : 500 }
229
+ ) ;
230
+ }
231
+ } catch ( error ) {
232
+ console . error ( "Error sending new comment email:" , error ) ;
233
+ return Response . json (
234
+ { error : "Failed to send email" } ,
235
+ { status : 500 }
236
+ ) ;
237
+ }
238
+ }
0 commit comments