posts "question of the day" to a discord webhook

Double post mitigation with stored execution date in KV

+42 -14
+42 -14
main.ts
··· 10 10 11 11 // @ts-ignore Deno.cron is unstable, run with --unstable-cron flag 12 12 Deno.cron("QOTD", Deno.env.get("CRON_STRING"), async () => { 13 - // Fetch data 14 - const response = await fetch(endpoint); 15 - const { data } = await response.json(); 16 - 17 13 // Open KV and fetch last index 18 14 // @ts-ignore Deno.openKv is unstable, run with --unstable-kv flag 19 15 const kv = await Deno.openKv( ··· 21 17 // Deno.env.get("DENO_KV_DATABASE_ID") 22 18 // }/connect`, 23 19 ); 24 - const current = await kv.get<number>(["lastIndex"]); 25 - const index = current.value ?? 0; 26 - const nextIndex = (index + 1) % data.length; 27 20 28 - // Perform atomic operation to update index 21 + // Create a key based on the current day (YYYY-MM-DD) 22 + const today = new Date().toISOString().split("T")[0]; 23 + const executionKey = ["qotd_execution", today]; 24 + 25 + // Check if we've already executed today 26 + const existingExecution = await kv.get(executionKey); 27 + if (existingExecution.value) { 28 + console.log(`Already executed QOTD for ${today}, skipping`); 29 + return; 30 + } 31 + 32 + // Try to claim today's execution 29 33 const result = await kv.atomic() 30 - .check(current) // Ensure the value hasn't changed 31 - .set(["lastIndex"], nextIndex) 34 + .check(existingExecution) // Ensure no one else claimed it 35 + .set(executionKey, { 36 + timestamp: Date.now(), 37 + instanceId: crypto.randomUUID(), 38 + }) 32 39 .commit(); 33 40 34 41 if (!result.ok) { 35 - console.log("Another instance already updated the index, skipping"); 42 + console.log("Another instance claimed today's execution, skipping"); 36 43 return; 37 44 } 38 45 39 - // Post to Discord webhook 40 - await sendDiscordNotification(data[index].question); 41 - console.log(`Cron: posted question of index ${index} to Discord.`); 46 + try { 47 + // Fetch data 48 + const response = await fetch(endpoint); 49 + const { data } = await response.json(); 50 + 51 + // Get and update index 52 + const indexResult = await kv.get<number>(["lastIndex"]); 53 + const index = indexResult.value ?? 0; 54 + const nextIndex = (index + 1) % data.length; 55 + 56 + // Post to Discord 57 + await sendDiscordNotification(data[index].question); 58 + console.log( 59 + `Cron: posted question of index ${index} to Discord for ${today}`, 60 + ); 61 + 62 + // Update index 63 + await kv.set(["lastIndex"], nextIndex); 64 + } catch (error) { 65 + // If we fail, remove our claim so another instance can try 66 + await kv.delete(executionKey); 67 + console.error("Error in cron job:", error); 68 + throw error; 69 + } 42 70 });