Skip to content

Commit 13f7337

Browse files
committed
feat: migrate sponsor + distribution routes to getConfig()
- sponsor-outreach: cooldownDays, maxOutreachPerRun, rateCardTiers from sponsor_config - gemini-outreach: geminiModel from pipeline_config, renamed RATE_CARD to DEFAULT_RATE_CARD - resend-notify: accept optional fromEmail + notificationEmails params (backward compatible) Keeps all API keys and auth secrets as process.env.
1 parent 6560d0e commit 13f7337

File tree

3 files changed

+25
-13
lines changed

3 files changed

+25
-13
lines changed

app/api/cron/sponsor-outreach/route.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { sanityWriteClient } from '@/lib/sanity-write-client'
55
import { generateOutreachEmail } from '@/lib/sponsor/gemini-outreach'
66
import { sendSponsorEmail } from '@/lib/sponsor/email-service'
77
import type { SponsorPoolEntry } from '@/lib/sponsor/gemini-outreach'
8-
9-
const MAX_PER_RUN = 5
10-
const COOLDOWN_DAYS = 14
8+
import { getConfig } from '@/lib/config'
119

1210
export async function POST(request: Request) {
1311
// Auth: Bearer token check against CRON_SECRET
@@ -25,9 +23,19 @@ export async function POST(request: Request) {
2523
try {
2624
console.log('[SPONSOR] Starting outbound sponsor outreach cron...')
2725

26+
// Fetch config from Sanity singleton
27+
const sponsorCfg = await getConfig("sponsor_config");
28+
const maxPerRun = sponsorCfg.maxOutreachPerRun;
29+
const cooldownDays = sponsorCfg.cooldownDays;
30+
31+
// Build rate card string from config tiers
32+
const rateCard = sponsorCfg.rateCardTiers
33+
.map((t) => `- ${t.name} ($${t.price}) — ${t.description}`)
34+
.join('\n');
35+
2836
// Calculate the cutoff date for cooldown
2937
const cutoffDate = new Date()
30-
cutoffDate.setDate(cutoffDate.getDate() - COOLDOWN_DAYS)
38+
cutoffDate.setDate(cutoffDate.getDate() - cooldownDays)
3139
const cutoffISO = cutoffDate.toISOString()
3240

3341
// Query Sanity for eligible sponsor pool entries
@@ -38,7 +46,7 @@ export async function POST(request: Request) {
3846
!defined(lastContactedAt)
3947
|| lastContactedAt < $cutoffDate
4048
)
41-
] | order(relevanceScore desc) [0...${MAX_PER_RUN - 1}] {
49+
] | order(relevanceScore desc) [0...${maxPerRun - 1}] {
4250
_id,
4351
companyName,
4452
contactName,
@@ -67,8 +75,8 @@ export async function POST(request: Request) {
6775

6876
for (const sponsor of sponsors) {
6977
try {
70-
// Generate personalized outreach email
71-
const email = await generateOutreachEmail(sponsor)
78+
// Generate personalized outreach email with config rate card
79+
const email = await generateOutreachEmail(sponsor, rateCard)
7280

7381
// Send the email (stubbed)
7482
const sendResult = await sendSponsorEmail(

lib/resend-notify.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export async function notifySubscribers(opts: {
77
videoTitle: string;
88
videoUrl: string;
99
description: string;
10+
fromEmail?: string;
11+
notificationEmails?: string[];
1012
}): Promise<{ sent: boolean; error?: string }> {
1113
const apiKey = process.env.RESEND_API_KEY;
1214
if (!apiKey) {
@@ -19,8 +21,8 @@ export async function notifySubscribers(opts: {
1921
const resend = new Resend(apiKey);
2022

2123
await resend.emails.send({
22-
from: "CodingCat.dev <noreply@codingcat.dev>",
23-
to: ["subscribers@codingcat.dev"], // TODO: fetch subscriber list
24+
from: `CodingCat.dev <${opts.fromEmail || "noreply@codingcat.dev"}>`,
25+
to: opts.notificationEmails || ["subscribers@codingcat.dev"],
2426
subject: opts.subject,
2527
html: `
2628
<h1>${opts.videoTitle}</h1>

lib/sponsor/gemini-outreach.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { GoogleGenerativeAI } from '@google/generative-ai'
2+
import { getConfigValue } from '@/lib/config'
23

34
export interface SponsorPoolEntry {
45
_id: string
@@ -16,7 +17,7 @@ export interface OutreachEmail {
1617
body: string
1718
}
1819

19-
const RATE_CARD = `
20+
const DEFAULT_RATE_CARD = `
2021
CodingCat.dev Sponsorship Tiers:
2122
- Dedicated Video ($4,000) — Full dedicated video about your product
2223
- Integrated Mid-Roll Ad ($1,800) — Mid-roll advertisement in our videos
@@ -33,7 +34,7 @@ Our audience: 50K+ developers interested in web development, JavaScript/TypeScri
3334
*/
3435
export async function generateOutreachEmail(
3536
sponsor: SponsorPoolEntry,
36-
rateCard: string = RATE_CARD
37+
rateCard: string = DEFAULT_RATE_CARD
3738
): Promise<OutreachEmail> {
3839
const apiKey = process.env.GEMINI_API_KEY
3940
if (!apiKey) {
@@ -42,7 +43,8 @@ export async function generateOutreachEmail(
4243
}
4344

4445
const genAI = new GoogleGenerativeAI(apiKey)
45-
const model = genAI.getGenerativeModel({ model: process.env.GEMINI_MODEL || 'gemini-2.5-flash' })
46+
const geminiModel = await getConfigValue("pipeline_config", "geminiModel", "gemini-2.0-flash");
47+
const model = genAI.getGenerativeModel({ model: geminiModel })
4648

4749
const optOutUrl = sponsor.optOutToken
4850
? `${process.env.NEXT_PUBLIC_URL || 'https://codingcat.dev'}/api/sponsor/opt-out?token=${sponsor.optOutToken}`
@@ -76,7 +78,7 @@ Respond ONLY with valid JSON, no markdown formatting:
7678
const response = result.response
7779
const text = response.text().trim()
7880

79-
const jsonStr = text.replace(/^\`\`\`json?\n?/i, '').replace(/\n?\`\`\`$/i, '').trim()
81+
const jsonStr = text.replace(/^```json?\n?/i, '').replace(/\n?```$/i, '').trim()
8082
const parsed = JSON.parse(jsonStr) as OutreachEmail
8183

8284
console.log('[SPONSOR] Gemini outreach email generated for:', sponsor.companyName)

0 commit comments

Comments
 (0)