Merge pull request 'fix-assgnment-message' (#50) from feature/add-exceptions-endpoints into develop

Reviewed-on: #50
This commit is contained in:
JoaoGustavo-dev 2025-10-15 22:26:28 +00:00
commit 7a1885d882
4 changed files with 213 additions and 268 deletions

View File

@ -0,0 +1,42 @@
import { NextRequest, NextResponse } from 'next/server'
import { ENV_CONFIG } from '@/lib/env-config'
export async function POST(req: NextRequest) {
try {
const body = await req.json().catch(() => ({}))
const target = `${ENV_CONFIG.SUPABASE_URL}/functions/v1/create-user`
const headers: Record<string,string> = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
}
const auth = req.headers.get('authorization')
if (auth) headers.Authorization = auth
const r = await fetch(target, { method: 'POST', headers, body: JSON.stringify(body) })
if (r.status === 404 || r.status >= 500) {
// fallback to signup
const email = body.email
let password = body.password
const full_name = body.full_name
const phone = body.phone
const role = body.role || (Array.isArray(body.roles) ? body.roles[0] : undefined)
if (!password) password = `senha${Math.floor(Math.random()*900)+100}!`
const userType = (role && String(role).toLowerCase() === 'paciente') ? 'paciente' : 'profissional'
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`
const signupRes = await fetch(signupUrl, {
method: 'POST',
headers: { 'Content-Type':'application/json', 'Accept':'application/json', 'apikey': ENV_CONFIG.SUPABASE_ANON_KEY },
body: JSON.stringify({ email, password, data: { userType, full_name, phone } })
})
const text = await signupRes.text()
try { return NextResponse.json({ fallback: true, from: 'signup', result: JSON.parse(text) }, { status: signupRes.status }) } catch { return new NextResponse(text, { status: signupRes.status }) }
}
const text = await r.text()
try { return NextResponse.json(JSON.parse(text), { status: r.status }) } catch { return new NextResponse(text, { status: r.status }) }
} catch (err:any) {
console.error('[app/api/create-user] error', err)
return NextResponse.json({ error: 'Bad gateway', details: String(err) }, { status: 502 })
}
}

View File

@ -50,6 +50,15 @@ export default function AssignmentForm({ patientId, open, onClose, onSaved }: Pr
if (open) load(); if (open) load();
}, [open, patientId]); }, [open, patientId]);
// Resolve a display name for a professional id (user_id or id)
function resolveProfessionalName(userId: string | number | null | undefined) {
if (!userId) return String(userId ?? '')
const uid = String(userId)
const found = professionals.find(p => String(p.user_id ?? p.id) === uid)
if (found) return found.full_name || found.name || found.email || String(found.user_id ?? found.id)
return uid
}
async function handleSave() { async function handleSave() {
if (!selectedProfessional) return toast({ title: 'Selecione um profissional', variant: 'default' }); if (!selectedProfessional) return toast({ title: 'Selecione um profissional', variant: 'default' });
setLoading(true); setLoading(true);
@ -96,7 +105,7 @@ export default function AssignmentForm({ patientId, open, onClose, onSaved }: Pr
<Label>Atribuições existentes</Label> <Label>Atribuições existentes</Label>
<ul className="pl-4 list-disc text-sm text-muted-foreground"> <ul className="pl-4 list-disc text-sm text-muted-foreground">
{existing.map((it) => ( {existing.map((it) => (
<li key={it.id}>{it.user_id} {it.role}</li> <li key={it.id}>{resolveProfessionalName(it.user_id)} {it.role}</li>
))} ))}
</ul> </ul>
</div> </div>

View File

@ -1618,6 +1618,19 @@ export function gerarSenhaAleatoria(): string {
} }
export async function criarUsuario(input: CreateUserInput): Promise<CreateUserResponse> { export async function criarUsuario(input: CreateUserInput): Promise<CreateUserResponse> {
// When running in the browser, call our Next.js proxy to avoid CORS/preflight
// issues that some Edge Functions may have. On server-side, call the function
// directly.
if (typeof window !== 'undefined') {
const proxyUrl = '/api/create-user'
const res = await fetch(proxyUrl, {
method: 'POST',
headers: { ...baseHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(input),
})
return await parse<CreateUserResponse>(res as Response)
}
const url = `${API_BASE}/functions/v1/create-user`; const url = `${API_BASE}/functions/v1/create-user`;
const res = await fetch(url, { const res = await fetch(url, {
method: "POST", method: "POST",
@ -1709,298 +1722,94 @@ export async function criarUsuarioDirectAuth(input: {
// ============================================ // ============================================
// Criar usuário para MÉDICO no Supabase Auth (sistema de autenticação) // Criar usuário para MÉDICO no Supabase Auth (sistema de autenticação)
export async function criarUsuarioMedico(medico: { export async function criarUsuarioMedico(medico: { email: string; full_name: string; phone_mobile: string; }): Promise<any> {
email: string; // Prefer server-side creation (new OpenAPI create-user) so roles are assigned
full_name: string; // correctly (and magic link is sent). Fallback to direct Supabase signup if
phone_mobile: string; // the server function is unavailable.
}): Promise<CreateUserWithPasswordResponse> { try {
const res = await criarUsuario({ email: medico.email, password: '', full_name: medico.full_name, phone: medico.phone_mobile, role: 'medico' as any });
return res;
} catch (err) {
console.warn('[CRIAR MÉDICO] Falha no endpoint server-side create-user, tentando fallback direto no Supabase Auth:', err);
// Fallback: create directly in Supabase Auth (old behavior)
}
// --- Fallback to previous direct signup ---
const senha = gerarSenhaAleatoria(); const senha = gerarSenhaAleatoria();
console.log('[CRIAR MÉDICO] Iniciando criação no Supabase Auth...');
console.log('Email:', medico.email);
console.log('Nome:', medico.full_name);
console.log('Telefone:', medico.phone_mobile);
console.log('Senha gerada:', senha);
// Endpoint do Supabase Auth (mesmo que auth.ts usa)
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
const payload = { const payload = {
email: medico.email, email: medico.email,
password: senha, password: senha,
data: { data: {
userType: 'profissional', // Para login em /login -> /profissional userType: 'profissional',
full_name: medico.full_name, full_name: medico.full_name,
phone: medico.phone_mobile, phone: medico.phone_mobile,
} }
}; };
console.log('[CRIAR MÉDICO] Enviando para:', signupUrl); const response = await fetch(signupUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"apikey": ENV_CONFIG.SUPABASE_ANON_KEY,
},
body: JSON.stringify(payload),
});
try { if (!response.ok) {
const response = await fetch(signupUrl, { const errorText = await response.text();
method: "POST", let errorMsg = `Erro ao criar usuário (${response.status})`;
headers: { try { const errorData = JSON.parse(errorText); errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg; } catch {}
"Content-Type": "application/json", throw new Error(errorMsg);
"Accept": "application/json",
"apikey": ENV_CONFIG.SUPABASE_ANON_KEY,
},
body: JSON.stringify(payload),
});
console.log('[CRIAR MÉDICO] Status da resposta:', response.status, response.statusText);
if (!response.ok) {
const errorText = await response.text();
console.error('[CRIAR MÉDICO] Erro na resposta:', errorText);
// Tenta parsear o erro para pegar mensagem específica
let errorMsg = `Erro ao criar usuário (${response.status})`;
try {
const errorData = JSON.parse(errorText);
errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg;
// Mensagens amigáveis para erros comuns
if (errorMsg.includes('already registered') || errorMsg.includes('already exists')) {
errorMsg = 'Este email já está cadastrado no sistema';
} else if (errorMsg.includes('invalid email')) {
errorMsg = 'Formato de email inválido';
} else if (errorMsg.includes('weak password')) {
errorMsg = 'Senha muito fraca';
}
} catch (e) {
// Se não conseguir parsear, usa mensagem genérica
}
throw new Error(errorMsg);
}
const responseData = await response.json();
console.log('[CRIAR MÉDICO] Usuário criado com sucesso no Supabase Auth!');
console.log('User ID:', responseData.user?.id || responseData.id);
// 🔧 AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário
// Isso força o Supabase a confirmar o email automaticamente
if (responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at) {
console.warn('[CRIAR MÉDICO] Email NÃO confirmado - tentando auto-confirmar via login...');
try {
const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`;
console.log('[AUTO-CONFIRMAR] Fazendo login automático para confirmar email...');
const loginResponse = await fetch(loginUrl, {
method: 'POST',
headers: {
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: medico.email,
password: senha,
}),
});
if (loginResponse.ok) {
const loginData = await loginResponse.json();
console.log('[AUTO-CONFIRMAR] Login automático realizado com sucesso!');
console.log('[AUTO-CONFIRMAR] Email confirmado:', loginData.user?.email_confirmed_at ? 'SIM' : 'NÃO');
// Atualizar responseData com dados do login (que tem email confirmado)
if (loginData.user) {
responseData.user = loginData.user;
}
} else {
const errorText = await loginResponse.text();
console.error('[AUTO-CONFIRMAR] Falha no login automático:', loginResponse.status, errorText);
console.warn('[AUTO-CONFIRMAR] Usuário pode não conseguir fazer login imediatamente!');
}
} catch (confirmError) {
console.error('[AUTO-CONFIRMAR] Erro ao tentar fazer login automático:', confirmError);
console.warn('[AUTO-CONFIRMAR] Continuando sem confirmação automática...');
}
} else {
console.log('[CRIAR MÉDICO] Email confirmado automaticamente!');
}
// Log bem visível com as credenciais para teste
console.log('========================================');
console.log('CREDENCIAIS DO MÉDICO CRIADO:');
console.log('Email:', medico.email);
console.log('Senha:', senha);
console.log('Pode fazer login?', responseData.user?.email_confirmed_at ? 'SIM' : 'NÃO (precisa confirmar email)');
console.log('========================================');
return {
success: true,
user: responseData.user || responseData,
email: medico.email,
password: senha,
};
} catch (error: any) {
console.error('[CRIAR MÉDICO] Erro ao criar usuário:', error);
throw error;
} }
const responseData = await response.json();
return { success: true, user: responseData.user || responseData, email: medico.email, password: senha };
} }
// Criar usuário para PACIENTE no Supabase Auth (sistema de autenticação) // Criar usuário para PACIENTE no Supabase Auth (sistema de autenticação)
export async function criarUsuarioPaciente(paciente: { export async function criarUsuarioPaciente(paciente: { email: string; full_name: string; phone_mobile: string; }): Promise<any> {
email: string; // Prefer server-side creation (OpenAPI create-user) to assign role 'paciente'.
full_name: string; try {
phone_mobile: string; const res = await criarUsuario({ email: paciente.email, password: '', full_name: paciente.full_name, phone: paciente.phone_mobile, role: 'paciente' as any });
}): Promise<CreateUserWithPasswordResponse> { return res;
} catch (err) {
console.warn('[CRIAR PACIENTE] Falha no endpoint server-side create-user, tentando fallback direto no Supabase Auth:', err);
}
// Fallback to previous direct signup behavior
const senha = gerarSenhaAleatoria(); const senha = gerarSenhaAleatoria();
console.log('[CRIAR PACIENTE] Iniciando criação no Supabase Auth...');
console.log('Email:', paciente.email);
console.log('Nome:', paciente.full_name);
console.log('Telefone:', paciente.phone_mobile);
console.log('Senha gerada:', senha);
// Endpoint do Supabase Auth (mesmo que auth.ts usa)
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
const payload = { const payload = {
email: paciente.email, email: paciente.email,
password: senha, password: senha,
data: { data: {
userType: 'paciente', // Para login em /login-paciente -> /paciente userType: 'paciente',
full_name: paciente.full_name, full_name: paciente.full_name,
phone: paciente.phone_mobile, phone: paciente.phone_mobile,
} }
}; };
console.log('[CRIAR PACIENTE] Enviando para:', signupUrl); const response = await fetch(signupUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
"apikey": ENV_CONFIG.SUPABASE_ANON_KEY,
},
body: JSON.stringify(payload),
});
try { if (!response.ok) {
const response = await fetch(signupUrl, { const errorText = await response.text();
method: "POST", let errorMsg = `Erro ao criar usuário (${response.status})`;
headers: { try { const errorData = JSON.parse(errorText); errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg; } catch {}
"Content-Type": "application/json", throw new Error(errorMsg);
"Accept": "application/json",
"apikey": ENV_CONFIG.SUPABASE_ANON_KEY,
},
body: JSON.stringify(payload),
});
console.log('[CRIAR PACIENTE] Status da resposta:', response.status, response.statusText);
if (!response.ok) {
const errorText = await response.text();
console.error('[CRIAR PACIENTE] Erro na resposta:', errorText);
// Tenta parsear o erro para pegar mensagem específica
let errorMsg = `Erro ao criar usuário (${response.status})`;
try {
const errorData = JSON.parse(errorText);
errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg;
// Mensagens amigáveis para erros comuns
if (errorMsg.includes('already registered') || errorMsg.includes('already exists')) {
errorMsg = 'Este email já está cadastrado no sistema';
} else if (errorMsg.includes('invalid email')) {
errorMsg = 'Formato de email inválido';
} else if (errorMsg.includes('weak password')) {
errorMsg = 'Senha muito fraca';
}
} catch (e) {
// Se não conseguir parsear, usa mensagem genérica
}
throw new Error(errorMsg);
}
const responseData = await response.json();
console.log('[CRIAR PACIENTE] Usuário criado com sucesso no Supabase Auth!');
console.log('User ID:', responseData.user?.id || responseData.id);
console.log('[CRIAR PACIENTE] Resposta completa do Supabase:', JSON.stringify(responseData, null, 2));
// VERIFICAÇÃO CRÍTICA: O usuário foi realmente criado?
if (!responseData.user && !responseData.id) {
console.error('AVISO: Supabase retornou sucesso mas sem user ID!');
console.error('Isso pode significar que o usuário não foi criado de verdade!');
}
const userId = responseData.user?.id || responseData.id;
// 🔧 AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário
// Isso força o Supabase a confirmar o email automaticamente
if (responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at) {
console.warn('[CRIAR PACIENTE] Email NÃO confirmado - tentando auto-confirmar via login...');
try {
const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`;
console.log('[AUTO-CONFIRMAR] Fazendo login automático para confirmar email...');
const loginResponse = await fetch(loginUrl, {
method: 'POST',
headers: {
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: paciente.email,
password: senha,
}),
});
console.log('[AUTO-CONFIRMAR] Status do login automático:', loginResponse.status);
if (loginResponse.ok) {
const loginData = await loginResponse.json();
console.log('[AUTO-CONFIRMAR] Login automático realizado com sucesso!');
console.log('[AUTO-CONFIRMAR] Dados completos do login:', JSON.stringify(loginData, undefined, 2));
console.log('[AUTO-CONFIRMAR] Email confirmado:', loginData.user?.email_confirmed_at ? 'SIM' : 'NÃO');
console.log('[AUTO-CONFIRMAR] UserType no metadata:', loginData.user?.user_metadata?.userType);
console.log('[AUTO-CONFIRMAR] Email verified:', loginData.user?.user_metadata?.email_verified);
// Atualizar responseData com dados do login (que tem email confirmado)
if (loginData.user) {
responseData.user = loginData.user;
}
} else {
const errorText = await loginResponse.text();
console.error('[AUTO-CONFIRMAR] Falha no login automático:', loginResponse.status, errorText);
console.warn('[AUTO-CONFIRMAR] Usuário pode não conseguir fazer login imediatamente!');
// Tentar parsear o erro para entender melhor
try {
const errorData = JSON.parse(errorText);
console.error('[AUTO-CONFIRMAR] Detalhes do erro:', errorData);
} catch (e) {
console.error('[AUTO-CONFIRMAR] Erro não é JSON:', errorText);
}
}
} catch (confirmError) {
console.error('[AUTO-CONFIRMAR] Erro ao tentar fazer login automático:', confirmError);
console.warn('[AUTO-CONFIRMAR] Continuando sem confirmação automática...');
}
} else {
console.log('[CRIAR PACIENTE] Email confirmado automaticamente!');
}
// Log bem visível com as credenciais para teste
console.log('========================================');
console.log('CREDENCIAIS DO PACIENTE CRIADO:');
console.log('Email:', paciente.email);
console.log('Senha:', senha);
console.log('UserType:', 'paciente');
console.log('Pode fazer login?', responseData.user?.email_confirmed_at ? 'SIM' : 'NÃO (precisa confirmar email)');
console.log('========================================');
return {
success: true,
user: responseData.user || responseData,
email: paciente.email,
password: senha,
};
} catch (error: any) {
console.error('[CRIAR PACIENTE] Erro ao criar usuário:', error);
throw error;
} }
const responseData = await response.json();
return { success: true, user: responseData.user || responseData, email: paciente.email, password: senha };
} }
// ===== CEP (usado nos formulários) ===== // ===== CEP (usado nos formulários) =====

View File

@ -0,0 +1,85 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import { ENV_CONFIG } from '@/lib/env-config'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })
try {
const target = `${ENV_CONFIG.SUPABASE_URL}/functions/v1/create-user`
const headers: Record<string,string> = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
}
// forward authorization header if present (keeps the caller's identity)
if (req.headers.authorization) headers.Authorization = String(req.headers.authorization)
const r = await fetch(target, {
method: 'POST',
headers,
body: JSON.stringify(req.body),
})
// If the function is not available (404) or returns server error (5xx),
// perform a fallback: create the user via Supabase Auth signup so the
// client doesn't experience a hard 404.
if (r.status === 404 || r.status >= 500) {
console.warn('[proxy/create-user] function returned', r.status, 'falling back to signup')
// attempt signup
try {
const body = req.body || {}
const email = body.email
let password = body.password
const full_name = body.full_name
const phone = body.phone
const role = body.role || (Array.isArray(body.roles) ? body.roles[0] : undefined)
// generate a password if none provided
if (!password) {
const rand = Math.floor(Math.random() * 900) + 100
password = `senha${rand}!`
}
const userType = (role && String(role).toLowerCase() === 'paciente') ? 'paciente' : 'profissional'
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`
const signupRes = await fetch(signupUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'apikey': ENV_CONFIG.SUPABASE_ANON_KEY,
},
body: JSON.stringify({
email,
password,
data: { userType, full_name, phone }
}),
})
const signupText = await signupRes.text()
try {
const signupJson = JSON.parse(signupText)
return res.status(signupRes.status).json({ fallback: true, from: 'signup', result: signupJson })
} catch {
return res.status(signupRes.status).send(signupText)
}
} catch (err2: any) {
console.error('[proxy/create-user] fallback signup failed', err2)
return res.status(502).json({ error: 'Bad gateway (fallback failed)', details: String(err2) })
}
}
const text = await r.text()
try {
const json = JSON.parse(text)
return res.status(r.status).json(json)
} catch {
// not JSON, return raw text
res.status(r.status).send(text)
}
} catch (err: any) {
console.error('[proxy/create-user] error', err)
return res.status(502).json({ error: 'Bad gateway', details: String(err) })
}
}