🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
Backend

Supabase Tutorial: Backend Completo em 15 Minutos

Domine Supabase do zero: authentication robusta, database PostgreSQL, APIs automáticas e real-time subscriptions. Implementação prática para projetos modernos.

CrazyStack
15 min de leitura
SupabasePostgreSQLAuthenticationReal-time

Por que isso é importante

Supabase elimina 80% da complexidade backend em projetos web modernos. Enquanto configurar authentication, database e APIs tradicionalmente leva semanas, Supabase entrega tudo isso em minutos com PostgreSQL real e recursos enterprise.

O que é Supabase

Supabase é uma plataforma Backend-as-a-Service (BaaS) que oferece infraestrutura completa para aplicações modernas. Diferente do Firebase, utiliza PostgreSQL como database principal, fornecendo SQL completo e recursos avançados.

ℹ️Recursos Principais

Database PostgreSQL com interface visual, Authentication com múltiplos providers, APIs REST/GraphQL geradas automaticamente, Real-time subscriptions nativas, Storage para arquivos e Edge Functions serverless.

Supabase

Backend moderno com PostgreSQL

Prós
  • PostgreSQL completo
  • Open source
  • SQL nativo
  • Real-time built-in
Contras
  • Comunidade menor
  • Menos integrações

Firebase

BaaS do Google

Prós
  • Ecosistema Google
  • Comunidade grande
  • Muitas integrações
Contras
  • NoSQL limitado
  • Vendor lock-in
  • Pricing imprevisível

Setup Inicial do Projeto

Configuração completa do ambiente Supabase com Next.js e TypeScript para máxima produtividade.

1
Instalação do CLI:
terminal
# Instalar CLI global
npm install -g supabase

# Criar projeto Next.js com Supabase
npx create-next-app@latest meu-app --typescript --tailwind --eslint
cd meu-app

# Instalar dependências Supabase
npm install @supabase/supabase-js @supabase/ssr
2
Inicializar Supabase Local:
terminal
# Inicializar projeto local
supabase init

# Iniciar containers locais
supabase start

# Verificar status
supabase status
3
Configurar Client:
lib/supabase.ts
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    persistSession: true,
    autoRefreshToken: true,
  },
  db: {
    schema: 'public',
  },
})

// Tipos TypeScript automáticos
export type Database = {
  public: {
    Tables: {
      profiles: {
        Row: {
          id: string
          username: string | null
          full_name: string | null
          avatar_url: string | null
          created_at: string
        }
        Insert: {
          id: string
          username?: string | null
          full_name?: string | null
          avatar_url?: string | null
        }
        Update: {
          id?: string
          username?: string | null
          full_name?: string | null
          avatar_url?: string | null
        }
      }
    }
  }
}

Database Setup e Schema

Criação de tabelas PostgreSQL com Row Level Security (RLS) para segurança máxima dos dados.

migrations/001_initial_schema.sql
-- Database Schema com RLS
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Tabela de perfis de usuário
CREATE TABLE profiles (
  id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
  username TEXT UNIQUE,
  full_name TEXT,
  avatar_url TEXT,
  website TEXT,
  bio TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Habilitar Row Level Security
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;

-- Policy: usuários veem apenas próprios dados
CREATE POLICY "Usuários podem ver próprio perfil" 
ON profiles FOR SELECT 
USING (auth.uid() = id);

-- Policy: usuários podem atualizar próprios dados
CREATE POLICY "Usuários podem atualizar próprio perfil" 
ON profiles FOR UPDATE 
USING (auth.uid() = id);

-- Policy: usuários podem inserir próprios dados
CREATE POLICY "Usuários podem inserir próprio perfil" 
ON profiles FOR INSERT 
WITH CHECK (auth.uid() = id);

-- Tabela de posts
CREATE TABLE posts (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  title TEXT NOT NULL,
  content TEXT,
  author_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  published BOOLEAN DEFAULT false,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- RLS para posts
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy: todos podem ver posts publicados
CREATE POLICY "Posts publicados são visíveis" 
ON posts FOR SELECT 
USING (published = true);

-- Policy: autores podem ver próprios posts
CREATE POLICY "Autores podem ver próprios posts" 
ON posts FOR SELECT 
USING (auth.uid() = author_id);

-- Trigger para updated_at automático
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_profiles_updated_at 
BEFORE UPDATE ON profiles 
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_posts_updated_at 
BEFORE UPDATE ON posts 
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

Authentication Completa

Implementação robusta de authentication com email, OAuth e proteção de rotas.

hooks/useAuth.ts
// hooks/useAuth.ts
'use client'

import { useState, useEffect, createContext, useContext } from 'react'
import { User, Session } from '@supabase/supabase-js'
import { supabase } from '@/lib/supabase'

interface AuthContextType {
  user: User | null
  session: Session | null
  loading: boolean
  signIn: (email: string, password: string) => Promise<any>
  signUp: (email: string, password: string) => Promise<any>
  signOut: () => Promise<void>
  signInWithGitHub: () => Promise<any>
  resetPassword: (email: string) => Promise<any>
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export function useAuth() {
  const context = useContext(AuthContext)
  if (context === undefined) {
    throw new Error('useAuth deve ser usado dentro de AuthProvider')
  }
  return context
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null)
  const [session, setSession] = useState<Session | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Verificar sessão inicial
    const getSession = async () => {
      const { data: { session }, error } = await supabase.auth.getSession()
      
      if (error) {
        console.error('Erro ao obter sessão:', error)
      } else {
        setSession(session)
        setUser(session?.user ?? null)
      }
      
      setLoading(false)
    }

    getSession()

    // Escutar mudanças de auth
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        console.log('Auth event:', event)
        setSession(session)
        setUser(session?.user ?? null)
        setLoading(false)

        // Criar perfil na primeira vez
        if (event === 'SIGNED_IN' && session?.user) {
          await createUserProfile(session.user)
        }
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  // Criar perfil de usuário
  const createUserProfile = async (user: User) => {
    const { data, error } = await supabase
      .from('profiles')
      .select('id')
      .eq('id', user.id)
      .single()

    if (error && error.code === 'PGRST116') {
      // Usuário não existe, criar perfil
      const { error: insertError } = await supabase
        .from('profiles')
        .insert({
          id: user.id,
          username: user.email?.split('@')[0],
          full_name: user.user_metadata?.full_name || '',
          avatar_url: user.user_metadata?.avatar_url || '',
        })

      if (insertError) {
        console.error('Erro ao criar perfil:', insertError)
      }
    }
  }

  // Login com email/senha
  const signIn = async (email: string, password: string) => {
    setLoading(true)
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password,
    })
    setLoading(false)
    
    if (error) throw error
    return data
  }

  // Registro
  const signUp = async (email: string, password: string) => {
    setLoading(true)
    const { data, error } = await supabase.auth.signUp({
      email,
      password,
      options: {
        emailRedirectTo: `${window.location.origin}/auth/callback`,
      },
    })
    setLoading(false)
    
    if (error) throw error
    return data
  }

  // Login com GitHub
  const signInWithGitHub = async () => {
    const { data, error } = await supabase.auth.signInWithOAuth({
      provider: 'github',
      options: {
        redirectTo: `${window.location.origin}/auth/callback`,
      }
    })
    
    if (error) throw error
    return data
  }

  // Reset de senha
  const resetPassword = async (email: string) => {
    const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
      redirectTo: `${window.location.origin}/auth/reset-password`,
    })
    
    if (error) throw error
    return data
  }

  // Logout
  const signOut = async () => {
    setLoading(true)
    const { error } = await supabase.auth.signOut()
    setLoading(false)
    
    if (error) throw error
  }

  const value = {
    user,
    session,
    loading,
    signIn,
    signUp,
    signOut,
    signInWithGitHub,
    resetPassword,
  }

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}

⚠️Proteção de Rotas

Implemente middleware para proteger rotas administrativas e garantir que apenas usuários autenticados acessem recursos restritos.

middleware.ts
// middleware.ts
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createMiddlewareClient({ req, res })

  // Verificar sessão
  const {
    data: { session },
  } = await supabase.auth.getSession()

  // Rotas protegidas
  const protectedPaths = ['/dashboard', '/profile', '/admin']
  const isProtectedPath = protectedPaths.some(path => 
    req.nextUrl.pathname.startsWith(path)
  )

  // Redirecionar se não autenticado
  if (isProtectedPath && !session) {
    const redirectUrl = req.nextUrl.clone()
    redirectUrl.pathname = '/auth/login'
    redirectUrl.searchParams.set('redirectedFrom', req.nextUrl.pathname)
    return NextResponse.redirect(redirectUrl)
  }

  // Redirecionar admin se não for admin
  if (req.nextUrl.pathname.startsWith('/admin') && session) {
    const { data: profile } = await supabase
      .from('profiles')
      .select('role')
      .eq('id', session.user.id)
      .single()

    if (profile?.role !== 'admin') {
      return NextResponse.redirect(new URL('/dashboard', req.url))
    }
  }

  return res
}

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*', '/admin/:path*']
}

Real-time Subscriptions

Implementação de subscriptions em tempo real para atualizações instantâneas da interface.

hooks/useRealtime.ts
// hooks/useRealtime.ts
'use client'

import { useEffect, useState } from 'react'
import { supabase } from '@/lib/supabase'
import { Database } from '@/lib/supabase'

type Profile = Database['public']['Tables']['profiles']['Row']
type Post = Database['public']['Tables']['posts']['Row']

export function useRealtimeProfiles() {
  const [profiles, setProfiles] = useState<Profile[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Buscar dados iniciais
    const fetchProfiles = async () => {
      try {
        const { data, error } = await supabase
          .from('profiles')
          .select('*')
          .order('created_at', { ascending: false })
        
        if (error) throw error
        setProfiles(data || [])
      } catch (error) {
        console.error('Erro ao buscar perfis:', error)
      } finally {
        setLoading(false)
      }
    }

    fetchProfiles()

    // Subscription para mudanças em tempo real
    const subscription = supabase
      .channel('profiles-realtime')
      .on(
        'postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: 'profiles'
        },
        (payload) => {
          console.log('Mudança detectada:', payload)
          
          switch (payload.eventType) {
            case 'INSERT':
              setProfiles(prev => [payload.new as Profile, ...prev])
              break
              
            case 'UPDATE':
              setProfiles(prev => 
                prev.map(profile => 
                  profile.id === payload.new.id 
                    ? { ...profile, ...payload.new as Profile }
                    : profile
                )
              )
              break
              
            case 'DELETE':
              setProfiles(prev => 
                prev.filter(profile => profile.id !== payload.old.id)
              )
              break
          }
        }
      )
      .subscribe((status) => {
        console.log('Subscription status:', status)
      })

    return () => {
      subscription.unsubscribe()
    }
  }, [])

  return { profiles, loading }
}

// Hook para posts em tempo real
export function useRealtimePosts() {
  const [posts, setPosts] = useState<Post[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const fetchPosts = async () => {
      try {
        const { data, error } = await supabase
          .from('posts')
          .select(`
            *,
            author:profiles(username, avatar_url)
          `)
          .eq('published', true)
          .order('created_at', { ascending: false })
        
        if (error) throw error
        setPosts(data || [])
      } catch (error) {
        console.error('Erro ao buscar posts:', error)
      } finally {
        setLoading(false)
      }
    }

    fetchPosts()

    // Subscription com filtro para posts publicados
    const subscription = supabase
      .channel('posts-realtime')
      .on(
        'postgres_changes',
        {
          event: '*',
          schema: 'public',
          table: 'posts',
          filter: 'published=eq.true'
        },
        (payload) => {
          switch (payload.eventType) {
            case 'INSERT':
              // Buscar dados completos com join
              fetchPostWithAuthor(payload.new.id)
              break
              
            case 'UPDATE':
              if (payload.new.published) {
                fetchPostWithAuthor(payload.new.id)
              } else {
                setPosts(prev => 
                  prev.filter(post => post.id !== payload.new.id)
                )
              }
              break
              
            case 'DELETE':
              setPosts(prev => 
                prev.filter(post => post.id !== payload.old.id)
              )
              break
          }
        }
      )
      .subscribe()

    const fetchPostWithAuthor = async (postId: string) => {
      const { data, error } = await supabase
        .from('posts')
        .select(`
          *,
          author:profiles(username, avatar_url)
        `)
        .eq('id', postId)
        .single()

      if (data && !error) {
        setPosts(prev => {
          const exists = prev.find(p => p.id === postId)
          if (exists) {
            return prev.map(p => p.id === postId ? data : p)
          } else {
            return [data, ...prev]
          }
        })
      }
    }

    return () => {
      subscription.unsubscribe()
    }
  }, [])

  return { posts, loading }
}

Storage e Upload de Arquivos

Sistema completo de upload, gerenciamento e otimização de arquivos com Supabase Storage.

utils/storage.ts
// utils/storage.ts
import { supabase } from '@/lib/supabase'

export interface UploadResult {
  filePath: string
  publicUrl: string
  fileSize: number
}

// Upload de avatar com validação
export async function uploadAvatar(file: File, userId: string): Promise<UploadResult> {
  // Validações
  const maxSize = 5 * 1024 * 1024 // 5MB
  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
  
  if (file.size > maxSize) {
    throw new Error('Arquivo muito grande. Máximo 5MB.')
  }
  
  if (!allowedTypes.includes(file.type)) {
    throw new Error('Tipo de arquivo não permitido. Use JPG, PNG ou WebP.')
  }

  // Gerar nome único
  const fileExt = file.name.split('.').pop()
  const fileName = `${userId}-${Date.now()}.${fileExt}`
  const filePath = `avatars/${fileName}`

  // Upload com metadata
  const { data, error } = await supabase.storage
    .from('avatars')
    .upload(filePath, file, {
      cacheControl: '3600',
      upsert: false,
      metadata: {
        userId,
        originalName: file.name,
        uploadedAt: new Date().toISOString(),
      }
    })

  if (error) throw error

  // Obter URL pública
  const { data: { publicUrl } } = supabase.storage
    .from('avatars')
    .getPublicUrl(filePath)

  return {
    filePath: data.path,
    publicUrl,
    fileSize: file.size,
  }
}

// Upload com resize automático
export async function uploadWithResize(
  file: File, 
  bucket: string, 
  maxWidth: number = 800,
  quality: number = 0.8
): Promise<UploadResult> {
  
  // Criar canvas para resize
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  const img = new Image()
  
  return new Promise((resolve, reject) => {
    img.onload = async () => {
      // Calcular novas dimensões
      const ratio = Math.min(maxWidth / img.width, maxWidth / img.height)
      canvas.width = img.width * ratio
      canvas.height = img.height * ratio
      
      // Desenhar imagem redimensionada
      ctx?.drawImage(img, 0, 0, canvas.width, canvas.height)
      
      // Converter para blob
      canvas.toBlob(async (blob) => {
        if (!blob) {
          reject(new Error('Falha ao processar imagem'))
          return
        }
        
        try {
          // Upload da imagem processada
          const fileExt = file.name.split('.').pop()
          const fileName = `processed-${Date.now()}.${fileExt}`
          const filePath = `${bucket}/${fileName}`
          
          const { data, error } = await supabase.storage
            .from(bucket)
            .upload(filePath, blob)
            
          if (error) throw error
          
          const { data: { publicUrl } } = supabase.storage
            .from(bucket)
            .getPublicUrl(filePath)
            
          resolve({
            filePath: data.path,
            publicUrl,
            fileSize: blob.size,
          })
        } catch (error) {
          reject(error)
        }
      }, file.type, quality)
    }
    
    img.onerror = () => reject(new Error('Falha ao carregar imagem'))
    img.src = URL.createObjectURL(file)
  })
}

// Gerenciar arquivos do usuário
export async function getUserFiles(userId: string, bucket: string) {
  const { data, error } = await supabase.storage
    .from(bucket)
    .list('', {
      limit: 100,
      offset: 0,
      search: userId,
    })

  if (error) throw error
  
  return data?.map(file => ({
    ...file,
    publicUrl: supabase.storage.from(bucket).getPublicUrl(file.name).data.publicUrl,
  })) || []
}

// Deletar arquivo
export async function deleteFile(bucket: string, filePath: string) {
  const { error } = await supabase.storage
    .from(bucket)
    .remove([filePath])

  if (error) throw error
}

// Listar buckets disponíveis
export async function listBuckets() {
  const { data, error } = await supabase.storage.listBuckets()
  
  if (error) throw error
  return data
}

// Criar bucket programaticamente
export async function createBucket(
  name: string, 
  isPublic: boolean = true,
  allowedMimeTypes?: string[]
) {
  const { data, error } = await supabase.storage.createBucket(name, {
    public: isPublic,
    allowedMimeTypes,
    fileSizeLimit: 10 * 1024 * 1024, // 10MB
  })

  if (error) throw error
  return data
}

Edge Functions Serverless

Funções serverless para lógica backend customizada, processamento de dados e integrações externas.

supabase/functions/send-notification/index.ts
// supabase/functions/send-notification/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

interface NotificationRequest {
  userId: string
  title: string
  message: string
  type: 'email' | 'push' | 'sms'
  metadata?: Record<string, any>
}

serve(async (req) => {
  // Handle CORS
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    // Verificar autenticação
    const authHeader = req.headers.get('Authorization')
    if (!authHeader) {
      throw new Error('Token de autorização necessário')
    }

    // Criar cliente Supabase com service role
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
    )

    // Verificar usuário
    const { data: { user }, error: authError } = await supabase.auth.getUser(
      authHeader.replace('Bearer ', '')
    )

    if (authError || !user) {
      throw new Error('Token inválido')
    }

    // Parse do body
    const { userId, title, message, type, metadata }: NotificationRequest = 
      await req.json()

    // Validações
    if (!userId || !title || !message || !type) {
      throw new Error('Dados obrigatórios: userId, title, message, type')
    }

    // Buscar dados do usuário
    const { data: profile, error: profileError } = await supabase
      .from('profiles')
      .select('*')
      .eq('id', userId)
      .single()

    if (profileError || !profile) {
      throw new Error('Usuário não encontrado')
    }

    let result: any = {}

    // Processar notificação baseado no tipo
    switch (type) {
      case 'email':
        result = await sendEmail(profile, title, message, metadata)
        break
        
      case 'push':
        result = await sendPushNotification(profile, title, message, metadata)
        break
        
      case 'sms':
        result = await sendSMS(profile, title, message, metadata)
        break
        
      default:
        throw new Error('Tipo de notificação inválido')
    }

    // Salvar log da notificação
    const { error: logError } = await supabase
      .from('notification_logs')
      .insert({
        user_id: userId,
        type,
        title,
        message,
        status: 'sent',
        metadata: {
          ...metadata,
          result,
          sent_at: new Date().toISOString(),
        }
      })

    if (logError) {
      console.error('Erro ao salvar log:', logError)
    }

    return new Response(
      JSON.stringify({ 
        success: true, 
        message: 'Notificação enviada com sucesso',
        result 
      }),
      { 
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
        status: 200,
      },
    )

  } catch (error) {
    console.error('Erro na function:', error)
    
    return new Response(
      JSON.stringify({ 
        error: error.message || 'Erro interno do servidor' 
      }),
      { 
        headers: { ...corsHeaders, 'Content-Type': 'application/json' },
        status: 400,
      },
    )
  }
})

// Função para enviar email
async function sendEmail(profile: any, title: string, message: string, metadata?: any) {
  const emailData = {
    personalizations: [{
      to: [{ email: profile.email }],
      subject: title,
    }],
    from: { 
      email: Deno.env.get('FROM_EMAIL') || 'noreply@seuapp.com',
      name: 'Seu App'
    },
    content: [{
      type: 'text/html',
      value: `
        <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
          <h2 style="color: #333;">${title}</h2>
          <p style="color: #666; line-height: 1.6;">${message}</p>
          ${metadata?.actionUrl ? `
            <a href="${metadata.actionUrl}" 
               style="background: #007bff; color: white; padding: 12px 24px; 
                      text-decoration: none; border-radius: 4px; display: inline-block;">
              ${metadata.actionText || 'Ver Detalhes'}
            </a>
          ` : ''}
        </div>
      `
    }],
  }

  const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${Deno.env.get('SENDGRID_API_KEY')}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(emailData),
  })

  if (!response.ok) {
    const error = await response.text()
    throw new Error(`Falha ao enviar email: ${error}`)
  }

  return { provider: 'sendgrid', messageId: response.headers.get('x-message-id') }
}

// Função para push notification
async function sendPushNotification(profile: any, title: string, message: string, metadata?: any) {
  // Implementar com FCM, OneSignal, etc.
  const pushData = {
    to: profile.push_token,
    title,
    body: message,
    data: metadata || {},
  }

  // Exemplo com FCM
  const response = await fetch('https://fcm.googleapis.com/fcm/send', {
    method: 'POST',
    headers: {
      'Authorization': `key=${Deno.env.get('FCM_SERVER_KEY')}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(pushData),
  })

  if (!response.ok) {
    throw new Error('Falha ao enviar push notification')
  }

  const result = await response.json()
  return { provider: 'fcm', messageId: result.multicast_id }
}

// Função para SMS
async function sendSMS(profile: any, title: string, message: string, metadata?: any) {
  if (!profile.phone) {
    throw new Error('Usuário não possui telefone cadastrado')
  }

  const smsData = {
    to: profile.phone,
    body: `${title}

${message}`,
  }

  // Exemplo com Twilio
  const response = await fetch(`https://api.twilio.com/2010-04-01/Accounts/${Deno.env.get('TWILIO_ACCOUNT_SID')}/Messages.json`, {
    method: 'POST',
    headers: {
      'Authorization': `Basic ${btoa(`${Deno.env.get('TWILIO_ACCOUNT_SID')}:${Deno.env.get('TWILIO_AUTH_TOKEN')}`)}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      To: profile.phone,
      From: Deno.env.get('TWILIO_PHONE_NUMBER') || '',
      Body: smsData.body,
    }),
  })

  if (!response.ok) {
    throw new Error('Falha ao enviar SMS')
  }

  const result = await response.json()
  return { provider: 'twilio', messageId: result.sid }
}

Performance e Otimizações

Técnicas avançadas para maximizar performance, reduzir latência e otimizar queries.

Connection Pooling

Gerenciar conexões PostgreSQL eficientemente

Query Optimization

Indexes, explain plans e query performance

Caching Strategy

Redis, CDN e cache de aplicação

Real-time Scaling

Gerenciar subscriptions em alta escala

utils/performance.ts
// utils/performance.ts
import { supabase } from '@/lib/supabase'

// Pagination eficiente com cursor
export async function getPaginatedData<T>(
  table: string,
  options: {
    cursor?: string
    limit?: number
    orderBy?: string
    ascending?: boolean
    select?: string
    filters?: Record<string, any>
  } = {}
) {
  const {
    cursor,
    limit = 20,
    orderBy = 'created_at',
    ascending = false,
    select = '*',
    filters = {}
  } = options

  let query = supabase
    .from(table)
    .select(select, { count: 'exact' })
    .order(orderBy, { ascending })
    .limit(limit)

  // Aplicar filtros
  Object.entries(filters).forEach(([key, value]) => {
    if (value !== undefined && value !== null) {
      query = query.eq(key, value)
    }
  })

  // Cursor pagination
  if (cursor) {
    query = ascending 
      ? query.gt(orderBy, cursor)
      : query.lt(orderBy, cursor)
  }

  const { data, error, count } = await query

  if (error) throw error

  const nextCursor = data && data.length > 0 
    ? data[data.length - 1][orderBy]
    : null

  return {
    data: data || [],
    nextCursor,
    hasMore: data ? data.length === limit : false,
    total: count || 0,
  }
}

// Query com cache Redis
export async function getCachedQuery<T>(
  cacheKey: string,
  queryFn: () => Promise<T>,
  ttlSeconds: number = 300
): Promise<T> {
  
  // Tentar buscar do cache primeiro
  try {
    const cached = localStorage.getItem(cacheKey)
    if (cached) {
      const { data, timestamp } = JSON.parse(cached)
      const isValid = Date.now() - timestamp < ttlSeconds * 1000
      
      if (isValid) {
        return data
      }
    }
  } catch (error) {
    console.warn('Erro ao ler cache:', error)
  }

  // Executar query se não está em cache
  const result = await queryFn()

  // Salvar no cache
  try {
    localStorage.setItem(cacheKey, JSON.stringify({
      data: result,
      timestamp: Date.now(),
    }))
  } catch (error) {
    console.warn('Erro ao salvar cache:', error)
  }

  return result
}

// Batch operations para múltiplas queries
export async function batchQueries(operations: Promise<any>[]) {
  const results = await Promise.allSettled(operations)
  
  const successful = results
    .filter((result): result is PromiseFulfilledResult<any> => 
      result.status === 'fulfilled'
    )
    .map(result => result.value)

  const errors = results
    .filter((result): result is PromiseRejectedResult => 
      result.status === 'rejected'
    )
    .map(result => result.reason)

  return { successful, errors }
}

// Connection singleton otimizada
class SupabaseManager {
  private static instance: SupabaseManager
  private connectionPool: Map<string, any> = new Map()

  static getInstance(): SupabaseManager {
    if (!SupabaseManager.instance) {
      SupabaseManager.instance = new SupabaseManager()
    }
    return SupabaseManager.instance
  }

  getConnection(config?: any) {
    const key = JSON.stringify(config || 'default')
    
    if (!this.connectionPool.has(key)) {
      const client = createClient(
        process.env.NEXT_PUBLIC_SUPABASE_URL!,
        process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
        {
          auth: {
            persistSession: true,
            autoRefreshToken: true,
          },
          db: {
            schema: 'public',
          },
          ...config,
        }
      )
      
      this.connectionPool.set(key, client)
    }
    
    return this.connectionPool.get(key)
  }

  clearConnections() {
    this.connectionPool.clear()
  }
}

export const supabaseManager = SupabaseManager.getInstance()

Deploy e Configuração de Produção

Configuração completa para ambiente de produção com monitoring, backup e segurança.

1
Configurar Variáveis de Ambiente:
.env.local
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://seu-projeto.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=sua-chave-publica
SUPABASE_SERVICE_ROLE_KEY=sua-chave-privada

# Configurações de produção
DATABASE_URL=postgresql://postgres:[password]@db.[ref].supabase.co:5432/postgres
SUPABASE_JWT_SECRET=seu-jwt-secret

# Integrações externas
SENDGRID_API_KEY=sua-chave-sendgrid
TWILIO_ACCOUNT_SID=seu-sid-twilio
FCM_SERVER_KEY=sua-chave-fcm
2
Script de Backup Automático:
scripts/backup.ts
// scripts/backup.ts
import { createClient } from '@supabase/supabase-js'
import fs from 'fs/promises'

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)

async function backupTables() {
  const tables = ['profiles', 'posts', 'comments', 'notification_logs']
  const backupData: Record<string, any[]> = {}

  for (const table of tables) {
    console.log(`📦 Fazendo backup da tabela: ${table}`)
    
    const { data, error } = await supabase
      .from(table)
      .select('*')
    
    if (error) {
      console.error(`❌ Erro na tabela ${table}:`, error)
      continue
    }
    
    backupData[table] = data || []
    console.log(`✅ ${table}: ${data?.length || 0} registros`)
  }

  // Salvar backup com timestamp
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
  const filename = `backup-${timestamp}.json`
  
  await fs.writeFile(filename, JSON.stringify(backupData, null, 2))
  console.log(`💾 Backup salvo: ${filename}`)

  return filename
}

// Executar backup
if (require.main === module) {
  backupTables()
    .then(filename => console.log(`🎉 Backup concluído: ${filename}`))
    .catch(console.error)
}
3
Monitoring e Health Checks:
pages/api/health.ts
// pages/api/health.ts
import { NextApiRequest, NextApiResponse } from 'next'
import { supabase } from '@/lib/supabase'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'GET') {
    return res.status(405).json({ error: 'Method not allowed' })
  }

  try {
    const checks = await Promise.allSettled([
      // Database health
      supabase.from('profiles').select('id').limit(1),
      
      // Auth health
      supabase.auth.getSession(),
      
      // Storage health
      supabase.storage.listBuckets(),
    ])

    const dbHealth = checks[0].status === 'fulfilled'
    const authHealth = checks[1].status === 'fulfilled'
    const storageHealth = checks[2].status === 'fulfilled'

    const isHealthy = dbHealth && authHealth && storageHealth

    const healthData = {
      status: isHealthy ? 'healthy' : 'unhealthy',
      timestamp: new Date().toISOString(),
      services: {
        database: dbHealth ? 'ok' : 'error',
        auth: authHealth ? 'ok' : 'error',
        storage: storageHealth ? 'ok' : 'error',
      },
      version: process.env.npm_package_version || '1.0.0',
      uptime: process.uptime(),
    }

    res.status(isHealthy ? 200 : 503).json(healthData)
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      timestamp: new Date().toISOString(),
      error: 'Health check failed',
    })
  }
}

Migrations e Versionamento de Schema

Sistema robusto de migrations para evolução controlada do database em produção, mantendo integridade e versionamento adequado.

migrations/20240120_001_create_users_system.sql
// migrations/20240120_001_create_users_system.sql
-- Migration: Sistema de usuários avançado
-- Autor: DevTeam
-- Data: 2024-01-20

BEGIN;

-- Extensões necessárias
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- Enum para roles de usuário
CREATE TYPE user_role AS ENUM ('user', 'moderator', 'admin', 'super_admin');

-- Enum para status de conta
CREATE TYPE account_status AS ENUM ('active', 'inactive', 'suspended', 'pending_verification');

-- Tabela de organizações
CREATE TABLE organizations (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  name TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  description TEXT,
  logo_url TEXT,
  website TEXT,
  subscription_plan TEXT DEFAULT 'free' CHECK (subscription_plan IN ('free', 'pro', 'enterprise')),
  max_users INTEGER DEFAULT 5,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Índices para organizações
CREATE INDEX idx_organizations_slug ON organizations(slug);
CREATE INDEX idx_organizations_plan ON organizations(subscription_plan);

-- Tabela de perfis expandida
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS organization_id UUID REFERENCES organizations(id);
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS role user_role DEFAULT 'user';
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS status account_status DEFAULT 'pending_verification';
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMP WITH TIME ZONE;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT false;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS phone TEXT;
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS timezone TEXT DEFAULT 'UTC';
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS preferences JSONB DEFAULT '{}';

-- Índices para profiles
CREATE INDEX idx_profiles_organization ON profiles(organization_id);
CREATE INDEX idx_profiles_role ON profiles(role);
CREATE INDEX idx_profiles_status ON profiles(status);
CREATE INDEX idx_profiles_email_verified ON profiles(email_verified);

-- Tabela de convites
CREATE TABLE invitations (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
  email TEXT NOT NULL,
  role user_role DEFAULT 'user',
  invited_by UUID REFERENCES auth.users(id),
  token TEXT UNIQUE NOT NULL DEFAULT encode(gen_random_bytes(32), 'hex'),
  expires_at TIMESTAMP WITH TIME ZONE DEFAULT (NOW() + INTERVAL '7 days'),
  accepted_at TIMESTAMP WITH TIME ZONE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Índices para invitations
CREATE INDEX idx_invitations_token ON invitations(token);
CREATE INDEX idx_invitations_email ON invitations(email);
CREATE INDEX idx_invitations_org ON invitations(organization_id);

-- Tabela de auditoria
CREATE TABLE audit_logs (
  id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id),
  organization_id UUID REFERENCES organizations(id),
  action TEXT NOT NULL,
  resource_type TEXT NOT NULL,
  resource_id TEXT,
  metadata JSONB DEFAULT '{}',
  ip_address INET,
  user_agent TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Índices para audit_logs
CREATE INDEX idx_audit_logs_user ON audit_logs(user_id);
CREATE INDEX idx_audit_logs_org ON audit_logs(organization_id);
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);

-- RLS Policies

-- Organizations
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Usuários podem ver própria organização"
ON organizations FOR SELECT
USING (
  id IN (
    SELECT organization_id 
    FROM profiles 
    WHERE id = auth.uid()
  )
);

CREATE POLICY "Admins podem atualizar organização"
ON organizations FOR UPDATE
USING (
  id IN (
    SELECT organization_id 
    FROM profiles 
    WHERE id = auth.uid() 
    AND role IN ('admin', 'super_admin')
  )
);

-- Invitations
ALTER TABLE invitations ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Admins podem gerenciar convites"
ON invitations FOR ALL
USING (
  organization_id IN (
    SELECT organization_id 
    FROM profiles 
    WHERE id = auth.uid() 
    AND role IN ('admin', 'super_admin')
  )
);

CREATE POLICY "Usuários podem ver próprios convites"
ON invitations FOR SELECT
USING (email = auth.email());

-- Audit Logs
ALTER TABLE audit_logs ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Usuários podem ver próprios logs"
ON audit_logs FOR SELECT
USING (user_id = auth.uid());

CREATE POLICY "Admins podem ver logs da organização"
ON audit_logs FOR SELECT
USING (
  organization_id IN (
    SELECT organization_id 
    FROM profiles 
    WHERE id = auth.uid() 
    AND role IN ('admin', 'super_admin')
  )
);

-- Funções auxiliares

-- Função para atualizar updated_at
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

-- Triggers para updated_at
CREATE TRIGGER update_organizations_updated_at 
BEFORE UPDATE ON organizations 
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- Função para criar organização padrão
CREATE OR REPLACE FUNCTION create_default_organization_for_user()
RETURNS TRIGGER AS $$
DECLARE
    org_id UUID;
BEGIN
    -- Criar organização pessoal
    INSERT INTO organizations (name, slug, description)
    VALUES (
        COALESCE(NEW.full_name, 'Organização Pessoal'),
        LOWER(REPLACE(COALESCE(NEW.username, NEW.id::text), ' ', '-')),
        'Organização pessoal criada automaticamente'
    )
    RETURNING id INTO org_id;
    
    -- Atualizar perfil com organização
    NEW.organization_id = org_id;
    NEW.role = 'admin';
    
    RETURN NEW;
END;
$$ language 'plpgsql';

-- Trigger para criar organização automática
CREATE TRIGGER create_org_for_new_profile
BEFORE INSERT ON profiles
FOR EACH ROW 
WHEN (NEW.organization_id IS NULL)
EXECUTE FUNCTION create_default_organization_for_user();

-- Função para log de auditoria
CREATE OR REPLACE FUNCTION log_user_action(
    p_action TEXT,
    p_resource_type TEXT,
    p_resource_id TEXT DEFAULT NULL,
    p_metadata JSONB DEFAULT '{}'
)
RETURNS UUID AS $$
DECLARE
    log_id UUID;
    user_org_id UUID;
BEGIN
    -- Buscar organização do usuário
    SELECT organization_id INTO user_org_id
    FROM profiles 
    WHERE id = auth.uid();
    
    -- Inserir log
    INSERT INTO audit_logs (
        user_id, 
        organization_id, 
        action, 
        resource_type, 
        resource_id, 
        metadata
    )
    VALUES (
        auth.uid(), 
        user_org_id, 
        p_action, 
        p_resource_type, 
        p_resource_id, 
        p_metadata
    )
    RETURNING id INTO log_id;
    
    RETURN log_id;
END;
$$ language 'plpgsql' SECURITY DEFINER;

-- Seeds de dados iniciais
INSERT INTO organizations (id, name, slug, description, subscription_plan) VALUES
('00000000-0000-0000-0000-000000000001', 'CrazyStack', 'crazystack', 'Organização principal da CrazyStack', 'enterprise');

COMMIT;

ℹ️Migration Management

Use sempre transações (BEGIN/COMMIT) em migrations, documente mudanças e teste em ambiente de desenvolvimento primeiro. Mantenha migrations atômicas e reversíveis.

scripts/migrate.ts
// scripts/migrate.ts
import { createClient } from '@supabase/supabase-js'
import fs from 'fs/promises'
import path from 'path'

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)

interface Migration {
  id: string
  filename: string
  sql: string
  applied_at?: string
}

// Tabela para controle de migrations
const MIGRATIONS_TABLE = `
CREATE TABLE IF NOT EXISTS _migrations (
  id TEXT PRIMARY KEY,
  filename TEXT NOT NULL,
  applied_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
`

async function initMigrationsTable() {
  const { error } = await supabase.rpc('exec_sql', { 
    sql_query: MIGRATIONS_TABLE 
  })
  
  if (error) {
    console.error('Erro ao criar tabela de migrations:', error)
    throw error
  }
}

async function getAppliedMigrations(): Promise<string[]> {
  const { data, error } = await supabase
    .from('_migrations')
    .select('id')
    .order('applied_at')
  
  if (error) throw error
  return data?.map(m => m.id) || []
}

async function getMigrationFiles(): Promise<Migration[]> {
  const migrationsDir = path.join(process.cwd(), 'migrations')
  const files = await fs.readdir(migrationsDir)
  
  const migrations: Migration[] = []
  
  for (const file of files.filter(f => f.endsWith('.sql'))) {
    const id = file.replace('.sql', '')
    const filepath = path.join(migrationsDir, file)
    const sql = await fs.readFile(filepath, 'utf8')
    
    migrations.push({ id, filename: file, sql })
  }
  
  return migrations.sort((a, b) => a.id.localeCompare(b.id))
}

async function runMigration(migration: Migration): Promise<void> {
  console.log(`📦 Aplicando migration: ${migration.filename}`)
  
  try {
    // Executar SQL da migration
    const { error: sqlError } = await supabase.rpc('exec_sql', {
      sql_query: migration.sql
    })
    
    if (sqlError) throw sqlError
    
    // Registrar migration como aplicada
    const { error: recordError } = await supabase
      .from('_migrations')
      .insert({
        id: migration.id,
        filename: migration.filename
      })
    
    if (recordError) throw recordError
    
    console.log(`✅ Migration aplicada: ${migration.filename}`)
    
  } catch (error) {
    console.error(`❌ Erro na migration ${migration.filename}:`, error)
    throw error
  }
}

async function runMigrations() {
  try {
    console.log('🚀 Iniciando migrations...')
    
    // Inicializar tabela de controle
    await initMigrationsTable()
    
    // Buscar migrations aplicadas e disponíveis
    const [appliedMigrations, availableMigrations] = await Promise.all([
      getAppliedMigrations(),
      getMigrationFiles()
    ])
    
    // Filtrar migrations pendentes
    const pendingMigrations = availableMigrations.filter(
      migration => !appliedMigrations.includes(migration.id)
    )
    
    if (pendingMigrations.length === 0) {
      console.log('✨ Nenhuma migration pendente')
      return
    }
    
    console.log(`📋 ${pendingMigrations.length} migrations pendentes`)
    
    // Aplicar migrations em ordem
    for (const migration of pendingMigrations) {
      await runMigration(migration)
    }
    
    console.log('🎉 Todas as migrations foram aplicadas com sucesso!')
    
  } catch (error) {
    console.error('💥 Erro ao executar migrations:', error)
    process.exit(1)
  }
}

// Executar se chamado diretamente
if (require.main === module) {
  runMigrations()
}

API REST Automática e Customização

Aproveitamento máximo da API REST gerada automaticamente pelo Supabase, com customizações avançadas e endpoints específicos.

lib/api-client.ts
// lib/api-client.ts
import { supabase } from './supabase'
import { Database } from './database.types'

type Tables = Database['public']['Tables']
type Profile = Tables['profiles']['Row']
type Post = Tables['posts']['Row']
type Organization = Tables['organizations']['Row']

// Cliente API tipado
export class SupabaseAPI {
  
  // Métodos para Profiles
  static async getProfile(userId: string): Promise<Profile | null> {
    const { data, error } = await supabase
      .from('profiles')
      .select(`
        *,
        organization:organizations(*)
      `)
      .eq('id', userId)
      .single()
    
    if (error) throw new APIError('Erro ao buscar perfil', error)
    return data
  }

  static async updateProfile(
    userId: string, 
    updates: Partial<Profile>
  ): Promise<Profile> {
    const { data, error } = await supabase
      .from('profiles')
      .update(updates)
      .eq('id', userId)
      .select()
      .single()
    
    if (error) throw new APIError('Erro ao atualizar perfil', error)
    
    // Log da ação
    await this.logAction('update', 'profile', userId, { updates })
    
    return data
  }

  // Métodos para Posts com filtros avançados
  static async getPosts(options: {
    authorId?: string
    organizationId?: string
    published?: boolean
    search?: string
    tags?: string[]
    page?: number
    limit?: number
    sortBy?: 'created_at' | 'updated_at' | 'title'
    sortOrder?: 'asc' | 'desc'
  } = {}): Promise<{
    posts: Post[]
    total: number
    page: number
    totalPages: number
  }> {
    const {
      authorId,
      organizationId,
      published = true,
      search,
      tags,
      page = 1,
      limit = 20,
      sortBy = 'created_at',
      sortOrder = 'desc'
    } = options

    let query = supabase
      .from('posts')
      .select(`
        *,
        author:profiles!author_id(username, full_name, avatar_url),
        organization:organizations!organization_id(name, slug)
      `, { count: 'exact' })

    // Aplicar filtros
    if (authorId) query = query.eq('author_id', authorId)
    if (organizationId) query = query.eq('organization_id', organizationId)
    if (published !== undefined) query = query.eq('published', published)
    
    // Busca por texto
    if (search) {
      query = query.or(`title.ilike.%${search}%,content.ilike.%${search}%`)
    }
    
    // Filtro por tags (assumindo coluna tags JSONB)
    if (tags && tags.length > 0) {
      const tagFilters = tags.map(tag => `tags.cs.[""${tag}""]`).join(',')
      query = query.or(tagFilters)
    }

    // Ordenação e paginação
    const offset = (page - 1) * limit
    query = query
      .order(sortBy, { ascending: sortOrder === 'asc' })
      .range(offset, offset + limit - 1)

    const { data, error, count } = await query

    if (error) throw new APIError('Erro ao buscar posts', error)

    return {
      posts: data || [],
      total: count || 0,
      page,
      totalPages: Math.ceil((count || 0) / limit)
    }
  }

  static async createPost(postData: {
    title: string
    content: string
    published?: boolean
    tags?: string[]
    metadata?: Record<string, any>
  }): Promise<Post> {
    const { data: userProfile } = await supabase
      .from('profiles')
      .select('organization_id')
      .eq('id', (await supabase.auth.getUser()).data.user?.id!)
      .single()

    const { data, error } = await supabase
      .from('posts')
      .insert({
        ...postData,
        author_id: (await supabase.auth.getUser()).data.user?.id!,
        organization_id: userProfile?.organization_id,
      })
      .select()
      .single()

    if (error) throw new APIError('Erro ao criar post', error)
    
    await this.logAction('create', 'post', data.id, postData)
    
    return data
  }

  // Métodos para Organizations
  static async getOrganizationMembers(orgId: string): Promise<Profile[]> {
    const { data, error } = await supabase
      .from('profiles')
      .select(`
        *,
        user:auth.users!id(email, created_at)
      `)
      .eq('organization_id', orgId)
      .order('role', { ascending: false })
      .order('created_at', { ascending: true })

    if (error) throw new APIError('Erro ao buscar membros', error)
    return data || []
  }

  static async inviteUserToOrganization(
    email: string,
    role: 'user' | 'moderator' | 'admin' = 'user'
  ): Promise<void> {
    const user = await supabase.auth.getUser()
    if (!user.data.user) throw new Error('Usuário não autenticado')

    const { data: profile } = await supabase
      .from('profiles')
      .select('organization_id, role')
      .eq('id', user.data.user.id)
      .single()

    if (!profile || !['admin', 'super_admin'].includes(profile.role!)) {
      throw new Error('Permissão insuficiente')
    }

    const { error } = await supabase
      .from('invitations')
      .insert({
        organization_id: profile.organization_id,
        email,
        role,
        invited_by: user.data.user.id
      })

    if (error) throw new APIError('Erro ao criar convite', error)
    
    await this.logAction('invite', 'user', email, { role })
  }

  // Métodos para Analytics
  static async getOrganizationStats(orgId: string): Promise<{
    totalMembers: number
    totalPosts: number
    publishedPosts: number
    draftPosts: number
    recentActivity: any[]
  }> {
    const [membersResult, postsResult, activityResult] = await Promise.all([
      supabase
        .from('profiles')
        .select('id', { count: 'exact', head: true })
        .eq('organization_id', orgId),
      
      supabase
        .from('posts')
        .select('published', { count: 'exact' })
        .eq('organization_id', orgId),
      
      supabase
        .from('audit_logs')
        .select(`
          action,
          resource_type,
          created_at,
          user:profiles!user_id(username, full_name)
        `)
        .eq('organization_id', orgId)
        .order('created_at', { ascending: false })
        .limit(10)
    ])

    const totalMembers = membersResult.count || 0
    const allPosts = postsResult.data || []
    const publishedPosts = allPosts.filter(p => p.published).length
    const draftPosts = allPosts.length - publishedPosts

    return {
      totalMembers,
      totalPosts: allPosts.length,
      publishedPosts,
      draftPosts,
      recentActivity: activityResult.data || []
    }
  }

  // Método para logging de ações
  private static async logAction(
    action: string,
    resourceType: string,
    resourceId: string,
    metadata: any = {}
  ): Promise<void> {
    try {
      await supabase.rpc('log_user_action', {
        p_action: action,
        p_resource_type: resourceType,
        p_resource_id: resourceId,
        p_metadata: metadata
      })
    } catch (error) {
      console.warn('Erro ao registrar log:', error)
    }
  }

  // Busca universal com full-text search
  static async searchAll(query: string, filters?: {
    types?: ('posts' | 'profiles' | 'organizations')[]
    organizationId?: string
  }): Promise<{
    posts: Post[]
    profiles: Profile[]
    organizations: Organization[]
  }> {
    const searchTypes = filters?.types || ['posts', 'profiles', 'organizations']
    const results: any = { posts: [], profiles: [], organizations: [] }

    const searches = []

    if (searchTypes.includes('posts')) {
      searches.push(
        supabase
          .from('posts')
          .select('*')
          .textSearch('title', query)
          .eq('published', true)
          .limit(10)
      )
    }

    if (searchTypes.includes('profiles')) {
      searches.push(
        supabase
          .from('profiles')
          .select('*')
          .or(`full_name.ilike.%${query}%,username.ilike.%${query}%`)
          .limit(10)
      )
    }

    if (searchTypes.includes('organizations')) {
      searches.push(
        supabase
          .from('organizations')
          .select('*')
          .or(`name.ilike.%${query}%,description.ilike.%${query}%`)
          .limit(10)
      )
    }

    const searchResults = await Promise.allSettled(searches)
    
    searchTypes.forEach((type, index) => {
      const result = searchResults[index]
      if (result.status === 'fulfilled' && result.value.data) {
        results[type] = result.value.data
      }
    })

    return results
  }
}

// Classe de erro customizada
export class APIError extends Error {
  constructor(message: string, public originalError?: any) {
    super(message)
    this.name = 'APIError'
  }
}

// Hooks React para usar a API
export function useProfile(userId?: string) {
  const [profile, setProfile] = useState<Profile | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    if (!userId) return

    const fetchProfile = async () => {
      try {
        setLoading(true)
        const data = await SupabaseAPI.getProfile(userId)
        setProfile(data)
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Erro desconhecido')
      } finally {
        setLoading(false)
      }
    }

    fetchProfile()
  }, [userId])

  return { profile, loading, error }
}

⚠️API Customizada

Para casos específicos que a API REST automática não atende, crie endpoints customizados usando Edge Functions ou páginas API do Next.js com validação adequada.

Testing e Quality Assurance

Estratégias completas de teste para aplicações Supabase, incluindo testes unitários, integração e end-to-end.

__tests__/supabase.test.ts
// __tests__/supabase.test.ts
import { createClient } from '@supabase/supabase-js'
import { Database } from '@/lib/database.types'

// Setup de teste com Supabase local
const supabaseUrl = process.env.SUPABASE_TEST_URL || 'http://localhost:54321'
const supabaseKey = process.env.SUPABASE_TEST_ANON_KEY || 'test-key'

const supabase = createClient<Database>(supabaseUrl, supabaseKey)

describe('Supabase Integration Tests', () => {
  let testUserId: string
  let testOrgId: string

  beforeAll(async () => {
    // Setup inicial para testes
    await setupTestDatabase()
  })

  afterAll(async () => {
    // Cleanup após testes
    await cleanupTestDatabase()
  })

  beforeEach(async () => {
    // Reset do estado antes de cada teste
    await resetTestData()
  })

  describe('Authentication', () => {
    test('deve criar usuário com email e senha', async () => {
      const email = `test-${Date.now()}@example.com`
      const password = 'password123'

      const { data, error } = await supabase.auth.signUp({
        email,
        password,
      })

      expect(error).toBeNull()
      expect(data.user).toBeDefined()
      expect(data.user?.email).toBe(email)
      
      testUserId = data.user!.id
    })

    test('deve fazer login com credenciais válidas', async () => {
      const email = `test-${Date.now()}@example.com`
      const password = 'password123'

      // Criar usuário primeiro
      await supabase.auth.signUp({ email, password })
      
      // Fazer login
      const { data, error } = await supabase.auth.signInWithPassword({
        email,
        password,
      })

      expect(error).toBeNull()
      expect(data.user).toBeDefined()
      expect(data.session).toBeDefined()
    })

    test('deve falhar com credenciais inválidas', async () => {
      const { data, error } = await supabase.auth.signInWithPassword({
        email: 'invalid@example.com',
        password: 'wrongpassword',
      })

      expect(error).toBeDefined()
      expect(data.user).toBeNull()
    })
  })

  describe('Database Operations', () => {
    test('deve criar perfil de usuário', async () => {
      const profileData = {
        id: testUserId,
        username: 'testuser',
        full_name: 'Test User',
        bio: 'Test bio',
      }

      const { data, error } = await supabase
        .from('profiles')
        .insert(profileData)
        .select()
        .single()

      expect(error).toBeNull()
      expect(data).toMatchObject(profileData)
    })

    test('deve criar organização', async () => {
      const orgData = {
        name: 'Test Organization',
        slug: 'test-org',
        description: 'Test organization description',
      }

      const { data, error } = await supabase
        .from('organizations')
        .insert(orgData)
        .select()
        .single()

      expect(error).toBeNull()
      expect(data).toMatchObject(orgData)
      testOrgId = data.id
    })

    test('deve aplicar RLS corretamente', async () => {
      // Tentar acessar dados de outro usuário deve falhar
      const { data, error } = await supabase
        .from('profiles')
        .select('*')
        .eq('id', 'other-user-id')

      expect(data).toEqual([]) // RLS deve bloquear acesso
    })
  })

  describe('Real-time Subscriptions', () => {
    test('deve receber updates em tempo real', async () => {
      const updates: any[] = []
      
      // Configurar subscription
      const subscription = supabase
        .channel('test-channel')
        .on(
          'postgres_changes',
          { event: '*', schema: 'public', table: 'profiles' },
          (payload) => {
            updates.push(payload)
          }
        )
        .subscribe()

      // Aguardar conexão
      await new Promise(resolve => setTimeout(resolve, 1000))

      // Criar novo perfil
      await supabase
        .from('profiles')
        .insert({
          id: 'test-realtime-user',
          username: 'realtimetest',
        })

      // Aguardar update
      await new Promise(resolve => setTimeout(resolve, 1000))

      expect(updates.length).toBeGreaterThan(0)
      expect(updates[0].eventType).toBe('INSERT')
      
      subscription.unsubscribe()
    })
  })

  describe('Storage Operations', () => {
    test('deve fazer upload de arquivo', async () => {
      const fileName = 'test-file.txt'
      const fileContent = 'Test file content'
      const file = new Blob([fileContent], { type: 'text/plain' })

      const { data, error } = await supabase.storage
        .from('test-bucket')
        .upload(fileName, file)

      expect(error).toBeNull()
      expect(data?.path).toBe(fileName)
    })

    test('deve deletar arquivo', async () => {
      const fileName = 'test-delete.txt'
      const file = new Blob(['test'], { type: 'text/plain' })

      // Upload primeiro
      await supabase.storage
        .from('test-bucket')
        .upload(fileName, file)

      // Deletar
      const { error } = await supabase.storage
        .from('test-bucket')
        .remove([fileName])

      expect(error).toBeNull()
    })
  })

  describe('Edge Functions', () => {
    test('deve chamar edge function com sucesso', async () => {
      const { data, error } = await supabase.functions.invoke('test-function', {
        body: { message: 'Hello from test' }
      })

      expect(error).toBeNull()
      expect(data).toBeDefined()
    })
  })
})

// Utilities para testes
async function setupTestDatabase() {
  // Criar buckets de teste
  await supabase.storage.createBucket('test-bucket', {
    public: true,
    fileSizeLimit: 1024 * 1024, // 1MB
  })

  // Executar migrations de teste se necessário
  const { error } = await supabase.rpc('exec_sql', {
    sql_query: `
      -- Adicionar dados de teste se necessário
      INSERT INTO test_data (name) VALUES ('test') ON CONFLICT DO NOTHING;
    `
  })

  if (error) console.warn('Setup warning:', error)
}

async function cleanupTestDatabase() {
  // Limpar dados de teste
  await supabase.rpc('exec_sql', {
    sql_query: `
      DELETE FROM profiles WHERE username LIKE 'test%';
      DELETE FROM organizations WHERE slug LIKE 'test%';
    `
  })

  // Deletar bucket de teste
  await supabase.storage.deleteBucket('test-bucket')
}

async function resetTestData() {
  // Reset entre testes
  await supabase.rpc('exec_sql', {
    sql_query: `
      TRUNCATE TABLE audit_logs;
      DELETE FROM profiles WHERE username LIKE 'test%';
    `
  })
}
__tests__/api-client.test.ts
// __tests__/api-client.test.ts
import { SupabaseAPI, APIError } from '@/lib/api-client'
import { supabase } from '@/lib/supabase'

// Mock do Supabase para testes unitários
jest.mock('@/lib/supabase', () => ({
  supabase: {
    from: jest.fn(),
    auth: {
      getUser: jest.fn(),
    },
    rpc: jest.fn(),
  }
}))

const mockSupabase = supabase as jest.Mocked<typeof supabase>

describe('SupabaseAPI', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  describe('getProfile', () => {
    test('deve retornar perfil com sucesso', async () => {
      const mockProfile = {
        id: 'user-1',
        username: 'testuser',
        full_name: 'Test User',
      }

      mockSupabase.from.mockReturnValue({
        select: jest.fn().mockReturnValue({
          eq: jest.fn().mockReturnValue({
            single: jest.fn().mockResolvedValue({
              data: mockProfile,
              error: null
            })
          })
        })
      } as any)

      const result = await SupabaseAPI.getProfile('user-1')

      expect(result).toEqual(mockProfile)
      expect(mockSupabase.from).toHaveBeenCalledWith('profiles')
    })

    test('deve lançar APIError em caso de erro', async () => {
      const mockError = { message: 'Database error' }

      mockSupabase.from.mockReturnValue({
        select: jest.fn().mockReturnValue({
          eq: jest.fn().mockReturnValue({
            single: jest.fn().mockResolvedValue({
              data: null,
              error: mockError
            })
          })
        })
      } as any)

      await expect(SupabaseAPI.getProfile('user-1')).rejects.toThrow(APIError)
    })
  })

  describe('getPosts', () => {
    test('deve retornar posts paginados', async () => {
      const mockPosts = [
        { id: 'post-1', title: 'Post 1' },
        { id: 'post-2', title: 'Post 2' }
      ]

      mockSupabase.from.mockReturnValue({
        select: jest.fn().mockReturnValue({
          eq: jest.fn().mockReturnValue({
            order: jest.fn().mockReturnValue({
              range: jest.fn().mockResolvedValue({
                data: mockPosts,
                error: null,
                count: 2
              })
            })
          })
        })
      } as any)

      const result = await SupabaseAPI.getPosts({ page: 1, limit: 10 })

      expect(result).toEqual({
        posts: mockPosts,
        total: 2,
        page: 1,
        totalPages: 1
      })
    })
  })

  describe('searchAll', () => {
    test('deve buscar em múltiplas tabelas', async () => {
      const mockResults = {
        posts: [{ id: 'post-1', title: 'Search result' }],
        profiles: [{ id: 'user-1', username: 'searchuser' }],
        organizations: [{ id: 'org-1', name: 'Search Org' }]
      }

      // Mock para posts
      mockSupabase.from.mockImplementation((table) => {
        const mockData = mockResults[table as keyof typeof mockResults] || []
        
        return {
          select: jest.fn().mockReturnValue({
            textSearch: jest.fn().mockReturnValue({
              eq: jest.fn().mockReturnValue({
                limit: jest.fn().mockResolvedValue({
                  data: mockData,
                  error: null
                })
              })
            }),
            or: jest.fn().mockReturnValue({
              limit: jest.fn().mockResolvedValue({
                data: mockData,
                error: null
              })
            })
          })
        } as any
      })

      const result = await SupabaseAPI.searchAll('search query')

      expect(result.posts).toEqual(mockResults.posts)
      expect(result.profiles).toEqual(mockResults.profiles)
      expect(result.organizations).toEqual(mockResults.organizations)
    })
  })
})

// Performance Tests
describe('SupabaseAPI Performance', () => {
  test('deve completar queries em tempo hábil', async () => {
    const startTime = Date.now()
    
    // Mock rápido
    mockSupabase.from.mockReturnValue({
      select: jest.fn().mockReturnValue({
        eq: jest.fn().mockReturnValue({
          single: jest.fn().mockResolvedValue({
            data: { id: 'test' },
            error: null
          })
        })
      })
    } as any)

    await SupabaseAPI.getProfile('test-user')
    
    const duration = Date.now() - startTime
    expect(duration).toBeLessThan(100) // Should complete in < 100ms
  })

  test('deve gerenciar concurrent requests', async () => {
    mockSupabase.from.mockReturnValue({
      select: jest.fn().mockReturnValue({
        eq: jest.fn().mockReturnValue({
          single: jest.fn().mockResolvedValue({
            data: { id: 'test' },
            error: null
          })
        })
      })
    } as any)

    const promises = Array.from({ length: 10 }, (_, i) => 
      SupabaseAPI.getProfile(`user-${i}`)
    )

    const results = await Promise.all(promises)
    expect(results).toHaveLength(10)
    expect(results.every(r => r?.id === 'test')).toBe(true)
  })
})

Jest + Testing Library

Testes unitários e de integração completos

Playwright

Testes end-to-end para fluxos críticos

Supabase Test Helpers

Utilities para testes com Supabase local

MSW (Mock Service Worker)

Mock de APIs para testes isolados

Segurança Avançada e Compliance

Implementação de práticas rigorosas de segurança, auditoria e conformidade com regulamentações como LGPD e GDPR.

lib/security.ts
// lib/security.ts
import { supabase } from './supabase'
import crypto from 'crypto'

// Configurações de segurança
export const SECURITY_CONFIG = {
  passwordMinLength: 12,
  passwordComplexityRegex: /^(?=.*[a-z])(?=.*[A-Z])(?=.*d)(?=.*[@$!%*?&])[A-Za-zd@$!%*?&]/,
  maxLoginAttempts: 5,
  lockoutDuration: 15 * 60 * 1000, // 15 minutos
  sessionTimeout: 24 * 60 * 60 * 1000, // 24 horas
  otpExpiration: 5 * 60 * 1000, // 5 minutos
}

// Classe para gerenciamento de segurança
export class SecurityManager {
  
  // Validação robusta de senha
  static validatePassword(password: string): {
    isValid: boolean
    errors: string[]
  } {
    const errors: string[] = []

    if (password.length < SECURITY_CONFIG.passwordMinLength) {
      errors.push(`Senha deve ter pelo menos ${SECURITY_CONFIG.passwordMinLength} caracteres`)
    }

    if (!SECURITY_CONFIG.passwordComplexityRegex.test(password)) {
      errors.push('Senha deve conter: maiúscula, minúscula, número e símbolo')
    }

    // Verificar senhas comuns
    const commonPasswords = [
      'password123', '123456789', 'qwerty123', 'admin123',
      'password1', 'welcome123', 'letmein123'
    ]
    
    if (commonPasswords.some(common => 
      password.toLowerCase().includes(common.toLowerCase())
    )) {
      errors.push('Senha muito comum, escolha uma mais segura')
    }

    return {
      isValid: errors.length === 0,
      errors
    }
  }

  // Rate limiting para tentativas de login
  static async checkRateLimit(
    identifier: string, 
    action: string = 'login'
  ): Promise<{
    allowed: boolean
    attempts: number
    resetTime?: Date
  }> {
    const key = `rate_limit:${action}:${identifier}`
    
    try {
      // Buscar tentativas recentes
      const { data: attempts, error } = await supabase
        .from('rate_limits')
        .select('*')
        .eq('identifier', identifier)
        .eq('action', action)
        .gte('created_at', new Date(Date.now() - SECURITY_CONFIG.lockoutDuration).toISOString())
        .order('created_at', { ascending: false })

      if (error) throw error

      const recentAttempts = attempts?.length || 0

      if (recentAttempts >= SECURITY_CONFIG.maxLoginAttempts) {
        const oldestAttempt = attempts?.[attempts.length - 1]
        const resetTime = new Date(
          new Date(oldestAttempt.created_at).getTime() + SECURITY_CONFIG.lockoutDuration
        )

        return {
          allowed: false,
          attempts: recentAttempts,
          resetTime
        }
      }

      return {
        allowed: true,
        attempts: recentAttempts
      }

    } catch (error) {
      console.error('Erro ao verificar rate limit:', error)
      // Em caso de erro, permitir (fail-open) mas logar
      await this.logSecurityEvent('rate_limit_error', identifier, { error })
      return { allowed: true, attempts: 0 }
    }
  }

  // Registrar tentativa para rate limiting
  static async recordAttempt(
    identifier: string,
    action: string,
    success: boolean,
    metadata: any = {}
  ): Promise<void> {
    try {
      await supabase
        .from('rate_limits')
        .insert({
          identifier,
          action,
          success,
          metadata,
          ip_address: metadata.ip,
          user_agent: metadata.userAgent
        })

      // Log de segurança
      await this.logSecurityEvent(
        success ? 'auth_success' : 'auth_failure',
        identifier,
        { action, ...metadata }
      )

    } catch (error) {
      console.error('Erro ao registrar tentativa:', error)
    }
  }

  // Two-Factor Authentication
  static async generateTOTP(userId: string): Promise<{
    secret: string
    qrCode: string
    backupCodes: string[]
  }> {
    const secret = crypto.randomBytes(20).toString('hex')
    
    // Gerar códigos de backup
    const backupCodes = Array.from({ length: 8 }, () => 
      crypto.randomBytes(4).toString('hex').toUpperCase()
    )

    // Salvar configuração 2FA
    const { error } = await supabase
      .from('user_2fa')
      .upsert({
        user_id: userId,
        secret,
        backup_codes: backupCodes,
        enabled: false // Usuário deve confirmar primeiro
      })

    if (error) throw error

    // Gerar QR Code (usando biblioteca externa)
    const qrCode = `otpauth://totp/SeuApp:${userId}?secret=${secret}&issuer=SeuApp`

    await this.logSecurityEvent('2fa_setup_initiated', userId)

    return { secret, qrCode, backupCodes }
  }

  static async verifyTOTP(
    userId: string, 
    token: string, 
    isBackupCode: boolean = false
  ): Promise<boolean> {
    try {
      const { data: config } = await supabase
        .from('user_2fa')
        .select('*')
        .eq('user_id', userId)
        .single()

      if (!config) return false

      if (isBackupCode) {
        // Verificar código de backup
        if (config.backup_codes.includes(token.toUpperCase())) {
          // Remover código usado
          const updatedCodes = config.backup_codes.filter(
            code => code !== token.toUpperCase()
          )
          
          await supabase
            .from('user_2fa')
            .update({ backup_codes: updatedCodes })
            .eq('user_id', userId)

          await this.logSecurityEvent('2fa_backup_used', userId, { codesRemaining: updatedCodes.length })
          return true
        }
        return false
      }

      // Verificar TOTP (implementar verificação real)
      const isValid = this.verifyTOTPToken(config.secret, token)
      
      if (isValid) {
        await this.logSecurityEvent('2fa_success', userId)
      } else {
        await this.logSecurityEvent('2fa_failure', userId, { token: token.substring(0, 2) + '****' })
      }

      return isValid

    } catch (error) {
      await this.logSecurityEvent('2fa_error', userId, { error: error.message })
      return false
    }
  }

  // Verificação de token TOTP (simplificada)
  private static verifyTOTPToken(secret: string, token: string): boolean {
    // Implementar verificação real do TOTP
    // Esta é uma versão simplificada
    const timeStep = Math.floor(Date.now() / 30000)
    const expectedToken = this.generateTOTPToken(secret, timeStep)
    
    return token === expectedToken
  }

  private static generateTOTPToken(secret: string, timeStep: number): string {
    // Implementação simplificada - use biblioteca como 'otplib' em produção
    const hash = crypto
      .createHmac('sha1', secret)
      .update(timeStep.toString())
      .digest('hex')
    
    return hash.substring(0, 6)
  }

  // Sanitização de dados
  static sanitizeInput(input: string, type: 'html' | 'sql' | 'xss' = 'xss'): string {
    if (!input) return ''

    switch (type) {
      case 'html':
        return input
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
          .replace(/"/g, '&quot;')
          .replace(/'/g, '&#x27;')
          .replace(///g, '&#x2F;')

      case 'sql':
        return input
          .replace(/'/g, "''")
          .replace(/;/g, '')
          .replace(/--/g, '')
          .replace(//*/g, '')
          .replace(/*//g, '')

      case 'xss':
      default:
        return input
          .replace(/<script[^<]*(?:(?!</script>)<[^<]*)*</script>/gi, '')
          .replace(/javascript:/gi, '')
          .replace(/onw+s*=/gi, '')
          .trim()
    }
  }

  // Validação de CSRF token
  static async validateCSRFToken(token: string, sessionId: string): Promise<boolean> {
    try {
      const { data } = await supabase
        .from('csrf_tokens')
        .select('*')
        .eq('token', token)
        .eq('session_id', sessionId)
        .gte('expires_at', new Date().toISOString())
        .single()

      if (data) {
        // Remover token após uso (one-time use)
        await supabase
          .from('csrf_tokens')
          .delete()
          .eq('token', token)

        return true
      }

      return false

    } catch (error) {
      await this.logSecurityEvent('csrf_validation_error', sessionId, { error })
      return false
    }
  }

  // Log de eventos de segurança
  static async logSecurityEvent(
    event: string,
    userId: string,
    metadata: any = {}
  ): Promise<void> {
    try {
      await supabase
        .from('security_logs')
        .insert({
          user_id: userId,
          event,
          metadata,
          ip_address: metadata.ip,
          user_agent: metadata.userAgent,
          timestamp: new Date().toISOString()
        })

      // Alertas para eventos críticos
      const criticalEvents = [
        'multiple_failed_logins',
        'suspicious_activity',
        '2fa_disabled',
        'admin_access',
        'data_export'
      ]

      if (criticalEvents.includes(event)) {
        await this.triggerSecurityAlert(event, userId, metadata)
      }

    } catch (error) {
      console.error('Erro ao registrar evento de segurança:', error)
    }
  }

  // Sistema de alertas de segurança
  private static async triggerSecurityAlert(
    event: string,
    userId: string,
    metadata: any
  ): Promise<void> {
    try {
      // Enviar alerta para administradores
      await supabase.functions.invoke('security-alert', {
        body: {
          event,
          userId,
          metadata,
          timestamp: new Date().toISOString()
        }
      })

    } catch (error) {
      console.error('Erro ao enviar alerta de segurança:', error)
    }
  }

  // Compliance com LGPD/GDPR
  static async exportUserData(userId: string): Promise<any> {
    try {
      const [profile, posts, logs] = await Promise.all([
        supabase.from('profiles').select('*').eq('id', userId),
        supabase.from('posts').select('*').eq('author_id', userId),
        supabase.from('audit_logs').select('*').eq('user_id', userId)
      ])

      const userData = {
        profile: profile.data?.[0],
        posts: posts.data || [],
        activityLogs: logs.data || [],
        exportedAt: new Date().toISOString(),
        format: 'JSON'
      }

      await this.logSecurityEvent('data_export', userId, { 
        recordsCount: {
          profile: 1,
          posts: posts.data?.length || 0,
          logs: logs.data?.length || 0
        }
      })

      return userData

    } catch (error) {
      await this.logSecurityEvent('data_export_error', userId, { error })
      throw error
    }
  }

  static async deleteUserData(userId: string, reason: string): Promise<void> {
    try {
      // Soft delete primeiro (manter por período legal)
      await supabase
        .from('profiles')
        .update({ 
          deleted_at: new Date().toISOString(),
          deletion_reason: reason,
          status: 'deleted'
        })
        .eq('id', userId)

      // Anonimizar posts ao invés de deletar
      await supabase
        .from('posts')
        .update({
          author_id: null,
          content: '[CONTEÚDO REMOVIDO - USUÁRIO DELETADO]',
          title: '[TÍTULO REMOVIDO - USUÁRIO DELETADO]'
        })
        .eq('author_id', userId)

      await this.logSecurityEvent('user_data_deletion', userId, { reason })

    } catch (error) {
      await this.logSecurityEvent('data_deletion_error', userId, { error, reason })
      throw error
    }
  }
}

// Middleware de segurança para APIs
export function withSecurity(handler: any) {
  return async (req: any, res: any) => {
    try {
      // Rate limiting
      const clientIP = req.ip || req.connection.remoteAddress
      const rateCheck = await SecurityManager.checkRateLimit(clientIP, 'api')
      
      if (!rateCheck.allowed) {
        return res.status(429).json({
          error: 'Too many requests',
          resetTime: rateCheck.resetTime
        })
      }

      // CSRF protection
      if (['POST', 'PUT', 'DELETE'].includes(req.method)) {
        const csrfToken = req.headers['x-csrf-token']
        const sessionId = req.headers['x-session-id']
        
        if (!csrfToken || !sessionId) {
          return res.status(403).json({ error: 'CSRF token required' })
        }

        const validCSRF = await SecurityManager.validateCSRFToken(csrfToken, sessionId)
        if (!validCSRF) {
          return res.status(403).json({ error: 'Invalid CSRF token' })
        }
      }

      // Sanitizar inputs
      if (req.body) {
        req.body = sanitizeObject(req.body)
      }

      return handler(req, res)

    } catch (error) {
      console.error('Security middleware error:', error)
      return res.status(500).json({ error: 'Security check failed' })
    }
  }
}

function sanitizeObject(obj: any): any {
  if (typeof obj !== 'object' || obj === null) {
    return typeof obj === 'string' ? SecurityManager.sanitizeInput(obj) : obj
  }

  const sanitized: any = Array.isArray(obj) ? [] : {}
  
  for (const key in obj) {
    sanitized[key] = sanitizeObject(obj[key])
  }

  return sanitized
}

⚠️Compliance LGPD/GDPR

Implemente funcionalidades de portabilidade de dados, direito ao esquecimento e consentimento explícito. Mantenha logs de auditoria e processos de resposta a incidentes.

Checklist de Implementação Supabase

Projeto Supabase criado e configurado
Database schema com RLS implementado
Authentication com providers configurada
Real-time subscriptions funcionando
Storage com upload de arquivos
Edge Functions para lógica customizada
Performance otimizada com cache
Middleware de proteção de rotas
Health checks e monitoring
Backup automático configurado
Deploy em produção testado
Documentação da API atualizada
Migrations e versionamento configurado
API REST customizada implementada
Testes unitários e integração criados
Segurança avançada e compliance ativo

Acelere seu desenvolvimento backend

Domine as ferramentas modernas de backend com nossos cursos práticos