@@ -132,6 +132,7 @@ async function fetchUpcomingCalendarEvents(
132132
133133async function buildAndOptionallySendDigest ( opts : {
134134 sendEmail : boolean ;
135+ force ?: boolean ;
135136 recipientEmail ?: string ;
136137 siteOrigin : string ;
137138} ) {
@@ -283,11 +284,46 @@ async function buildAndOptionallySendDigest(opts: {
283284 let emailResult : { sent : boolean ; provider : string } | null = null ;
284285
285286 if ( opts . sendEmail ) {
286- emailResult = await sendCodingCatEmail ( {
287- to : opts . recipientEmail || "alex@codingcat.dev" ,
288- subject : `[CodingCat.dev Today] ${ todayEtStr } : ${ pendingHolds . length } Holds, ${ upcoming72hBookings . length } Upcoming Recordings, ${ actionLeads . length } Deals` ,
289- text : emailText ,
290- } ) ;
287+ const digestStateId = "private.opsDigestState" ;
288+ let alreadySentToday = false ;
289+ if ( ! opts . force ) {
290+ try {
291+ const lastSentDateEt = await client . fetch < string | null > (
292+ `*[_id == $id][0].lastSentDateEt` ,
293+ { id : digestStateId } ,
294+ ) ;
295+ if ( lastSentDateEt === todayEtStr ) {
296+ alreadySentToday = true ;
297+ }
298+ } catch {
299+ // Proceed if state lookup fails
300+ }
301+ }
302+
303+ if ( alreadySentToday ) {
304+ emailResult = {
305+ sent : false ,
306+ provider : `skipped-already-sent-${ todayEtStr } ` ,
307+ } ;
308+ } else {
309+ emailResult = await sendCodingCatEmail ( {
310+ to : opts . recipientEmail || "alex@codingcat.dev" ,
311+ subject : `[CodingCat.dev Today] ${ todayEtStr } : ${ pendingHolds . length } Holds, ${ upcoming72hBookings . length } Upcoming Recordings, ${ actionLeads . length } Deals` ,
312+ text : emailText ,
313+ } ) ;
314+ if ( emailResult . sent ) {
315+ try {
316+ await client . createOrReplace ( {
317+ _id : digestStateId ,
318+ _type : "opsDigestState" ,
319+ lastSentDateEt : todayEtStr ,
320+ lastSentAt : new Date ( ) . toISOString ( ) ,
321+ } ) ;
322+ } catch {
323+ // Ignore state persistence failure
324+ }
325+ }
326+ }
291327 }
292328
293329 return {
@@ -311,11 +347,15 @@ export const GET: APIRoute = async ({ url, request }) => {
311347 const sendEmail =
312348 url . searchParams . get ( "sendEmail" ) === "1" ||
313349 url . searchParams . get ( "sendEmail" ) === "true" ;
350+ const force =
351+ url . searchParams . get ( "force" ) === "1" ||
352+ url . searchParams . get ( "force" ) === "true" ;
314353 const recipientEmail = url . searchParams . get ( "to" ) || undefined ;
315354 const siteOrigin = url . origin || new URL ( request . url ) . origin ;
316355
317356 const payload = await buildAndOptionallySendDigest ( {
318357 sendEmail,
358+ force,
319359 recipientEmail,
320360 siteOrigin,
321361 } ) ;
@@ -327,23 +367,29 @@ export const GET: APIRoute = async ({ url, request }) => {
327367
328368export const POST : APIRoute = async ( { url, request } ) => {
329369 let body : {
330- action ?: "patch_doc" | "create_doc" | "delete_doc" ;
370+ action ?: "patch_doc" | "create_doc" | "delete_doc" | "send_digest" ;
331371 docId ?: string ;
332372 patch ?: Record < string , any > ;
373+ set ?: Record < string , any > ;
333374 doc ?: Record < string , any > ;
334375 sendEmail ?: boolean ;
376+ force ?: boolean ;
335377 to ?: string ;
336378 } = { } ;
337379 try {
338380 body = await request . json ( ) ;
339381 } catch {
340- // Default to sending email on POST
382+ // Ignore invalid JSON body
341383 }
342384
343- if ( body . action ) {
385+ if ( body . action && body . action !== "send_digest" ) {
344386 const { client } = getSanityServerClient ( ) ;
345- if ( body . action === "patch_doc" && body . docId && body . patch ) {
346- const updated = await client . patch ( body . docId ) . set ( body . patch ) . commit ( ) ;
387+ const patchPayload = body . patch || body . set ;
388+ if ( body . action === "patch_doc" && body . docId && patchPayload ) {
389+ const updated = await client
390+ . patch ( body . docId )
391+ . set ( patchPayload )
392+ . commit ( ) ;
347393 return new Response ( JSON . stringify ( { ok : true , doc : updated } ) , {
348394 status : 200 ,
349395 headers : CORS_HEADERS ,
@@ -363,11 +409,22 @@ export const POST: APIRoute = async ({ url, request }) => {
363409 headers : CORS_HEADERS ,
364410 } ) ;
365411 }
412+ return new Response (
413+ JSON . stringify ( {
414+ ok : false ,
415+ error : `Invalid or incomplete payload for action "${ body . action } "` ,
416+ } ) ,
417+ {
418+ status : 400 ,
419+ headers : CORS_HEADERS ,
420+ } ,
421+ ) ;
366422 }
367423
368424 const siteOrigin = url . origin || new URL ( request . url ) . origin ;
369425 const payload = await buildAndOptionallySendDigest ( {
370- sendEmail : body . sendEmail ?? true ,
426+ sendEmail : body . sendEmail === true || body . action === "send_digest" ,
427+ force : body . force === true ,
371428 recipientEmail : body . to ,
372429 siteOrigin,
373430 } ) ;
0 commit comments