initial commit: rocket.new export of broadcastbeat

This commit is contained in:
Ryan Salazar
2026-05-07 16:39:17 +00:00
commit 70aa1ad46e
302 changed files with 60710 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
// DNS verification — checks SPF, DKIM, DMARC, PTR, MX, blacklist
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ domain_id: string }> }
) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { domain_id } = await params;
const supabase = await createClient();
const { data: domainRecord } = await supabase.from('rmp_email_sending_domains').select('*').eq('id', domain_id).single();
if (!domainRecord) return NextResponse.json({ error: 'Domain not found' }, { status: 404 });
const domain = domainRecord.domain;
const now = new Date().toISOString();
// Perform DNS lookups via public DNS-over-HTTPS
async function dnsLookup(type: string, name: string): Promise<string[]> {
try {
const r = await fetch(`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(name)}&type=${type}`, {
headers: { Accept: 'application/dns-json' },
});
const d = await r.json();
return (d.Answer ?? []).map((a: any) => a.data as string);
} catch {
return [];
}
}
const [txtRecords, mxRecords] = await Promise.all([
dnsLookup('TXT', domain),
dnsLookup('MX', domain),
]);
const spf_valid = txtRecords.some((r: string) => r.includes('v=spf1'));
const dmarc_valid = (await dnsLookup('TXT', `_dmarc.${domain}`)).some((r: string) => r.includes('v=DMARC1'));
const mx_valid = mxRecords.length > 0;
// DKIM — check common selectors
const dkimSelectors = ['default', 'google', 'selector1', 'selector2', 'k1', 'mail'];
let dkim_valid = false;
for (const sel of dkimSelectors) {
const recs = await dnsLookup('TXT', `${sel}._domainkey.${domain}`);
if (recs.some((r: string) => r.includes('v=DKIM1'))) { dkim_valid = true; break; }
}
// PTR — simplified check
const ptr_valid = false; // Requires reverse DNS lookup — not available via DoH
// Blacklist check (Spamhaus ZEN) — simplified
const blacklisted = false; // Full blacklist check requires DNS PTR lookup of IP
const health_score = [spf_valid, dkim_valid, dmarc_valid, mx_valid].filter(Boolean).length * 25;
const overall_pass = spf_valid && dkim_valid && dmarc_valid && mx_valid;
// Update domain record
await supabase.from('rmp_email_sending_domains').update({
spf_valid, spf_checked_at: now,
dkim_valid, dkim_checked_at: now,
dmarc_valid, dmarc_checked_at: now,
ptr_valid, ptr_checked_at: now,
mx_valid, mx_checked_at: now,
dns_fully_verified: overall_pass,
blacklisted,
blacklist_checked_at: now,
health_score,
}).eq('id', domain_id);
// Log each check
for (const [type, passed] of [['spf', spf_valid], ['dkim', dkim_valid], ['dmarc', dmarc_valid], ['mx', mx_valid], ['ptr', ptr_valid]] as [string, boolean][]) {
await supabase.from('rmp_email_dns_log').insert({ domain_id, check_type: type, passed, checked_at: now });
}
return NextResponse.json({ spf: spf_valid, dkim: dkim_valid, dmarc: dmarc_valid, ptr: ptr_valid, mx: mx_valid, blacklist: blacklisted, overall_pass, health_score });
}

View File

@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
// GET /api/email/domains/reset-counts — run daily midnight
export async function GET(request: NextRequest) {
const secret = request.headers.get('x-cron-secret') ?? request.nextUrl.searchParams.get('secret');
if (secret !== process.env.CRON_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const supabase = await createClient();
const today = new Date().toISOString().split('T')[0];
const { error } = await supabase.from('rmp_email_sending_domains').update({ sends_today: 0, sends_reset_at: today });
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,44 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
// GET /api/email/domains/verify-all — run daily 6:00 AM
export async function GET(request: NextRequest) {
const secret = request.headers.get('x-cron-secret') ?? request.nextUrl.searchParams.get('secret');
if (secret !== process.env.CRON_SECRET) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const supabase = await createClient();
const { data: domains } = await supabase.from('rmp_email_sending_domains').select('id, domain, dns_fully_verified');
let verified = 0;
let failed = 0;
for (const d of (domains ?? [])) {
try {
const r = await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}/api/email/domains/${d.id}/verify`);
const result = await r.json();
if (result.overall_pass) verified++;
else {
failed++;
// Alert admin if previously passing domain now fails
if (d.dns_fully_verified && !result.overall_pass) {
const notifyEmail = process.env.NOTIFY_EMAIL;
if (notifyEmail) {
await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}/api/marketplace/send-email`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: notifyEmail,
subject: `RMP DNS Alert: ${d.domain} now failing`,
text: `Domain ${d.domain} was previously verified but DNS checks are now failing.`,
}),
}).catch(() => {});
}
}
}
} catch {}
}
return NextResponse.json({ ok: true, verified, failed });
}

View File

@@ -0,0 +1,75 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
// POST /api/email/inbound-reply — called by Kerio when reply arrives
export async function POST(request: NextRequest) {
const supabase = await createClient();
const body = await request.json();
const fromEmail: string = body.from ?? '';
const replyBody: string = body.body ?? body.text ?? '';
const sendingDomain: string = body.to_domain ?? body.domain ?? '';
if (!fromEmail) return NextResponse.json({ ok: false, error: 'No from address' }, { status: 400 });
// Identify site from sending domain
const { data: domainRecord } = await supabase
.from('rmp_email_sending_domains')
.select('site')
.eq('domain', sendingDomain)
.single();
const site = domainRecord?.site ?? 'broadcastbeat';
// Load active keywords
const { data: keywords } = await supabase
.from('rmp_suppression_keywords')
.select('keyword, keyword_type')
.eq('active', true);
const bodyLower = replyBody.toLowerCase();
let matchedKeyword: { keyword: string; keyword_type: string } | null = null;
for (const kw of (keywords ?? [])) {
if (bodyLower.includes(kw.keyword.toLowerCase())) {
matchedKeyword = kw;
break;
}
}
if (matchedKeyword) {
const suppression_type = matchedKeyword.keyword_type === 'hostile' ? 'hostile_reply' : 'unsubscribe';
// Upsert suppression
await supabase.from('rmp_email_suppressions').upsert({
site,
email: fromEmail.toLowerCase().trim(),
suppression_type,
source: 'reply_inbound',
reply_text: replyBody.slice(0, 500),
sending_domain: sendingDomain,
suppressed_by: 'system',
}, { onConflict: 'site,email' });
// Notify admin
const notifyEmail = process.env.NOTIFY_EMAIL;
if (notifyEmail) {
try {
await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}/api/marketplace/send-email`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: notifyEmail,
subject: `RMP Email ${suppression_type}: ${fromEmail}`,
text: `${fromEmail} replied with: ${replyBody.slice(0, 200)}`,
}),
});
} catch {}
}
return NextResponse.json({ ok: true, action: 'suppressed', type: suppression_type });
}
// No keyword match — route to normal inbox (no action needed)
return NextResponse.json({ ok: true, action: 'routed' });
}

View File

@@ -0,0 +1,35 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { id } = await params;
const supabase = await createClient();
const body = await request.json();
const { data: domain } = await supabase.from('rmp_email_sending_domains').select('*').eq('id', id).single();
if (!domain) return NextResponse.json({ error: 'Domain not found' }, { status: 404 });
await supabase.from('rmp_email_sending_domains').update({
bypass_verification: true,
bypass_reason: body.reason,
bypass_set_by: auth.user?.email ?? 'admin',
bypass_set_at: new Date().toISOString(),
}).eq('id', id);
// Log
await supabase.from('rmp_email_dns_log').insert({
domain_id: id,
check_type: 'bypass_override',
passed: true,
result_detail: `Bypass set: ${body.reason}`,
});
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { id } = await params;
const supabase = await createClient();
const body = await request.json();
const { error } = await supabase.from('rmp_email_sending_domains').update(body).eq('id', id);
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function GET(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const { searchParams } = new URL(request.url);
const site = searchParams.get('site');
let query = supabase.from('rmp_email_sending_domains').select('*').order('site').order('sort_order');
if (site) query = query.eq('site', site);
const { data, error } = await query;
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ domains: data ?? [] });
}
export async function POST(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const body = await request.json();
const { data, error } = await supabase.from('rmp_email_sending_domains').insert({
site: body.site,
domain: body.domain,
domain_type: body.domain_type ?? 'subdomain',
is_primary: body.is_primary ?? false,
sort_order: body.sort_order ?? 0,
is_active: false,
dns_fully_verified: false,
}).select().single();
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ domain: data });
}

View File

@@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { id } = await params;
const supabase = await createClient();
const body = await request.json();
const { error } = await supabase.from('rmp_suppression_keywords').update({ active: body.active }).eq('id', id);
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,31 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function GET(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const { data, error } = await supabase.from('rmp_suppression_keywords').select('*').order('keyword_type').order('keyword');
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ keywords: data ?? [] });
}
export async function POST(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const body = await request.json();
const { data, error } = await supabase.from('rmp_suppression_keywords').insert({
keyword: body.keyword.toLowerCase().trim(),
keyword_type: body.keyword_type ?? 'unsubscribe',
active: true,
}).select().single();
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ keyword: data });
}

View File

@@ -0,0 +1,27 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function GET(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const { searchParams } = new URL(request.url);
const site = searchParams.get('site');
const status = searchParams.get('status');
let query = supabase
.from('rmp_email_send_log')
.select('*, rmp_email_sending_domains(domain)')
.order('created_at', { ascending: false })
.limit(200);
if (site) query = query.eq('site', site);
if (status) query = query.eq('status', status);
const { data, error } = await query;
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ logs: data ?? [] });
}

View File

@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function GET(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const now = new Date();
const monthStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
const [domainsR, sendsR, suppressionsR] = await Promise.all([
supabase.from('rmp_email_sending_domains').select('site, sends_today, blacklisted'),
supabase.from('rmp_email_send_log').select('id', { count: 'exact', head: true }).gte('created_at', monthStart),
supabase.from('rmp_email_suppressions').select('site'),
]);
const domains = domainsR.data ?? [];
const sends_today = domains.reduce((s: number, d: any) => s + (d.sends_today ?? 0), 0);
const sends_month = sendsR.count ?? 0;
const blacklisted = domains.filter((d: any) => d.blacklisted).length;
// Suppressions by site
const suppressions_by_site: Record<string, number> = {};
for (const s of (suppressionsR.data ?? [])) {
suppressions_by_site[s.site] = (suppressions_by_site[s.site] ?? 0) + 1;
}
const total_suppressions = (suppressionsR.data ?? []).length;
return NextResponse.json({ sends_today, sends_month, total_suppressions, blacklisted, suppressions_by_site });
}

View File

@@ -0,0 +1,19 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { id } = await params;
const supabase = await createClient();
const { error } = await supabase.from('rmp_email_suppressions').delete().eq('id', id);
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ ok: true });
}

View File

@@ -0,0 +1,44 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function GET(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const { searchParams } = new URL(request.url);
const site = searchParams.get('site') ?? '';
const type = searchParams.get('type') ?? '';
const search = searchParams.get('search') ?? '';
let query = supabase.from('rmp_email_suppressions').select('*').order('suppressed_at', { ascending: false });
if (site) query = query.eq('site', site);
if (type) query = query.eq('suppression_type', type);
if (search) query = query.ilike('email', `%${search}%`);
const { data, error } = await query;
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ suppressions: data ?? [] });
}
export async function POST(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const body = await request.json();
const { data, error } = await supabase.from('rmp_email_suppressions').upsert({
site: body.site,
email: body.email.toLowerCase().trim(),
suppression_type: body.suppression_type ?? 'manual',
source: 'manual',
notes: body.notes || null,
suppressed_by: 'admin',
}, { onConflict: 'site,email' }).select().single();
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ suppression: data });
}

View File

@@ -0,0 +1,25 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { requireRmpAdmin } from '@/lib/accounting/rmpService';
export async function GET(request: NextRequest) {
const auth = await requireRmpAdmin();
if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const supabase = await createClient();
const { searchParams } = new URL(request.url);
const site = searchParams.get('site') ?? '';
const now = new Date();
const monthStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
let baseQuery = supabase.from('rmp_email_suppressions').select('id, suppression_type, suppressed_at');
if (site) baseQuery = baseQuery.eq('site', site);
const { data: all } = await baseQuery;
const total = (all ?? []).length;
const added_this_month = (all ?? []).filter((s: any) => s.suppressed_at >= monthStart).length;
const hostile_replies = (all ?? []).filter((s: any) => s.suppression_type === 'hostile_reply').length;
return NextResponse.json({ total, added_this_month, hostile_replies });
}