- embedText: call Gemini REST API (768-dim) instead of Voyage (1024-dim) - Migration: drop+recreate incidents.embedding as vector(768), update match_incidents function, swap VOYAGE_API_KEY → GOOGLE_AI_API_KEY in app_settings - Settings UI: relabel to "Google AI API Key (Embeddings)" - All call sites updated (incidents POST, similar GET) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPf5Rc8QPx6V8KLEEgfKEQ
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
export const dynamic = 'force-dynamic'
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
import { createClient } from '@/lib/supabase/server'
|
|
|
|
const ALLOWED_KEYS = [
|
|
'ANTHROPIC_API_KEY',
|
|
'DEEPSEEK_API_KEY',
|
|
'GOOGLE_AI_API_KEY',
|
|
'META_WHATSAPP_PHONE_NUMBER_ID',
|
|
'META_WHATSAPP_ACCESS_TOKEN',
|
|
] as const
|
|
type SettingKey = typeof ALLOWED_KEYS[number]
|
|
|
|
async function requireAdmin(supabase: Awaited<ReturnType<typeof createClient>>) {
|
|
const { data: { user }, error } = await supabase.auth.getUser()
|
|
if (error || !user) return null
|
|
const { data: profile } = await supabase.from('users').select('role').eq('id', user.id).single()
|
|
if (!profile || profile.role !== 'admin') return null
|
|
return user
|
|
}
|
|
|
|
export async function GET() {
|
|
const supabase = await createClient()
|
|
const user = await requireAdmin(supabase)
|
|
if (!user) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
|
|
const { data } = await supabase.from('app_settings').select('key, value, updated_at')
|
|
const masked = (data ?? []).map(row => ({
|
|
key: row.key,
|
|
set: Boolean(row.value),
|
|
masked_value: row.value ? `${row.value.slice(0, 8)}${'•'.repeat(12)}` : '',
|
|
updated_at: row.updated_at,
|
|
}))
|
|
return NextResponse.json(masked)
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const supabase = await createClient()
|
|
const user = await requireAdmin(supabase)
|
|
if (!user) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
|
|
|
let body: { key?: string; value?: string }
|
|
try { body = await request.json() } catch {
|
|
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 })
|
|
}
|
|
|
|
if (!body.key || !ALLOWED_KEYS.includes(body.key as SettingKey)) {
|
|
return NextResponse.json({ error: 'Invalid key' }, { status: 422 })
|
|
}
|
|
if (typeof body.value !== 'string' || body.value.trim() === '') {
|
|
return NextResponse.json({ error: 'value must be a non-empty string' }, { status: 422 })
|
|
}
|
|
|
|
const { error } = await supabase.from('app_settings').upsert({
|
|
key: body.key,
|
|
value: body.value,
|
|
updated_at: new Date().toISOString(),
|
|
updated_by: user.id,
|
|
})
|
|
if (error) return NextResponse.json({ error: 'Save failed' }, { status: 500 })
|
|
|
|
await supabase.rpc('write_audit_log', {
|
|
p_table_name: 'app_settings',
|
|
p_record_id: user.id,
|
|
p_action: 'UPDATE',
|
|
p_new_value: { key: body.key, set: Boolean(body.value) } as never,
|
|
})
|
|
|
|
return NextResponse.json({ ok: true })
|
|
}
|