feat(auth): Creates user in patient registration

- Adds automatic user creation in the new patient registration flow.              - To avoid merge conflicts, the user and profile APIs have been separated from the main lib/api.ts file.
This commit is contained in:
M-Gabrielly 2025-09-30 12:01:37 -03:00
parent ba89bc17f2
commit b577a418e1
6 changed files with 164 additions and 149 deletions

View File

@ -26,8 +26,9 @@ import {
listarAnexos, listarAnexos,
removerAnexo, removerAnexo,
buscarPacientePorId, buscarPacientePorId,
listarPerfis,
} from "@/lib/api"; } from "@/lib/api";
import { listarPerfis } from "@/lib/api/perfis";
import { criarUsuario } from "@/lib/api/usuarios";
type Mode = "create" | "edit"; type Mode = "create" | "edit";
@ -232,22 +233,20 @@ export function PatientRegistrationForm({
if (mode === "create") { if (mode === "create") {
saved = await criarPaciente(payload); saved = await criarPaciente(payload);
console.log("--- INÍCIO DO TESTE DE API ---"); if (saved.email && saved.nome) {
console.log("Paciente recém-criado:", saved);
try { try {
console.log("Buscando lista de perfis para verificar a criação..."); await criarUsuario({
const perfis = await listarPerfis(); email: saved.email,
console.log("Lista de Perfis encontrada:", perfis); full_name: saved.nome,
const perfilCorrespondente = perfis.find(p => p.email === saved.email); phone: saved.telefone || undefined,
if (perfilCorrespondente) { role: 'paciente',
console.log("SUCESSO: Perfil correspondente foi encontrado!", perfilCorrespondente); });
} else { } catch (userError) {
console.log("FALHA: Nenhum perfil correspondente ao email do paciente foi encontrado na lista de perfis."); console.error("Falha ao criar usuário para o paciente:", userError);
// TODO: O que fazer se a criação do usuário falhar?
// Por enquanto, apenas logamos o erro.
} }
} catch (error) {
console.error("ERRO AO BUSCAR PERFIS:", error);
} }
console.log("--- FIM DO TESTE DE API ---");
} else { } else {
if (patientId == null) throw new Error("Paciente inexistente para edição"); if (patientId == null) throw new Error("Paciente inexistente para edição");

View File

@ -60,7 +60,7 @@ export type PacienteInput = {
const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "https://mock.apidog.com/m1/1053378-0-default"; export const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "https://mock.apidog.com/m1/1053378-0-default";
const MEDICOS_BASE = process.env.NEXT_PUBLIC_MEDICOS_BASE_PATH ?? "/medicos"; const MEDICOS_BASE = process.env.NEXT_PUBLIC_MEDICOS_BASE_PATH ?? "/medicos";
export const PATHS = { export const PATHS = {
@ -88,7 +88,7 @@ function getAuthToken(): string | null {
return localStorage.getItem('auth_token'); return localStorage.getItem('auth_token');
} }
function headers(kind: "json" | "form" = "json"): Record<string, string> { export function headers(kind: "json" | "form" = "json"): Record<string, string> {
const h: Record<string, string> = {}; const h: Record<string, string> = {};
// API Key da Supabase sempre necessária // API Key da Supabase sempre necessária
@ -104,7 +104,7 @@ function headers(kind: "json" | "form" = "json"): Record<string, string> {
return h; return h;
} }
function logAPI(title: string, info: { url?: string; payload?: any; result?: any } = {}) { export function logAPI(title: string, info: { url?: string; payload?: any; result?: any } = {}) {
try { try {
console.group(`[API] ${title}`); console.group(`[API] ${title}`);
if (info.url) console.log("url:", info.url); if (info.url) console.log("url:", info.url);
@ -114,7 +114,7 @@ function logAPI(title: string, info: { url?: string; payload?: any; result?: any
} catch {} } catch {}
} }
async function parse<T>(res: Response): Promise<T> { export async function parse<T>(res: Response): Promise<T> {
let json: any = null; let json: any = null;
try { try {
json = await res.json(); json = await res.json();
@ -344,135 +344,6 @@ export type MedicoInput = {
valor_consulta?: number | string | null; valor_consulta?: number | string | null;
}; };
//
// Perfis de Usuário (Profiles)
//
export type UserProfile = {
id: string;
full_name?: string;
email?: string;
phone?: string;
avatar_url?: string;
disabled?: boolean;
created_at?: string;
updated_at?: string;
};
export type UserProfileInput = {
full_name?: string;
email?: string;
phone?: string;
avatar_url?: string;
disabled?: boolean;
};
export async function listarPerfis(params?: { page?: number; limit?: number; q?: string }): Promise<UserProfile[]> {
const query = new URLSearchParams();
if (params?.page) query.set("page", String(params.page));
if (params?.limit) query.set("limit", String(params.limit));
if (params?.q) query.set("q", params.q);
const url = `${API_BASE}/rest/v1/profiles${query.toString() ? `?${query.toString()}` : ""}`;
const res = await fetch(url, { method: "GET", headers: headers("json") });
const data = await parse<ApiOk<UserProfile[]>>(res);
logAPI("listarPerfis", { url, result: data });
return data?.data ?? (data as any);
}
export async function buscarPerfilPorId(id: string | number): Promise<UserProfile> {
const url = `${API_BASE}/rest/v1/profiles?id=eq.${id}`;
const res = await fetch(url, { method: "GET", headers: headers("json") });
// A API da Supabase/PostgREST retorna um array mesmo pedindo um ID, então pegamos o primeiro.
const data = await parse<UserProfile[]>(res);
const profile = data[0];
logAPI("buscarPerfilPorId", { url, result: profile });
return profile;
}
export async function atualizarPerfil(id: string | number, input: UserProfileInput): Promise<UserProfile> {
const url = `${API_BASE}/rest/v1/profiles?id=eq.${id}`;
const res = await fetch(url, { method: "PATCH", headers: headers("json"), body: JSON.stringify(input) });
// O método PATCH no PostgREST retorna um array vazio por padrão. Para retornar os dados, precisa de um header `Prefer: return=representation`
// Por simplicidade, vamos assumir que se não deu erro, a operação foi um sucesso.
// Se a API estiver configurada para retornar o objeto, o parse vai funcionar.
const data = await parse<ApiOk<UserProfile>>(res);
logAPI("atualizarPerfil", { url, payload: input, result: data });
return data?.data ?? (data as any);
}
//
// User Management APIs
//
export type UserRole = {
id: string;
user_id: string;
role: 'admin' | 'medico' | 'paciente';
created_at: string;
};
export type CreateUserInput = {
email: string;
password?: string;
full_name: string;
phone?: string;
role: 'admin' | 'medico' | 'paciente';
};
export type CreatedUser = {
id: string;
email: string;
full_name: string;
phone?: string;
role: string;
};
export type CompleteUserInfo = {
user: {
id: string;
email: string;
email_confirmed_at: string;
created_at: string;
last_sign_in_at: string;
};
profile: UserProfile;
roles: string[];
permissions: {
isAdmin: boolean;
isManager: boolean;
isDoctor: boolean;
isSecretary: boolean;
isAdminOrManager: boolean;
};
};
export async function criarUsuario(input: CreateUserInput): Promise<{ success: boolean; user: CreatedUser }> {
const url = `${API_BASE}/functions/v1/create-user`;
const res = await fetch(url, { method: "POST", headers: headers("json"), body: JSON.stringify(input) });
const data = await parse<any>(res);
logAPI("criarUsuario", { url, payload: input, result: data });
return data;
}
export async function listarUserRoles(): Promise<UserRole[]> {
const url = `${API_BASE}/rest/v1/user_roles`;
const res = await fetch(url, { method: "GET", headers: headers("json") });
const data = await parse<UserRole[]>(res);
logAPI("listarUserRoles", { url, result: data });
return data ?? [];
}
export async function getCompleteUserInfo(userId: string): Promise<CompleteUserInfo> {
const url = `${API_BASE}/functions/v1/user-info`;
// Assuming the function takes the user ID in the body of a POST request
const res = await fetch(url, { method: "POST", headers: headers("json"), body: JSON.stringify({ id: userId }) });
const data = await parse<CompleteUserInfo>(res);
logAPI("getCompleteUserInfo", { url, payload: { id: userId }, result: data });
return data;
}
// //
// MÉDICOS (CRUD) // MÉDICOS (CRUD)

View File

@ -0,0 +1 @@
// Arquivo reservado para as APIs de Médicos

View File

@ -0,0 +1 @@
// Arquivo reservado para as APIs de Pacientes

View File

@ -0,0 +1,64 @@
import {
API_BASE,
headers,
logAPI,
parse,
} from "../api";
import type { ApiOk } from "../api";
//
// Perfis de Usuário (Profiles)
//
export type UserProfile = {
id: string;
full_name?: string;
email?: string;
phone?: string;
avatar_url?: string;
disabled?: boolean;
created_at?: string;
updated_at?: string;
};
export type UserProfileInput = {
full_name?: string;
email?: string;
phone?: string;
avatar_url?: string;
disabled?: boolean;
};
export async function listarPerfis(params?: { page?: number; limit?: number; q?: string }): Promise<UserProfile[]> {
const query = new URLSearchParams();
if (params?.page) query.set("page", String(params.page));
if (params?.limit) query.set("limit", String(params.limit));
if (params?.q) query.set("q", params.q);
const url = `${API_BASE}/rest/v1/profiles${query.toString() ? `?${query.toString()}` : ""}`;
const res = await fetch(url, { method: "GET", headers: headers("json") });
const data = await parse<ApiOk<UserProfile[]>>(res);
logAPI("listarPerfis", { url, result: data });
return data?.data ?? (data as any);
}
export async function buscarPerfilPorId(id: string | number): Promise<UserProfile> {
const url = `${API_BASE}/rest/v1/profiles?id=eq.${id}`;
const res = await fetch(url, { method: "GET", headers: headers("json") });
// A API da Supabase/PostgREST retorna um array mesmo pedindo um ID, então pegamos o primeiro.
const data = await parse<UserProfile[]>(res);
const profile = data[0];
logAPI("buscarPerfilPorId", { url, result: profile });
return profile;
}
export async function atualizarPerfil(id: string | number, input: UserProfileInput): Promise<UserProfile> {
const url = `${API_BASE}/rest/v1/profiles?id=eq.${id}`;
const res = await fetch(url, { method: "PATCH", headers: headers("json"), body: JSON.stringify(input) });
// O método PATCH no PostgREST retorna um array vazio por padrão. Para retornar os dados, precisa de um header `Prefer: return=representation`
// Por simplicidade, vamos assumir que se não deu erro, a operação foi um sucesso.
// Se a API estiver configurada para retornar o objeto, o parse vai funcionar.
const data = await parse<ApiOk<UserProfile>>(res);
logAPI("atualizarPerfil", { url, payload: input, result: data });
return data?.data ?? (data as any);
}

View File

@ -0,0 +1,79 @@
import {
API_BASE,
headers,
logAPI,
parse,
} from "../api";
import type { ApiOk } from "../api";
import type { UserProfile } from "./perfis";
//
// User Management APIs
//
export type UserRole = {
id: string;
user_id: string;
role: 'admin' | 'medico' | 'paciente';
created_at: string;
};
export type CreateUserInput = {
email: string;
password?: string;
full_name: string;
phone?: string;
role: 'admin' | 'medico' | 'paciente';
};
export type CreatedUser = {
id: string;
email: string;
full_name: string;
phone?: string;
role: string;
};
export type CompleteUserInfo = {
user: {
id: string;
email: string;
email_confirmed_at: string;
created_at: string;
last_sign_in_at: string;
};
profile: UserProfile;
roles: string[];
permissions: {
isAdmin: boolean;
isManager: boolean;
isDoctor: boolean;
isSecretary: boolean;
isAdminOrManager: boolean;
};
};
export async function criarUsuario(input: CreateUserInput): Promise<{ success: boolean; user: CreatedUser }> {
const url = `${API_BASE}/functions/v1/create-user`;
const res = await fetch(url, { method: "POST", headers: headers("json"), body: JSON.stringify(input) });
const data = await parse<any>(res);
logAPI("criarUsuario", { url, payload: input, result: data });
return data;
}
export async function listarUserRoles(): Promise<UserRole[]> {
const url = `${API_BASE}/rest/v1/user_roles`;
const res = await fetch(url, { method: "GET", headers: headers("json") });
const data = await parse<UserRole[]>(res);
logAPI("listarUserRoles", { url, result: data });
return data ?? [];
}
export async function getCompleteUserInfo(userId: string): Promise<CompleteUserInfo> {
const url = `${API_BASE}/functions/v1/user-info`;
// Assuming the function takes the user ID in the body of a POST request
const res = await fetch(url, { method: "POST", headers: headers("json"), body: JSON.stringify({ id: userId }) });
const data = await parse<CompleteUserInfo>(res);
logAPI("getCompleteUserInfo", { url, payload: { id: userId }, result: data });
return data;
}