import { serve } from "https://deno.land/std@0.168.0/http/server.ts" import { createClient } from "https://esm.sh/@supabase/supabase-js@2" import { JWT } from "https://esm.sh/google-auth-library@8.9.0" const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apiKey, content-type', } serve(async (req) => { if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders }) try { const bodyText = await req.text() const payload = JSON.parse(bodyText) const tableName = payload.table const eventType = payload.type const record = payload.record const old_record = payload.old_record if (!tableName || !record) { throw new Error("Payload non valido, manca table o record.") } const supabaseClient = createClient( Deno.env.get('SUPABASE_URL') ?? '', Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' ) // ========================================================================= // 🥷 1. IDENTIFICARE ESTRARRE I BERSAGLI (CHI DEVO NOTIFICARE?) // ========================================================================= let usersToNotify: string[] = [] let notificationTitle = '' let notificationBody = '' let referenceId = '' let fluxEventType = '' // 'task_assigned', 'task_completed', etc. // SCENARIO A: ASSEGNAZIONE TASK if (tableName === 'task_assignments' && eventType === 'INSERT') { const assigneeId = record.staff_id const assignerId = record.assigned_by_id referenceId = record.task_id fluxEventType = 'task_assigned' if (assigneeId === assignerId) { return new Response(JSON.stringify({ message: "Auto-assegnazione ignorata." }), { status: 200, headers: corsHeaders }) } usersToNotify.push(assigneeId) // Costruiamo i testi const { data: taskData } = await supabaseClient.from('tasks').select('title, description, due_date').eq('id', referenceId).single() const { data: assignerData } = await supabaseClient.from('staff_members').select('first_name, last_name').eq('id', assignerId).single() const taskTitle = taskData?.title || 'Senza titolo' const taskDesc = taskData?.description || 'Nessuna descrizione' const assignerName = assignerData ? `${assignerData.first_name} ${assignerData.last_name}`.trim() : 'Un collega' let dueDateStr = 'Nessuna scadenza' if (taskData?.due_date) { dueDateStr = new Date(taskData.due_date).toLocaleDateString('it-IT') } notificationTitle = 'Nuovo Task Assegnato' notificationBody = `${taskTitle}\n\nCreato da: ${assignerName}\nScadenza: ${dueDateStr}\nDettagli: ${taskDesc}` } // SCENARIO B: COMPLETAMENTO TASK else if (tableName === 'tasks' && eventType === 'UPDATE') { const justCompleted = record.status === 'completed' && old_record.status !== 'completed'; if (!justCompleted) { return new Response(JSON.stringify({ message: "Update ignorato (non è un completamento)." }), { status: 200, headers: corsHeaders }) } const completerId = record.updated_by_id referenceId = record.id fluxEventType = 'task_completed' // Nota: assicurati di avere questa colonna o un fallback nelle preferenze const { data: assignments } = await supabaseClient.from('task_assignments').select('staff_id').eq('task_id', referenceId) if (assignments && assignments.length > 0) { usersToNotify = assignments.map(a => a.staff_id).filter(id => id !== completerId) } if (usersToNotify.length === 0) { return new Response(JSON.stringify({ message: "Nessun altro assegnatario da notificare per la chiusura." }), { status: 200, headers: corsHeaders }) } const { data: completerData } = await supabaseClient.from('staff_members').select('first_name, last_name').eq('id', completerId).single() const completerName = completerData ? `${completerData.first_name} ${completerData.last_name}`.trim() : 'Un collega' const taskTitle = record.title || 'Senza titolo' notificationTitle = 'Task Completato ✅' notificationBody = `${completerName} ha appena chiuso il task: ${taskTitle}` } // SCENARIO C: ALTRI EVENTI (Es. note_invited, ecc. Mettili qui quando ti serviranno) else { return new Response(JSON.stringify({ message: "Tabella o evento non gestito." }), { status: 200, headers: corsHeaders }) } // ========================================================================= // 🥷 2. MOTORE DI INVIO MASSIVO PER I BERSAGLI IDENTIFICATI // ========================================================================= // Inizializziamo FCM una volta sola per risparmiare tempo se ci sono push da mandare const firebaseSecret = Deno.env.get('FIREBASE_SERVICE_ACCOUNT') let fcmAccessToken = '' let fcmProjectId = '' if (firebaseSecret) { const credentials = JSON.parse(firebaseSecret) fcmProjectId = credentials.project_id const jwtClient = new JWT({ email: credentials.client_email, key: credentials.private_key, scopes: ['https://www.googleapis.com/auth/firebase.messaging'], }) fcmAccessToken = (await jwtClient.getAccessToken()).token ?? '' } const resendApiKey = Deno.env.get('RESEND_API_KEY') let pushSentCount = 0; let emailSentCount = 0; // Cicliamo tutti gli utenti che meritano la notifica for (const targetStaffId of usersToNotify) { // A) Leggiamo le preferenze dello specifico utente const { data: settings } = await supabaseClient .from('staff_notification_settings') .select('*') .eq('staff_id', targetStaffId) .single() if (!settings) continue; // Salta se non ha le preferenze let sendPush = false let sendEmail = false // B) Traduciamo l'evento nelle sue preferenze // (Se aggiungi 'task_completed' al DB settings, mettilo qui. Per ora riuso le preesistenti se manca) switch (fluxEventType) { case 'task_assigned': sendPush = settings.task_assigned_push sendEmail = settings.task_assigned_email break case 'task_completed': // Se nel DB hai aggiunto task_completed_push usa quello. // Altrimenti puoi fare fallback su task_assigned_push per testare. sendPush = settings.task_assigned_push sendEmail = settings.task_assigned_email break // Aggiungi qui gli altri case (note_invited, new_operation) } if (!sendPush && !sendEmail) continue; // Questo utente non vuole essere disturbato // Recuperiamo nome ed email per questo specifico utente const { data: staffMember } = await supabaseClient.from('staff_members').select('email, first_name').eq('id', targetStaffId).single() // C) INVIO PUSH (FCM) if (sendPush && fcmAccessToken) { const { data: devices } = await supabaseClient.from('staff_devices').select('fcm_token').eq('staff_id', targetStaffId) if (devices) { for (const device of devices) { try { const res = await fetch(`https://fcm.googleapis.com/v1/projects/${fcmProjectId}/messages:send`, { method: 'POST', headers: { 'Authorization': `Bearer ${fcmAccessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ message: { token: device.fcm_token, notification: { title: notificationTitle, body: notificationBody }, data: { click_action: 'FLUTTER_NOTIFICATION_CLICK', eventType: fluxEventType, referenceId: referenceId }, }, }), }); if (res.ok) pushSentCount++; else console.error("FCM HA RIFIUTATO LA NOTIFICA:", await res.json()); } catch (err) { console.error('Errore rete FCM:', err) } } } } // D) INVIO EMAIL (Resend) if (sendEmail && resendApiKey && staffMember?.email) { try { await fetch('https://api.resend.com/emails', { method: 'POST', headers: { 'Authorization': `Bearer ${resendApiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ from: 'FLUX Notifiche ', to: staffMember.email, subject: notificationTitle, html: `

Ciao ${staffMember.first_name},

${notificationBody.replace(/\n/g, '
')}


Il team FLUX

`, }), }) emailSentCount++; } catch (err) { console.error('Errore invio Email:', err) } } } return new Response(JSON.stringify({ success: true, targets_found: usersToNotify.length, push_sent: pushSentCount, email_sent: emailSentCount }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 200 }) } catch (error) { console.error("ERRORE FATALE NELLA FUNZIONE:", error) return new Response(JSON.stringify({ error: error.message }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' }, status: 500 }) } })