fix: add GET handler for /api/whatsapp/config + fix swapped args

Fixes two bugs in the WhatsApp config API route:

1. Test API Connection button returned 405 Method Not Allowed
   The "Test API Connection" button in Settings calls
   GET /api/whatsapp/config but only a POST handler existed.
   Added a GET handler that:
   - Fetches the user's saved config from Supabase
   - Decrypts the stored access token
   - Pings Meta's /v21.0/{phone_number_id} endpoint to verify
     the credentials still work
   - Returns { connected: true, phone_info: {...} } on success,
     or { connected: false, error: "..." } with a useful message

2. POST handler passed verifyPhoneNumber() args in wrong order
   The function signature is verifyPhoneNumber(phoneNumberId, accessToken)
   but the POST was calling verifyPhoneNumber(access_token, phone_number_id).
   This meant saving a config called Meta API with the token as the URL
   and the phone number ID as the auth header — which would either fail
   silently or throw an opaque error. Fixed to pass args in the correct
   order.

3. While fixing, also improved POST error handling:
   - Meta API errors now surface the actual error message to the user
     instead of returning a generic 500
   - Sets status="connected" and connected_at on successful save
   - Uses .maybeSingle() instead of .single() for the existence check
     so it doesn't throw on first-time save

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arnas Donauskas 2026-04-16 20:40:28 +03:00
parent 9ccc53e24c
commit 04934519fd
No known key found for this signature in database

View File

@ -1,8 +1,92 @@
import { NextResponse } from 'next/server' import { NextResponse } from 'next/server'
import { createClient } from '@/lib/supabase/server' import { createClient } from '@/lib/supabase/server'
import { verifyPhoneNumber } from '@/lib/whatsapp/meta-api' import { verifyPhoneNumber } from '@/lib/whatsapp/meta-api'
import { encrypt } from '@/lib/whatsapp/encryption' import { encrypt, decrypt } from '@/lib/whatsapp/encryption'
/**
* GET /api/whatsapp/config
*
* Used by the "Test API Connection" button in Settings.
* Fetches the user's saved config, decrypts the access token, and pings
* Meta to verify the credentials still work. Returns the phone info
* (display_phone_number, verified_name, quality_rating) on success.
*/
export async function GET() {
try {
const supabase = await createClient()
const {
data: { user },
error: authError,
} = await supabase.auth.getUser()
if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { data: config, error: configError } = await supabase
.from('whatsapp_config')
.select('phone_number_id, access_token, status')
.eq('user_id', user.id)
.maybeSingle()
if (configError) {
console.error('Error fetching whatsapp_config:', configError)
return NextResponse.json(
{ error: 'Failed to fetch configuration' },
{ status: 500 }
)
}
if (!config) {
return NextResponse.json(
{ connected: false, error: 'No WhatsApp configuration saved yet' },
{ status: 404 }
)
}
// Decrypt access token and call Meta to verify it still works
let accessToken: string
try {
accessToken = decrypt(config.access_token)
} catch (err) {
console.error('Failed to decrypt access token:', err)
return NextResponse.json(
{ connected: false, error: 'Stored access token is corrupted — please re-save configuration' },
{ status: 500 }
)
}
try {
const phoneInfo = await verifyPhoneNumber(
config.phone_number_id,
accessToken
)
return NextResponse.json({
connected: true,
phone_info: phoneInfo,
})
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown Meta API error'
console.error('Meta API verification failed:', message)
return NextResponse.json(
{ connected: false, error: message },
{ status: 502 }
)
}
} catch (error) {
console.error('Error in WhatsApp config GET:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
/**
* POST /api/whatsapp/config
*
* Saves or updates the WhatsApp config for the authenticated user.
* Verifies the credentials with Meta before saving. Tokens are encrypted
* at rest with AES-256-CBC.
*/
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const supabase = await createClient() const supabase = await createClient()
@ -13,10 +97,7 @@ export async function POST(request: Request) {
} = await supabase.auth.getUser() } = await supabase.auth.getUser()
if (authError || !user) { if (authError || !user) {
return NextResponse.json( return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
{ error: 'Unauthorized' },
{ status: 401 }
)
} }
const body = await request.json() const body = await request.json()
@ -29,22 +110,32 @@ export async function POST(request: Request) {
) )
} }
// Verify the phone number with Meta API // Verify credentials with Meta BEFORE saving
const phoneInfo = await verifyPhoneNumber(access_token, phone_number_id) // (function signature is verifyPhoneNumber(phoneNumberId, accessToken))
let phoneInfo
try {
phoneInfo = await verifyPhoneNumber(phone_number_id, access_token)
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown Meta API error'
console.error('Meta API verification failed during save:', message)
return NextResponse.json(
{ error: `Meta API error: ${message}` },
{ status: 400 }
)
}
// Encrypt sensitive tokens // Encrypt sensitive tokens before storing
const encryptedAccessToken = encrypt(access_token) const encryptedAccessToken = encrypt(access_token)
const encryptedVerifyToken = verify_token ? encrypt(verify_token) : null const encryptedVerifyToken = verify_token ? encrypt(verify_token) : null
// Check if config already exists for this user // Upsert
const { data: existing } = await supabase const { data: existing } = await supabase
.from('whatsapp_config') .from('whatsapp_config')
.select('id') .select('id')
.eq('user_id', user.id) .eq('user_id', user.id)
.single() .maybeSingle()
if (existing) { if (existing) {
// Update existing config
const { error: updateError } = await supabase const { error: updateError } = await supabase
.from('whatsapp_config') .from('whatsapp_config')
.update({ .update({
@ -52,6 +143,8 @@ export async function POST(request: Request) {
waba_id: waba_id || null, waba_id: waba_id || null,
access_token: encryptedAccessToken, access_token: encryptedAccessToken,
verify_token: encryptedVerifyToken, verify_token: encryptedVerifyToken,
status: 'connected',
connected_at: new Date().toISOString(),
updated_at: new Date().toISOString(), updated_at: new Date().toISOString(),
}) })
.eq('user_id', user.id) .eq('user_id', user.id)
@ -64,7 +157,6 @@ export async function POST(request: Request) {
) )
} }
} else { } else {
// Insert new config
const { error: insertError } = await supabase const { error: insertError } = await supabase
.from('whatsapp_config') .from('whatsapp_config')
.insert({ .insert({
@ -73,6 +165,8 @@ export async function POST(request: Request) {
waba_id: waba_id || null, waba_id: waba_id || null,
access_token: encryptedAccessToken, access_token: encryptedAccessToken,
verify_token: encryptedVerifyToken, verify_token: encryptedVerifyToken,
status: 'connected',
connected_at: new Date().toISOString(),
}) })
if (insertError) { if (insertError) {
@ -90,9 +184,6 @@ export async function POST(request: Request) {
}) })
} catch (error) { } catch (error) {
console.error('Error in WhatsApp config POST:', error) console.error('Error in WhatsApp config POST:', error)
return NextResponse.json( return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
{ error: 'Internal server error' },
{ status: 500 }
)
} }
} }