Skip to content

Commit d75e973

Browse files
Miriadcontent
andcommitted
feat: migrate sponsor + distribution routes to getConfig()
- sponsor-outreach: cooldownDays, maxOutreachPerRun from sponsor_config - gemini-outreach: geminiModel from pipeline_config, rate card from caller - sanity-distribute: pass distribution config to notifySubscribers - resend-notify: accept fromEmail + notificationEmails params Co-authored-by: content <content@miriad.systems>
1 parent 4df823b commit d75e973

File tree

4 files changed

+34
-11
lines changed

4 files changed

+34
-11
lines changed

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

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import { NextResponse } from 'next/server'
44
import { sanityWriteClient } from '@/lib/sanity-write-client'
55
import { generateOutreachEmail } from '@/lib/sponsor/gemini-outreach'
66
import { sendSponsorEmail } from '@/lib/sponsor/email-service'
7+
import { getConfig } from '@/lib/config'
78
import type { SponsorPoolEntry } from '@/lib/sponsor/gemini-outreach'
89

9-
const MAX_PER_RUN = 5
10-
const COOLDOWN_DAYS = 14
11-
1210
export async function POST(request: Request) {
1311
// Auth: Bearer token check against CRON_SECRET
1412
const cronSecret = process.env.CRON_SECRET;
@@ -25,9 +23,24 @@ export async function POST(request: Request) {
2523
try {
2624
console.log('[SPONSOR] Starting outbound sponsor outreach cron...')
2725

26+
// Fetch sponsor 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 = [
33+
'CodingCat.dev Sponsorship Tiers:',
34+
...sponsorCfg.rateCardTiers.map(
35+
(t) => `- ${t.name} ($${t.price.toLocaleString()}) — ${t.description}`
36+
),
37+
'',
38+
'Our audience: 50K+ developers interested in web development, JavaScript/TypeScript, React, Next.js, and modern dev tools.',
39+
].join('\n');
40+
2841
// Calculate the cutoff date for cooldown
2942
const cutoffDate = new Date()
30-
cutoffDate.setDate(cutoffDate.getDate() - COOLDOWN_DAYS)
43+
cutoffDate.setDate(cutoffDate.getDate() - cooldownDays)
3144
const cutoffISO = cutoffDate.toISOString()
3245

3346
// Query Sanity for eligible sponsor pool entries
@@ -38,7 +51,7 @@ export async function POST(request: Request) {
3851
!defined(lastContactedAt)
3952
|| lastContactedAt < $cutoffDate
4053
)
41-
] | order(relevanceScore desc) [0...${MAX_PER_RUN - 1}] {
54+
] | order(relevanceScore desc) [0...${maxPerRun - 1}] {
4255
_id,
4356
companyName,
4457
contactName,
@@ -68,7 +81,7 @@ export async function POST(request: Request) {
6881
for (const sponsor of sponsors) {
6982
try {
7083
// Generate personalized outreach email
71-
const email = await generateOutreachEmail(sponsor)
84+
const email = await generateOutreachEmail(sponsor, rateCard)
7285

7386
// Send the email (stubbed)
7487
const sendResult = await sendSponsorEmail(

app/api/webhooks/sanity-distribute/route.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { generateWithGemini } from "@/lib/gemini";
55
import { uploadVideo, uploadShort, generateShortsMetadata } from "@/lib/youtube-upload";
66
import { notifySubscribers } from "@/lib/resend-notify";
77
import { postVideoAnnouncement } from "@/lib/x-social";
8+
import { getConfig } from "@/lib/config";
89

910
const WEBHOOK_SECRET = process.env.SANITY_WEBHOOK_SECRET;
1011

@@ -140,6 +141,9 @@ async function appendDistributionLog(docId: string, entries: DistributionLogEntr
140141
async function runDistribution(docId: string, doc: AutomatedVideoDoc): Promise<void> {
141142
const log: DistributionLogEntry[] = [];
142143

144+
// Fetch distribution config from Sanity singleton
145+
const distConfig = await getConfig("distribution_config");
146+
143147
try {
144148
await updateStatus(docId, "uploading");
145149

@@ -206,6 +210,8 @@ async function runDistribution(docId: string, doc: AutomatedVideoDoc): Promise<v
206210
videoTitle: metadata.title,
207211
videoUrl: ytUrl,
208212
description: metadata.description.slice(0, 280),
213+
fromEmail: distConfig.resendFromEmail,
214+
notificationEmails: distConfig.notificationEmails,
209215
});
210216
log.push(logEntry("email", "success"));
211217
} catch (e) {

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: 5 additions & 3 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}`

0 commit comments

Comments
 (0)