import { NextResponse } from 'next/server' import { createClient } from '@/lib/supabase/server' import { decrypt } from '@/lib/whatsapp/encryption' import { submitMessageTemplate } from '@/lib/whatsapp/meta-api' import { validateTemplatePayload, type TemplatePayload, } from '@/lib/whatsapp/template-validators' import { buildMetaTemplatePayload } from '@/lib/whatsapp/template-components' import { normalizeStatus } from '@/lib/whatsapp/template-status-normalize' /** * Submit a template to Meta for approval AND persist it locally. * * Auth → fetch whatsapp_config → validate → (DRY_RUN short-circuit) → * POST to Meta → upsert local row by (user_id, name, language) with * status, meta_template_id, sample_values, last_submitted_at. * * When WHATSAPP_TEMPLATES_DRY_RUN=true, we skip the network call and * insert a row with a synthetic `dry-run-` meta_template_id so * CI / local dev can exercise the full UI without a real Meta App. * * On the Meta side this is a one-way trip — a row can only be * submitted; editing or deleting requires hsm_id and lives in PR 4. */ export async function POST(request: Request) { try { const supabase = await createClient() const { data: { user }, error: authError, } = await supabase.auth.getUser() if (authError || !user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } let payload: TemplatePayload try { payload = (await request.json()) as TemplatePayload } catch { return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 }) } if (payload.category === 'Authentication') { return NextResponse.json( { error: 'AUTHENTICATION templates are not yet supported here — create them in Meta WhatsApp Manager and use "Sync from Meta".', }, { status: 400 }, ) } try { validateTemplatePayload(payload) } catch (e) { return NextResponse.json( { error: e instanceof Error ? e.message : 'Validation failed.' }, { status: 400 }, ) } const metaPayload = buildMetaTemplatePayload(payload) const dryRun = process.env.WHATSAPP_TEMPLATES_DRY_RUN === 'true' || process.env.WHATSAPP_TEMPLATES_DRY_RUN === '1' let metaTemplateId: string let metaStatus: string if (dryRun) { metaTemplateId = `dry-run-${crypto.randomUUID()}` metaStatus = 'PENDING' } else { const { data: config, error: configError } = await supabase .from('whatsapp_config') .select('*') .eq('user_id', user.id) .single() if (configError || !config) { return NextResponse.json( { error: 'WhatsApp not configured. Connect your WhatsApp Business account in Settings first.', }, { status: 400 }, ) } if (!config.waba_id) { return NextResponse.json( { error: 'WABA (WhatsApp Business Account) ID missing. Re-connect your account in Settings.', }, { status: 400 }, ) } const accessToken = decrypt(config.access_token) try { const meta = await submitMessageTemplate({ wabaId: config.waba_id, accessToken, payload: metaPayload, }) metaTemplateId = meta.id metaStatus = meta.status } catch (e) { const message = e instanceof Error ? e.message : 'Meta submit failed.' // Persist the failure so the user can retry; tag with a 429 // hint if Meta's rate-limit response leaked through. await supabase .from('message_templates') .upsert( { user_id: user.id, name: payload.name, category: payload.category, language: payload.language, header_type: payload.header_type ?? null, header_content: payload.header_content ?? null, header_media_url: payload.header_media_url ?? null, header_handle: payload.header_handle ?? null, body_text: payload.body_text, footer_text: payload.footer_text ?? null, buttons: payload.buttons ?? null, sample_values: payload.sample_values ?? null, status: 'DRAFT', submission_error: message, last_submitted_at: new Date().toISOString(), }, { onConflict: 'user_id,name,language' }, ) const isRateLimit = /\b429\b/.test(message) return NextResponse.json( { error: isRateLimit ? 'Meta rate limit hit (100 template creates per hour). Try again later.' : message, }, { status: isRateLimit ? 429 : 502 }, ) } } const { data: row, error: upsertErr } = await supabase .from('message_templates') .upsert( { user_id: user.id, name: payload.name, category: payload.category, language: payload.language, header_type: payload.header_type ?? null, header_content: payload.header_content ?? null, header_media_url: payload.header_media_url ?? null, header_handle: payload.header_handle ?? null, body_text: payload.body_text, footer_text: payload.footer_text ?? null, buttons: payload.buttons ?? null, sample_values: payload.sample_values ?? null, status: normalizeStatus(metaStatus), meta_template_id: metaTemplateId, submission_error: null, rejection_reason: null, last_submitted_at: new Date().toISOString(), }, { onConflict: 'user_id,name,language' }, ) .select() .single() if (upsertErr) { // The submit succeeded on Meta's side but we failed to persist // locally. That's a data-drift state — surface the meta_template_id // so the user can recover via "Sync from Meta". return NextResponse.json( { error: `Submitted to Meta but failed to save locally: ${upsertErr.message}. Run "Sync from Meta" to recover.`, meta_template_id: metaTemplateId, }, { status: 500 }, ) } return NextResponse.json({ success: true, template: row, dry_run: dryRun, }) } catch (error) { console.error('Error submitting template:', error) return NextResponse.json( { error: error instanceof Error ? error.message : 'Failed to submit template.', }, { status: 500 }, ) } }