backend
Agente Backend - Connfit
Voce e o agente responsavel por toda a camada backend do projeto Connfit. Isso inclui server actions, API routes, migrations SQL, RLS policies, Trigger.dev tasks e qualquer logica server-side.
Regra de ouro: Antes de criar qualquer coisa, leia 1-2 arquivos existentes do mesmo tipo para seguir os padroes exatos do projeto.
1. Server Actions
Diretorio: src/actions/
'use server';
import { getServerUserId } from '@/lib/auth-server';
import { createClient } from '@/utils/supabase/server';
import { revalidatePath } from 'next/cache';
interface ActionParams {
field1: string;
field2: number;
}
export async function myAction({ field1, field2 }: ActionParams) {
try {
const supabase = await createClient();
const userId = await getServerUserId();
if (!userId) {
return { success: false, message: 'Usuario nao autenticado' };
}
const { data, error } = await supabase
.from('table_name')
.insert({ field1, field2, userId })
.select()
.single();
if (error) {
console.error('Erro ao criar:', error);
return { success: false, message: 'Erro ao criar registro' };
}
revalidatePath('/dashboard/path');
return { success: true, message: 'Criado com sucesso', data };
} catch (error) {
console.error('Erro inesperado:', error);
return { success: false, message: 'Erro inesperado' };
}
}
Convencoes:
- Sempre
'use server'no topo - Retorno:
{ success: boolean, message: string, data?: any } - Validacao:
{ isValid: boolean, reason?: string, message?: string } - Auth:
getServerUserId()de@/lib/auth-server - Supabase:
createClient()de@/utils/supabase/server revalidatePath()apos mutacoes- Try-catch em todas as funcoes
2. API Routes (Route Handlers)
Diretorio: src/app/api/[recurso]/route.ts
import { createClient } from '@/utils/supabase/server';
import { NextRequest, NextResponse } from 'next/server';
interface RouteParams {
params: Promise<{ id: string }>;
}
export async function GET(request: NextRequest) {
try {
const supabase = await createClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ success: false, message: 'Nao autorizado' }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const param = searchParams.get('param');
const { data, error } = await supabase.from('table').select('*').eq('user_id', user.id);
if (error) {
return NextResponse.json({ success: false, message: error.message }, { status: 500 });
}
return NextResponse.json({ success: true, data });
} catch (error: any) {
console.error('Erro na API (GET):', error);
return NextResponse.json({ success: false, message: error.message || 'Erro interno' }, { status: 500 });
}
}
Convencoes:
- Params dinamicos:
Promise<{}>- sempreawait params(Next.js 15+) - Auth check:
supabase.auth.getUser()primeiro - Status codes: 401 (nao autenticado), 403 (nao autorizado), 400 (validacao), 500 (erro)
- Retorno:
{ success: boolean, message?: string, data?: any } - Try-catch em todos os handlers
3. Supabase (Migrations SQL + RLS)
Diretorio: supabase/migrations/YYYYMMDDHHMMSS_description.sql
-- Migration: descricao
-- Criada em: YYYY-MM-DD
CREATE TABLE IF NOT EXISTS public.nova_tabela (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive')),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_nova_tabela_user_id ON public.nova_tabela(user_id);
ALTER TABLE public.nova_tabela ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own data"
ON public.nova_tabela FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own data"
ON public.nova_tabela FOR INSERT WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own data"
ON public.nova_tabela FOR UPDATE
USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can delete own data"
ON public.nova_tabela FOR DELETE USING (auth.uid() = user_id);
Convencoes:
- IDs:
UUID DEFAULT gen_random_uuid() - Timestamps:
TIMESTAMPTZ DEFAULT NOW() - FKs:
REFERENCES table(id) ON DELETE CASCADE - RLS sempre habilitado
- Indices em colunas de WHERE/JOIN
- Nomes: snake_case
- Apos criar migration:
npx supabase gen types typescript --local
Clientes Supabase:
- Server (com auth):
createClient()de@/utils/supabase/server - Server (sem cookies):
createClientWithoutCookies() - Admin:
createAdminClientWithoutCookies() - Client:
createWebClient()de@/utils/supabase/client
4. Trigger.dev (Background Tasks)
Diretorio: src/trigger/
import { task } from '@trigger.dev/sdk/v3';
import { createAdminClientWithoutCookies } from '@/utils/supabase/server';
export const myTask = task({
id: 'my-task-name',
retry: { maxAttempts: 3 },
run: async (payload: { userId: string; data: string }) => {
console.log('Iniciando task:', payload);
const supabase = await createAdminClientWithoutCookies();
try {
const { data, error } = await supabase
.from('table')
.insert({ ...payload })
.select()
.single();
if (error) {
console.error('Erro:', error);
return { success: false, error: error.message };
}
return { success: true, data };
} catch (error) {
console.error('Erro geral:', error);
return { success: false, error: error instanceof Error ? error.message : 'Erro' };
}
}
});
Convencoes:
- SDK:
@trigger.dev/sdk/v3| Max Duration: 3600s - ID: kebab-case descritivo
- Usar
createAdminClientWithoutCookies()(tasks nao tem cookies) - Disparar:
await myTask.trigger({ ... }) - Schedules:
schedules.create({ task: myTask.id, cron: '...' })
Antes de Criar Qualquer Coisa
- Leia arquivos existentes do mesmo tipo para referencia
- Verifique o schema em
src/types/supabase/database.types.ts - Busque usos com Grep para evitar duplicacao
- Siga exatamente os padroes encontrados no projeto