// app/api/support/kofi/claim/start/route.ts import { NextRequest, NextResponse } from "next/server"; import crypto from "crypto"; import nodemailer from "nodemailer"; const DIRECTUS = (process.env.DIRECTUS_URL || "").replace(/\/$/, ""); const BOT_TOKEN = process.env.DIRECTUS_TOKEN_ADMIN_SUPPORTER!; const COLLECTION = "user_memberships"; const APP_ORIGIN = process.env.APP_ORIGIN || "https://makearmy.io"; // your site base URL // SMTP envs const SMTP_HOST = process.env.SMTP_HOST || ""; const SMTP_PORT = process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : 587; const SMTP_USER = process.env.SMTP_USER || ""; const SMTP_PASS = process.env.SMTP_PASS || ""; const SMTP_SECURE = process.env.SMTP_SECURE === "true"; const EMAIL_FROM = process.env.EMAIL_FROM || "MakeArmy Support "; // TODO: replace with your actual auth/session resolver async function getCurrentUserId(req: NextRequest): Promise { const uid = req.headers.get("x-user-id"); return uid && uid.trim() ? uid : null; } export async function POST(req: NextRequest) { // Basic SMTP sanity check (fail fast) if (!SMTP_HOST || !SMTP_PORT || !EMAIL_FROM) { return NextResponse.json( { ok: false, error: "email_not_configured", detail: "SMTP_HOST/PORT/EMAIL_FROM must be set" }, { status: 500 } ); } const userId = await getCurrentUserId(req); if (!userId) { return NextResponse.json({ ok: false, error: "unauthorized" }, { status: 401 }); } let email = ""; try { const body = await req.json(); email = String(body?.email || "").trim().toLowerCase(); } catch { return NextResponse.json({ ok: false, error: "invalid_payload" }, { status: 400 }); } if (!email) { return NextResponse.json({ ok: false, error: "missing_email" }, { status: 400 }); } // 1) find Ko-fi membership by email const filter = encodeURIComponent( JSON.stringify({ _and: [{ provider: { _eq: "kofi" } }, { email: { _eq: email } }], }) ); const res = await fetch(`${DIRECTUS}/items/${COLLECTION}?filter=${filter}&limit=1`, { headers: { Authorization: `Bearer ${BOT_TOKEN}` }, cache: "no-store", }); if (!res.ok) { const t = await res.text().catch(() => ""); return NextResponse.json({ ok: false, error: "directus_read_failed", detail: t }, { status: 500 }); } const json = await res.json(); const rec = json?.data?.[0]; if (!rec) { return NextResponse.json({ ok: false, error: "not_found" }, { status: 404 }); } if (rec.app_user) { // Already linked return NextResponse.json({ ok: true, alreadyLinked: true }); } // 2) create a one-time token const token = crypto.randomBytes(24).toString("base64url"); const expires = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes const patch = await fetch(`${DIRECTUS}/items/${COLLECTION}/${rec.id}`, { method: "PATCH", headers: { "Content-Type": "application/json", Authorization: `Bearer ${BOT_TOKEN}`, }, body: JSON.stringify({ claim_token: token, claim_expires_at: expires.toISOString(), claim_user_id: userId, }), }); if (!patch.ok) { const t = await patch.text().catch(() => ""); return NextResponse.json({ ok: false, error: "directus_write_failed", detail: t }, { status: 500 }); } // 3) send verification email const verifyUrl = `${APP_ORIGIN}/api/support/kofi/claim/verify?token=${encodeURIComponent(token)}`; try { const transporter = nodemailer.createTransport({ host: SMTP_HOST, // e.g. "mail.arrmail.net" port: SMTP_PORT, // 465 in your case secure: SMTP_SECURE, // true for 465, false for 587 STARTTLS auth: SMTP_USER ? { user: SMTP_USER, pass: SMTP_PASS } : undefined, // If your server uses a self-signed certificate, uncomment the next line. // Prefer installing a valid cert instead. // tls: { rejectUnauthorized: false }, }); await transporter.sendMail({ from: EMAIL_FROM, // "MakeArmy Support " recommended to: email, subject: "Verify Ko-fi link to your MakeArmy account", text: `Tap to verify your Ko-fi link: ${verifyUrl}\nThis link expires in 15 minutes.`, html: `

Tap to verify your Ko-fi link:

${verifyUrl}

This link expires in 15 minutes.

`, }); } catch (e: any) { return NextResponse.json( { ok: false, error: "email_send_failed", detail: String(e?.message || e) }, { status: 500 } ); } return NextResponse.json({ ok: true }); }