import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' import { createSupabaseClient, createServiceClient } from '../_shared/supabase-client.ts' import { trySignR2Url } from '../_shared/r2_signer.ts' interface BeaconRequest { lat: number long: number title: string description: string type: 'police' | 'checkpoint' | 'taskForce' | 'hazard' | 'safety' | 'community' image_url?: string } interface ResponseData { beacon?: Record error?: string } serve(async (req: Request) => { try { // Get auth header const authHeader = req.headers.get('Authorization') if (!authHeader) { return new Response( JSON.stringify({ error: 'Missing Authorization header' } as ResponseData), { status: 401, headers: { 'Content-Type': 'application/json' } } ) } const supabase = createSupabaseClient(authHeader) const { data: { user }, error: userError } = await supabase.auth.getUser() if (userError || !user) { console.error('Auth error:', userError) return new Response( JSON.stringify({ error: 'Unauthorized' } as ResponseData), { status: 401, headers: { 'Content-Type': 'application/json' } } ) } // Use service role for DB operations const supabaseAdmin = createServiceClient() // Parse request body const body = await req.json() // Convert lat/long to numbers (handles both int and double from client) const beaconReq: BeaconRequest = { lat: Number(body.lat), long: Number(body.long), title: body.title, description: body.description, type: body.type, image_url: body.image_url } // Validate required fields if (!beaconReq.lat || !beaconReq.long || !beaconReq.title || !beaconReq.description || !beaconReq.type) { return new Response( JSON.stringify({ error: 'Missing required fields: lat, long, title, description, type' } as ResponseData), { status: 400, headers: { 'Content-Type': 'application/json' } } ) } // Validate beacon type const validTypes = ['police', 'checkpoint', 'taskForce', 'hazard', 'safety', 'community'] if (!validTypes.includes(beaconReq.type)) { return new Response( JSON.stringify({ error: 'Invalid beacon type. Must be: police, checkpoint, taskForce, hazard, safety, or community' } as ResponseData), { status: 400, headers: { 'Content-Type': 'application/json' } } ) } // Get user's profile and trust score (use admin client to bypass RLS) const { data: profile, error: profileError } = await supabaseAdmin .from('profiles') .select('id, trust_state(harmony_score)') .eq('id', user.id) .single() if (profileError || !profile) { return new Response( JSON.stringify({ error: 'Profile not found' } as ResponseData), { status: 404, headers: { 'Content-Type': 'application/json' } } ) } // Get a default category for beacons (search by slug to match seed data) const { data: category } = await supabaseAdmin .from('categories') .select('id') .eq('slug', 'beacon_alerts') .single() let categoryId = category?.id if (!categoryId) { // Create the beacon category if it doesn't exist (with service role bypass) const { data: newCategory, error: insertError } = await supabaseAdmin .from('categories') .insert({ slug: 'beacon_alerts', name: 'Beacon Alerts', description: 'Community safety and alert posts' }) .select('id') .single() if (insertError || !newCategory) { console.error('Failed to create beacon category:', insertError) return new Response( JSON.stringify({ error: 'Failed to create beacon category' } as ResponseData), { status: 500, headers: { 'Content-Type': 'application/json' } } ) } categoryId = newCategory.id } // Get user's trust score for initial confidence const trustScore = profile.trust_state?.harmony_score ?? 0.5 const initialConfidence = 0.5 + (trustScore * 0.3) // Start at 50-80% based on trust // Create the beacon post const { data: beacon, error: beaconError } = await supabaseAdmin .from('posts') .insert({ author_id: user.id, category_id: categoryId, body: beaconReq.description, is_beacon: true, beacon_type: beaconReq.type, location: `SRID=4326;POINT(${beaconReq.long} ${beaconReq.lat})`, confidence_score: Math.min(1.0, Math.max(0.0, initialConfidence)), is_active_beacon: true, image_url: beaconReq.image_url, status: 'active', tone_label: 'neutral', cis_score: 0.8, allow_chain: false // Beacons don't allow chaining }) .select() .single() if (beaconError) { console.error('Error creating beacon:', beaconError) return new Response( JSON.stringify({ error: `Failed to create beacon: ${beaconError.message}` } as ResponseData), { status: 500, headers: { 'Content-Type': 'application/json' } } ) } // Get full beacon data with author info const { data: fullBeacon } = await supabaseAdmin .from('posts') .select(` *, author:profiles!posts_author_id_fkey ( id, handle, display_name, avatar_url ) `) .eq('id', beacon.id) .single() let signedBeacon = fullBeacon if (fullBeacon?.image_url) { signedBeacon = { ...fullBeacon, image_url: await trySignR2Url(fullBeacon.image_url) } } return new Response( JSON.stringify({ beacon: signedBeacon } as ResponseData), { status: 201, headers: { 'Content-Type': 'application/json' } } ) } catch (error) { console.error('Unexpected error:', error) return new Response( JSON.stringify({ error: 'Internal server error' } as ResponseData), { status: 500, headers: { 'Content-Type': 'application/json' } } ) } })