From b852b3d3f3540a653e61a8eb3867863e3332124a Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Thu, 7 Nov 2024 18:31:26 -0500 Subject: [PATCH 1/8] Initial signup lottery implementation --- src/app/api/cron/every-day/route.ts | 41 ++++++++++++++++++++++++++ src/app/api/cron/every-minute/route.ts | 8 ----- src/middleware.ts | 23 +++++++++++++-- vercel.json | 4 +++ 4 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 src/app/api/cron/every-day/route.ts diff --git a/src/app/api/cron/every-day/route.ts b/src/app/api/cron/every-day/route.ts new file mode 100644 index 00000000..e2439146 --- /dev/null +++ b/src/app/api/cron/every-day/route.ts @@ -0,0 +1,41 @@ +import Airtable from 'airtable' +import type { NextRequest } from 'next/server' + +Airtable.configure({ + apiKey: process.env.AIRTABLE_API_KEY, + endpointUrl: 'https://middleman.hackclub.com/airtable/v0', +}) +const base = Airtable.base('appTeNFYcUiYfGcR6') + +async function processDailyJobs() { + // read all free sticker orders created in the last 24 hours + const eligibleUsers = await base('people') + .select({ + filterByFormula: `AND( + has_ordered_free_stickers, + verified_eligible, + )`, + }) + .all() + + const winner = new Array(eligibleUsers).sort(() => Math.random() - 0.5)[0] + + // create the order + + const order = await base('orders').create({ + shop_item: 'item_free_raspberry_pi_zero', + recipient: [winner?.id], + }) + + // send a DM to the winner + + // post in the #high-seas channel +} + +export async function GET(request: NextRequest) { + await processDailyJobs() + return Response.json({ success: true }) +} + +export const maxDuration = 60 +export const fetchCache = 'force-no-store' diff --git a/src/app/api/cron/every-minute/route.ts b/src/app/api/cron/every-minute/route.ts index 2d025b4e..6cfdc941 100644 --- a/src/app/api/cron/every-minute/route.ts +++ b/src/app/api/cron/every-minute/route.ts @@ -2,14 +2,6 @@ import type { NextRequest } from 'next/server' import { processBackgroundJobs } from './process-background-jobs' export async function GET(request: NextRequest) { - const authHeader = request.headers.get('authorization') - const isDev = process.env.NODE_ENV === 'development' - if (!isDev && authHeader !== `Bearer ${process.env.CRON_SECRET}`) { - return new Response('Unauthorized', { - status: 401, - }) - } - await processBackgroundJobs() return Response.json({ success: true }) diff --git a/src/middleware.ts b/src/middleware.ts index f3eba1d8..057d2f7a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -9,7 +9,7 @@ import { person, } from './app/utils/data' -export async function middleware(request: NextRequest) { +export async function userPageMiddleware(request: NextRequest) { const session = await getSession() const slackId = session?.slackId @@ -125,6 +125,25 @@ export async function middleware(request: NextRequest) { return response } +const cronjobMiddleware = async (request: NextRequest) => { + const authHeader = request.headers.get('authorization') + const isDev = process.env.NODE_ENV === 'development' + if (!isDev && authHeader !== `Bearer ${process.env.CRON_SECRET}`) { + return new Response('Unauthorized', { + status: 401, + }) + } + return NextResponse.next() +} + +export async function middleware(request: NextRequest) { + if (request.nextUrl.pathname.startsWith('/api/cron')) { + return cronjobMiddleware(request) + } else { + return userPageMiddleware(request) + } +} + export const config = { - matcher: ['/signpost', '/shipyard', '/wonderdome', '/shop'], + matcher: ['/signpost', '/shipyard', '/wonderdome', '/shop', '/api/cron/'], } diff --git a/vercel.json b/vercel.json index daa9fed0..b533285f 100644 --- a/vercel.json +++ b/vercel.json @@ -3,6 +3,10 @@ { "path": "/api/cron/every-minute", "schedule": "* * * * *" + }, + { + "path": "/api/cron/every-day", + "schedule": "0 14 * * *" } ] } From 455f65b317f394cd2050733267b1f1de21a0f9d7 Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Fri, 8 Nov 2024 10:23:58 -0500 Subject: [PATCH 2/8] Add public messages for the lottery --- src/app/api/cron/every-day/route.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/app/api/cron/every-day/route.ts b/src/app/api/cron/every-day/route.ts index e2439146..4e9321d4 100644 --- a/src/app/api/cron/every-day/route.ts +++ b/src/app/api/cron/every-day/route.ts @@ -5,9 +5,18 @@ Airtable.configure({ apiKey: process.env.AIRTABLE_API_KEY, endpointUrl: 'https://middleman.hackclub.com/airtable/v0', }) + const base = Airtable.base('appTeNFYcUiYfGcR6') +const highSeasChannelId = 'C07PZMBUNDS' + async function processDailyJobs() { + const startingMessage = base('arrpheus_message_requests').create({ + message_text: `Each day, a newly signed up user will win a free Raspberry Pi Zero! Today's winner is...`, + target_slack_id: highSeasChannelId, + requester_identifier: 'cron-job', + }) + // read all free sticker orders created in the last 24 hours const eligibleUsers = await base('people') .select({ @@ -21,15 +30,26 @@ async function processDailyJobs() { const winner = new Array(eligibleUsers).sort(() => Math.random() - 0.5)[0] // create the order - - const order = await base('orders').create({ + const order = base('orders').create({ shop_item: 'item_free_raspberry_pi_zero', recipient: [winner?.id], }) // send a DM to the winner + const dmMessage = base('arrpheus_message_requests').create({ + message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, + target_slack_id: winner?.fields['slack_id'], + requester_identifier: 'cron-job', + }) // post in the #high-seas channel + const publicMessage = base('arrpheus_message_requests').create({ + message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, + target_slack_id: highSeasChannelId, + requester_identifier: 'cron-job', + }) + + await Promise.all([order, dmMessage, publicMessage]) } export async function GET(request: NextRequest) { From 54f2e5e0ed4867bf4c27a3684ba3a03c1979a656 Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Fri, 8 Nov 2024 12:01:07 -0500 Subject: [PATCH 3/8] Fix API routing running on build --- src/app/api/cron/every-day/route.ts | 11 +++++------ src/app/api/cron/every-minute/route.ts | 12 +++++------- src/app/signout/route.ts | 15 ++++++++------- src/middleware.ts | 7 ++++--- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/app/api/cron/every-day/route.ts b/src/app/api/cron/every-day/route.ts index 4e9321d4..0e66202e 100644 --- a/src/app/api/cron/every-day/route.ts +++ b/src/app/api/cron/every-day/route.ts @@ -1,5 +1,7 @@ -import Airtable from 'airtable' -import type { NextRequest } from 'next/server' +export const dynamic = 'force-dynamic' + +import Airtable from 'airtable'; +import type { NextRequest } from 'next/server'; Airtable.configure({ apiKey: process.env.AIRTABLE_API_KEY, @@ -54,8 +56,5 @@ async function processDailyJobs() { export async function GET(request: NextRequest) { await processDailyJobs() - return Response.json({ success: true }) + return Response.json({ success: true }); } - -export const maxDuration = 60 -export const fetchCache = 'force-no-store' diff --git a/src/app/api/cron/every-minute/route.ts b/src/app/api/cron/every-minute/route.ts index 6cfdc941..9ed88fd4 100644 --- a/src/app/api/cron/every-minute/route.ts +++ b/src/app/api/cron/every-minute/route.ts @@ -1,11 +1,9 @@ -import type { NextRequest } from 'next/server' -import { processBackgroundJobs } from './process-background-jobs' +export const dynamic = 'force-dynamic' -export async function GET(request: NextRequest) { +import { processBackgroundJobs } from './process-background-jobs'; + +export async function GET() { await processBackgroundJobs() - return Response.json({ success: true }) + return Response.json({ success: true }); } - -export const maxDuration = 60 -export const fetchCache = 'force-no-store' diff --git a/src/app/signout/route.ts b/src/app/signout/route.ts index cc4a4f5b..131958ea 100644 --- a/src/app/signout/route.ts +++ b/src/app/signout/route.ts @@ -1,9 +1,10 @@ -import { redirect } from 'next/navigation' -import { NextRequest } from 'next/server' -import { deleteSession } from '../utils/auth' +export const dynamic = 'force-dynamic' -export async function GET(request: NextRequest) { - console.log('SIGNING OUT!!!!!!') - await deleteSession() - return redirect('/') +import { redirect } from "next/navigation"; +import { deleteSession } from "../utils/auth"; + +export async function GET() { + console.log("SIGNING OUT!!!!!!"); + await deleteSession(); + return redirect("/"); } diff --git a/src/middleware.ts b/src/middleware.ts index 057d2f7a..eeb8f5e7 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -129,9 +129,10 @@ const cronjobMiddleware = async (request: NextRequest) => { const authHeader = request.headers.get('authorization') const isDev = process.env.NODE_ENV === 'development' if (!isDev && authHeader !== `Bearer ${process.env.CRON_SECRET}`) { - return new Response('Unauthorized', { - status: 401, - }) + return Response.json( + { success: false, message: 'authentication failed' }, + { status: 401 } + ) } return NextResponse.next() } From b345b2d21b5c8b9cf30fa2667f82890c2ccd77af Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Fri, 8 Nov 2024 12:01:23 -0500 Subject: [PATCH 4/8] Fix airtable calls on daily cronjob --- src/app/api/cron/every-day/route.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/api/cron/every-day/route.ts b/src/app/api/cron/every-day/route.ts index 0e66202e..2ad11597 100644 --- a/src/app/api/cron/every-day/route.ts +++ b/src/app/api/cron/every-day/route.ts @@ -5,7 +5,7 @@ import type { NextRequest } from 'next/server'; Airtable.configure({ apiKey: process.env.AIRTABLE_API_KEY, - endpointUrl: 'https://middleman.hackclub.com/airtable/v0', + endpointUrl: 'https://middleman.hackclub.com/airtable' }) const base = Airtable.base('appTeNFYcUiYfGcR6') @@ -13,6 +13,8 @@ const base = Airtable.base('appTeNFYcUiYfGcR6') const highSeasChannelId = 'C07PZMBUNDS' async function processDailyJobs() { + console.log("Processing daily jobs") + const startingMessage = base('arrpheus_message_requests').create({ message_text: `Each day, a newly signed up user will win a free Raspberry Pi Zero! Today's winner is...`, target_slack_id: highSeasChannelId, @@ -24,7 +26,8 @@ async function processDailyJobs() { .select({ filterByFormula: `AND( has_ordered_free_stickers, - verified_eligible, + has_ordered_free_stickers = TRUE(), + verified_eligible = TRUE() )`, }) .all() From 74e67e0cd1bd3a2623cafcbc76f3f66b32dff6b6 Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Fri, 8 Nov 2024 12:01:41 -0500 Subject: [PATCH 5/8] Add mis-comitted line --- src/app/api/cron/every-day/route.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/api/cron/every-day/route.ts b/src/app/api/cron/every-day/route.ts index 2ad11597..dad0231f 100644 --- a/src/app/api/cron/every-day/route.ts +++ b/src/app/api/cron/every-day/route.ts @@ -22,10 +22,8 @@ async function processDailyJobs() { }) // read all free sticker orders created in the last 24 hours - const eligibleUsers = await base('people') - .select({ - filterByFormula: `AND( - has_ordered_free_stickers, + const eligibleUsers = await base('people').select({ + filterByFormula: `AND( has_ordered_free_stickers = TRUE(), verified_eligible = TRUE() )`, From 05a63ff14677282fe0695c0db6ced9cffec3049c Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Fri, 8 Nov 2024 14:25:39 -0500 Subject: [PATCH 6/8] Handle duplicate lottery jobs --- .../create-background-job.ts | 2 +- src/app/api/cron/every-day/route.ts | 54 +---------- src/app/api/cron/every-minute/route.ts | 5 +- .../process-background-jobs.ts | 94 ++++++++++++++++++- src/app/marketing/marketing-utils.ts | 2 +- 5 files changed, 102 insertions(+), 55 deletions(-) rename src/app/api/cron/{every-minute => }/create-background-job.ts (87%) rename src/app/api/cron/{every-minute => }/process-background-jobs.ts (52%) diff --git a/src/app/api/cron/every-minute/create-background-job.ts b/src/app/api/cron/create-background-job.ts similarity index 87% rename from src/app/api/cron/every-minute/create-background-job.ts rename to src/app/api/cron/create-background-job.ts index 855bb96c..5c890174 100644 --- a/src/app/api/cron/every-minute/create-background-job.ts +++ b/src/app/api/cron/create-background-job.ts @@ -1,7 +1,7 @@ import { sql } from '@vercel/postgres' export default async function createBackgroundJob( - type: string, + type: 'run_lottery' | 'create_person' | 'invite', args: {}, status: 'pending' | 'completed' | 'failed' = 'pending', ) { diff --git a/src/app/api/cron/every-day/route.ts b/src/app/api/cron/every-day/route.ts index dad0231f..78423960 100644 --- a/src/app/api/cron/every-day/route.ts +++ b/src/app/api/cron/every-day/route.ts @@ -1,61 +1,15 @@ export const dynamic = 'force-dynamic' +export const fetchCache = 'force-no-store' -import Airtable from 'airtable'; -import type { NextRequest } from 'next/server'; - -Airtable.configure({ - apiKey: process.env.AIRTABLE_API_KEY, - endpointUrl: 'https://middleman.hackclub.com/airtable' -}) - -const base = Airtable.base('appTeNFYcUiYfGcR6') - -const highSeasChannelId = 'C07PZMBUNDS' +import createBackgroundJob from "../create-background-job" async function processDailyJobs() { console.log("Processing daily jobs") - const startingMessage = base('arrpheus_message_requests').create({ - message_text: `Each day, a newly signed up user will win a free Raspberry Pi Zero! Today's winner is...`, - target_slack_id: highSeasChannelId, - requester_identifier: 'cron-job', - }) - - // read all free sticker orders created in the last 24 hours - const eligibleUsers = await base('people').select({ - filterByFormula: `AND( - has_ordered_free_stickers = TRUE(), - verified_eligible = TRUE() - )`, - }) - .all() - - const winner = new Array(eligibleUsers).sort(() => Math.random() - 0.5)[0] - - // create the order - const order = base('orders').create({ - shop_item: 'item_free_raspberry_pi_zero', - recipient: [winner?.id], - }) - - // send a DM to the winner - const dmMessage = base('arrpheus_message_requests').create({ - message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, - target_slack_id: winner?.fields['slack_id'], - requester_identifier: 'cron-job', - }) - - // post in the #high-seas channel - const publicMessage = base('arrpheus_message_requests').create({ - message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, - target_slack_id: highSeasChannelId, - requester_identifier: 'cron-job', - }) - - await Promise.all([order, dmMessage, publicMessage]) + await createBackgroundJob('run_lottery', {}) } -export async function GET(request: NextRequest) { +export async function GET() { await processDailyJobs() return Response.json({ success: true }); } diff --git a/src/app/api/cron/every-minute/route.ts b/src/app/api/cron/every-minute/route.ts index 9ed88fd4..f00e99e9 100644 --- a/src/app/api/cron/every-minute/route.ts +++ b/src/app/api/cron/every-minute/route.ts @@ -1,6 +1,7 @@ -export const dynamic = 'force-dynamic' +import { processBackgroundJobs } from "../process-background-jobs"; -import { processBackgroundJobs } from './process-background-jobs'; +export const dynamic = 'force-dynamic' +export const fetchCache = 'force-no-store' export async function GET() { await processBackgroundJobs() diff --git a/src/app/api/cron/every-minute/process-background-jobs.ts b/src/app/api/cron/process-background-jobs.ts similarity index 52% rename from src/app/api/cron/every-minute/process-background-jobs.ts rename to src/app/api/cron/process-background-jobs.ts index 35c7a3af..82331465 100644 --- a/src/app/api/cron/every-minute/process-background-jobs.ts +++ b/src/app/api/cron/process-background-jobs.ts @@ -1,6 +1,7 @@ 'use server' -import { sql } from '@vercel/postgres' +import { sql } from "@vercel/postgres"; +import Airtable from 'airtable'; async function processPendingInviteJobs() { const { rows } = @@ -120,9 +121,100 @@ async function processPendingPersonInitJobs() { ) } +async function processLotteryJobs() { + const { rows } = await sql` + SELECT * + FROM background_job + WHERE type = 'run_lottery' + AND status = 'pending' + LIMIT 1; + ` + + if (rows.length === 0) { return } + + const previous = (await sql` + SELECT * + FROM background_job + WHERE type = 'run_lottery' + AND status = 'completed' + ORDER BY created_at DESC + LIMIT 1;`).rows[0] + + console.log("Previous lottery job", previous) + + if (previous && previous.created_at > new Date(Date.now() - 1000 * 60 * 60 * 23)) { return } + + Airtable.configure({ + apiKey: process.env.AIRTABLE_API_KEY, + endpointUrl: process.env.AIRTABLE_ENDPOINT_URL, + }) + + const base = Airtable.base('appTeNFYcUiYfGcR6') + + const highSeasChannelId = 'C07PZMBUNDS' + await base('arrpheus_message_requests').create({ + message_text: `Each day, a newly signed up user will win a free Raspberry Pi Zero! Today's winner is...`, + // target_slack_id: highSeasChannelId, + target_slack_id: 'U0C7B14Q3', + requester_identifier: 'cron-job', + }) + + // read all free sticker orders created in the last 24 hours + const eligibleUsers = await base('people').select({ + filterByFormula: `AND( + has_ordered_free_stickers = TRUE(), + verified_eligible = TRUE(), + verified_ineligible = FALSE(), + DATETIME_DIFF(NOW(), verification_updated_at, 'hours') <= 24 + )`, + }).all() + + const winner = eligibleUsers.sort(() => Math.random() - 0.5)[0] + console.log("Winner", winner) + + // create the order + const order = await base('shop_orders').create({ + status: 'draft', + shop_item: ['recKV56D2PATOqK4W'], + recipient: [winner?.id] + }) + + // send a DM to the winner + const messageRequests = [{ + message_text: `Hey, congrats <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, + // target_slack_id: winner?.fields['slack_id'], + target_slack_id: 'U0C7B14Q3', + requester_identifier: 'cron-job', + }, { + message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, + // target_slack_id: highSeasChannelId, + target_slack_id: 'U0C7B14Q3', + requester_identifier: 'cron-job', + }] + + // const messagePromise = base('arrpheus_message_requests').create(messageRequests[0]) + const messagePromise = await base('arrpheus_message_requests').create({ + message_text: `Each day, a newly signed up user will win a free Raspberry Pi Zero! Today's winner is...`, + // target_slack_id: highSeasChannelId, + target_slack_id: 'U0C7B14Q3', + requester_identifier: 'cron-job', + }) + + const _upsert = await sql` + UPDATE background_job + SET status='completed', + output=${JSON.stringify(order)} + WHERE type='run_lottery' + AND status='pending'` + + console.log({rows}) + console.log("Lottery job completed", _upsert) + +} export async function processBackgroundJobs() { await Promise.all([ processPendingInviteJobs(), processPendingPersonInitJobs(), + processLotteryJobs(), ]) } diff --git a/src/app/marketing/marketing-utils.ts b/src/app/marketing/marketing-utils.ts index 75f784c6..8084f3bb 100644 --- a/src/app/marketing/marketing-utils.ts +++ b/src/app/marketing/marketing-utils.ts @@ -4,7 +4,7 @@ import Airtable from 'airtable' import { createWaka } from '../utils/waka' import { getSession } from '../utils/auth' -import createBackgroundJob from '../api/cron/every-minute/create-background-job' +import createBackgroundJob from "../api/cron/create-background-job"; const highSeasPeopleTable = () => { const highSeasBaseId = process.env.BASE_ID From 4c2498c5eec9ed6d163837abc2f0fb888c16f391 Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Wed, 13 Nov 2024 12:52:12 -0500 Subject: [PATCH 7/8] Flip lottery into prod! --- src/app/api/cron/process-background-jobs.ts | 33 ++++++++------------- vercel.json | 2 +- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/app/api/cron/process-background-jobs.ts b/src/app/api/cron/process-background-jobs.ts index 82331465..a03d0ae2 100644 --- a/src/app/api/cron/process-background-jobs.ts +++ b/src/app/api/cron/process-background-jobs.ts @@ -154,8 +154,7 @@ async function processLotteryJobs() { const highSeasChannelId = 'C07PZMBUNDS' await base('arrpheus_message_requests').create({ message_text: `Each day, a newly signed up user will win a free Raspberry Pi Zero! Today's winner is...`, - // target_slack_id: highSeasChannelId, - target_slack_id: 'U0C7B14Q3', + target_slack_id: highSeasChannelId, requester_identifier: 'cron-job', }) @@ -174,42 +173,36 @@ async function processLotteryJobs() { // create the order const order = await base('shop_orders').create({ - status: 'draft', + status: 'fresh', shop_item: ['recKV56D2PATOqK4W'], recipient: [winner?.id] }) // send a DM to the winner const messageRequests = [{ - message_text: `Hey, congrats <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, - // target_slack_id: winner?.fields['slack_id'], - target_slack_id: 'U0C7B14Q3', + message_text: `Hey, congrats <@${winner?.fields['slack_id']}>! You won today's free Raspberry Pi Zero! 🎉 We're shipping it to the same address as your sticker bundle.`, + target_slack_id: winner?.fields['slack_id'], requester_identifier: 'cron-job', }, { - message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉`, - // target_slack_id: highSeasChannelId, - target_slack_id: 'U0C7B14Q3', + message_text: `Heads up, <@${winner?.fields['slack_id']}> won today's Raspberry Pi Zero! 🎉`, + target_slack_id: 'U0C7B14Q3', // notify msw for observability + requester_identifier: 'cron-job', + },{ + message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉 Every day a newly signed up person will get one.`, + target_slack_id: highSeasChannelId, requester_identifier: 'cron-job', }] - // const messagePromise = base('arrpheus_message_requests').create(messageRequests[0]) - const messagePromise = await base('arrpheus_message_requests').create({ - message_text: `Each day, a newly signed up user will win a free Raspberry Pi Zero! Today's winner is...`, - // target_slack_id: highSeasChannelId, - target_slack_id: 'U0C7B14Q3', - requester_identifier: 'cron-job', - }) + const messagePromise = base('arrpheus_message_requests').create(messageRequests.map(m => ({fields: m}))) - const _upsert = await sql` + const upsert = await sql` UPDATE background_job SET status='completed', output=${JSON.stringify(order)} WHERE type='run_lottery' AND status='pending'` - console.log({rows}) - console.log("Lottery job completed", _upsert) - + await Promise.all([upsert, messagePromise]) } export async function processBackgroundJobs() { await Promise.all([ diff --git a/vercel.json b/vercel.json index b533285f..c432aef6 100644 --- a/vercel.json +++ b/vercel.json @@ -6,7 +6,7 @@ }, { "path": "/api/cron/every-day", - "schedule": "0 14 * * *" + "schedule": "0 18 * * *" } ] } From 2d57e3486bb321ddf062ce9819dd33b650d55c56 Mon Sep 17 00:00:00 2001 From: Max Wofford Date: Wed, 13 Nov 2024 13:04:06 -0500 Subject: [PATCH 8/8] Run fmt --- src/app/api/cron/every-day/route.ts | 6 +- src/app/api/cron/every-minute/route.ts | 4 +- src/app/api/cron/process-background-jobs.ts | 69 +++++++++++++-------- src/app/marketing/marketing-utils.ts | 2 +- src/app/signout/route.ts | 10 +-- src/middleware.ts | 2 +- 6 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/app/api/cron/every-day/route.ts b/src/app/api/cron/every-day/route.ts index 78423960..ad48cd02 100644 --- a/src/app/api/cron/every-day/route.ts +++ b/src/app/api/cron/every-day/route.ts @@ -1,15 +1,15 @@ export const dynamic = 'force-dynamic' export const fetchCache = 'force-no-store' -import createBackgroundJob from "../create-background-job" +import createBackgroundJob from '../create-background-job' async function processDailyJobs() { - console.log("Processing daily jobs") + console.log('Processing daily jobs') await createBackgroundJob('run_lottery', {}) } export async function GET() { await processDailyJobs() - return Response.json({ success: true }); + return Response.json({ success: true }) } diff --git a/src/app/api/cron/every-minute/route.ts b/src/app/api/cron/every-minute/route.ts index f00e99e9..69f9ed4e 100644 --- a/src/app/api/cron/every-minute/route.ts +++ b/src/app/api/cron/every-minute/route.ts @@ -1,4 +1,4 @@ -import { processBackgroundJobs } from "../process-background-jobs"; +import { processBackgroundJobs } from '../process-background-jobs' export const dynamic = 'force-dynamic' export const fetchCache = 'force-no-store' @@ -6,5 +6,5 @@ export const fetchCache = 'force-no-store' export async function GET() { await processBackgroundJobs() - return Response.json({ success: true }); + return Response.json({ success: true }) } diff --git a/src/app/api/cron/process-background-jobs.ts b/src/app/api/cron/process-background-jobs.ts index a03d0ae2..006b8670 100644 --- a/src/app/api/cron/process-background-jobs.ts +++ b/src/app/api/cron/process-background-jobs.ts @@ -1,7 +1,7 @@ 'use server' -import { sql } from "@vercel/postgres"; -import Airtable from 'airtable'; +import { sql } from '@vercel/postgres' +import Airtable from 'airtable' async function processPendingInviteJobs() { const { rows } = @@ -130,19 +130,28 @@ async function processLotteryJobs() { LIMIT 1; ` - if (rows.length === 0) { return } + if (rows.length === 0) { + return + } - const previous = (await sql` + const previous = ( + await sql` SELECT * FROM background_job WHERE type = 'run_lottery' AND status = 'completed' ORDER BY created_at DESC - LIMIT 1;`).rows[0] + LIMIT 1;` + ).rows[0] - console.log("Previous lottery job", previous) + console.log('Previous lottery job', previous) - if (previous && previous.created_at > new Date(Date.now() - 1000 * 60 * 60 * 23)) { return } + if ( + previous && + previous.created_at > new Date(Date.now() - 1000 * 60 * 60 * 23) + ) { + return + } Airtable.configure({ apiKey: process.env.AIRTABLE_API_KEY, @@ -159,41 +168,49 @@ async function processLotteryJobs() { }) // read all free sticker orders created in the last 24 hours - const eligibleUsers = await base('people').select({ - filterByFormula: `AND( + const eligibleUsers = await base('people') + .select({ + filterByFormula: `AND( has_ordered_free_stickers = TRUE(), verified_eligible = TRUE(), verified_ineligible = FALSE(), DATETIME_DIFF(NOW(), verification_updated_at, 'hours') <= 24 )`, - }).all() + }) + .all() const winner = eligibleUsers.sort(() => Math.random() - 0.5)[0] - console.log("Winner", winner) + console.log('Winner', winner) // create the order const order = await base('shop_orders').create({ status: 'fresh', shop_item: ['recKV56D2PATOqK4W'], - recipient: [winner?.id] + recipient: [winner?.id], }) // send a DM to the winner - const messageRequests = [{ - message_text: `Hey, congrats <@${winner?.fields['slack_id']}>! You won today's free Raspberry Pi Zero! 🎉 We're shipping it to the same address as your sticker bundle.`, - target_slack_id: winner?.fields['slack_id'], - requester_identifier: 'cron-job', - }, { - message_text: `Heads up, <@${winner?.fields['slack_id']}> won today's Raspberry Pi Zero! 🎉`, - target_slack_id: 'U0C7B14Q3', // notify msw for observability - requester_identifier: 'cron-job', - },{ - message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉 Every day a newly signed up person will get one.`, - target_slack_id: highSeasChannelId, - requester_identifier: 'cron-job', - }] + const messageRequests = [ + { + message_text: `Hey, congrats <@${winner?.fields['slack_id']}>! You won today's free Raspberry Pi Zero! 🎉 We're shipping it to the same address as your sticker bundle.`, + target_slack_id: winner?.fields['slack_id'], + requester_identifier: 'cron-job', + }, + { + message_text: `Heads up, <@${winner?.fields['slack_id']}> won today's Raspberry Pi Zero! 🎉`, + target_slack_id: 'U0C7B14Q3', // notify msw for observability + requester_identifier: 'cron-job', + }, + { + message_text: `Congratulations to <@${winner?.fields['slack_id']}> for winning a free Raspberry Pi Zero! 🎉 Every day a newly signed up person will get one.`, + target_slack_id: highSeasChannelId, + requester_identifier: 'cron-job', + }, + ] - const messagePromise = base('arrpheus_message_requests').create(messageRequests.map(m => ({fields: m}))) + const messagePromise = base('arrpheus_message_requests').create( + messageRequests.map((m) => ({ fields: m })), + ) const upsert = await sql` UPDATE background_job diff --git a/src/app/marketing/marketing-utils.ts b/src/app/marketing/marketing-utils.ts index 8084f3bb..56e021c7 100644 --- a/src/app/marketing/marketing-utils.ts +++ b/src/app/marketing/marketing-utils.ts @@ -4,7 +4,7 @@ import Airtable from 'airtable' import { createWaka } from '../utils/waka' import { getSession } from '../utils/auth' -import createBackgroundJob from "../api/cron/create-background-job"; +import createBackgroundJob from '../api/cron/create-background-job' const highSeasPeopleTable = () => { const highSeasBaseId = process.env.BASE_ID diff --git a/src/app/signout/route.ts b/src/app/signout/route.ts index 131958ea..91bd830e 100644 --- a/src/app/signout/route.ts +++ b/src/app/signout/route.ts @@ -1,10 +1,10 @@ export const dynamic = 'force-dynamic' -import { redirect } from "next/navigation"; -import { deleteSession } from "../utils/auth"; +import { redirect } from 'next/navigation' +import { deleteSession } from '../utils/auth' export async function GET() { - console.log("SIGNING OUT!!!!!!"); - await deleteSession(); - return redirect("/"); + console.log('SIGNING OUT!!!!!!') + await deleteSession() + return redirect('/') } diff --git a/src/middleware.ts b/src/middleware.ts index eeb8f5e7..f50bd631 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -131,7 +131,7 @@ const cronjobMiddleware = async (request: NextRequest) => { if (!isDev && authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return Response.json( { success: false, message: 'authentication failed' }, - { status: 401 } + { status: 401 }, ) } return NextResponse.next()