feat: Implementar todas correções desde desvinculação Netlify
- Corrigir sintaxe PostgREST em todos serviços (getById, update, delete) * patientService: ?id=eq.uuid * doctorService: ?id=eq.uuid * appointmentService: ?id=eq.uuid * reportService: ?id=eq.uuid - Adicionar componente Chatbot com IA * Respostas inteligentes baseadas em palavras-chave * Quick replies para perguntas frequentes * Integrado em CentralAjuda e CentralAjudaMedico - Padronizar LoginPaciente * Usar loginComEmailSenha (mesma API de LoginSecretaria) * Remover lógica antiga de loginPaciente * Simplificar fluxo de autenticação
This commit is contained in:
parent
e163a1dc7e
commit
0d3cad1b55
@ -1,163 +0,0 @@
|
|||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return { statusCode: 200, headers, body: "" };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token n<>o fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathParts = event.path.split("/");
|
|
||||||
const appointmentId =
|
|
||||||
pathParts[pathParts.length - 1] !== "appointments"
|
|
||||||
? pathParts[pathParts.length - 1]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
let url = `${SUPABASE_URL}/rest/v1/appointments`;
|
|
||||||
if (appointmentId && appointmentId !== "appointments") {
|
|
||||||
url += `?id=eq.${appointmentId}&select=*`;
|
|
||||||
} else if (event.queryStringParameters) {
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
);
|
|
||||||
url += `?${params.toString()}`;
|
|
||||||
if (!params.has("select")) {
|
|
||||||
url += url.includes("?") ? "&select=*" : "?select=*";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += "?select=*";
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader },
|
|
||||||
});
|
|
||||||
let data = await response.json();
|
|
||||||
if (
|
|
||||||
appointmentId &&
|
|
||||||
appointmentId !== "appointments" &&
|
|
||||||
Array.isArray(data) &&
|
|
||||||
data.length > 0
|
|
||||||
) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: { ...headers, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
if (!body.patient_id || !body.doctor_id || !body.scheduled_at) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigat<61>rios: patient_id, doctor_id, scheduled_at",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/rest/v1/appointments`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
let data = await response.json();
|
|
||||||
if (Array.isArray(data) && data.length > 0) data = data[0];
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: { ...headers, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "PATCH") {
|
|
||||||
if (!appointmentId || appointmentId === "appointments") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do agendamento <20> obrigat<61>rio" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`,
|
|
||||||
{
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let data = await response.json();
|
|
||||||
if (Array.isArray(data) && data.length > 0) data = data[0];
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: { ...headers, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "DELETE") {
|
|
||||||
if (!appointmentId || appointmentId === "appointments") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do agendamento <20> obrigat<61>rio" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return { statusCode: response.status, headers, body: "" };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro:", error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Erro interno" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Listar Atribuições
|
|
||||||
* GET /rest/v1/patient_assignments
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET - Listar atribuições
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Monta URL com query params (se houver)
|
|
||||||
const queryString = event.queryStringParameters
|
|
||||||
? "?" +
|
|
||||||
new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
).toString()
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/patient_assignments${queryString}`,
|
|
||||||
{
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro ao listar atribuições:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST - Criar atribuição
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.patient_id || !body.user_id || !body.role) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "patient_id, user_id e role são obrigatórios",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/patient_assignments`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro ao criar atribuição:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Login
|
|
||||||
* Faz proxy seguro para API Supabase com apikey protegida
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
// Constantes da API (protegidas no backend)
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
interface LoginRequest {
|
|
||||||
email: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
// CORS headers
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle preflight
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apenas POST é permitido
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Parse body
|
|
||||||
const body: LoginRequest = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.email || !body.password) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Email e senha são obrigatórios" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Faz requisição para API Supabase COM a apikey protegida
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/auth/v1/token?grant_type=password`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: body.email,
|
|
||||||
password: body.password,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Repassa a resposta para o frontend
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro no login:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Logout
|
|
||||||
* Invalida a sessão do usuário no Supabase
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Pega o Bearer token do header
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Faz logout no Supabase
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/auth/v1/logout`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Logout retorna 204 No Content (sem body)
|
|
||||||
if (response.status === 204) {
|
|
||||||
return {
|
|
||||||
statusCode: 204,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Se não for 204, retorna o body da resposta
|
|
||||||
const data = await response.text();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: data || "{}",
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro no logout:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Magic Link
|
|
||||||
* Envia link de autenticação sem senha por email
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
// Constantes da API (protegidas no backend)
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
interface MagicLinkRequest {
|
|
||||||
email: string;
|
|
||||||
redirect_url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
// CORS headers
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle preflight
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apenas POST é permitido
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Parse body
|
|
||||||
const body: MagicLinkRequest = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.email) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Email é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Faz requisição para API Supabase COM a apikey protegida
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/auth/v1/otp`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: body.email,
|
|
||||||
options: {
|
|
||||||
emailRedirectTo:
|
|
||||||
body.redirect_url ||
|
|
||||||
"https://mediconnectbrasil.netlify.app/auth/callback",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Repassa a resposta para o frontend
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[auth-magic-link] Erro:", error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro ao enviar magic link",
|
|
||||||
details: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Refresh Token
|
|
||||||
* Renova o access token usando o refresh token
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
interface RefreshTokenRequest {
|
|
||||||
refresh_token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body: RefreshTokenRequest = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.refresh_token) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Refresh token é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Faz requisição para renovar token no Supabase
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
refresh_token: body.refresh_token,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro ao renovar token:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Auth User
|
|
||||||
* GET /auth/v1/user - Retorna dados do usuário autenticado
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/auth/v1/user`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de auth user:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Delete Avatar
|
|
||||||
* DELETE /storage/v1/object/avatars/{userId}/avatar
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "DELETE, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "DELETE") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = event.queryStringParameters?.userId;
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "userId é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/storage/v1/object/avatars/${userId}/avatar`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// DELETE pode retornar 200 com body vazio
|
|
||||||
const contentType = response.headers.get("content-type");
|
|
||||||
const data = contentType?.includes("application/json")
|
|
||||||
? await response.json()
|
|
||||||
: {};
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro ao deletar avatar:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Upload Avatar
|
|
||||||
* POST /storage/v1/object/avatars/{userId}/avatar
|
|
||||||
*
|
|
||||||
* Aceita JSON com base64 para simplificar o upload via Netlify Functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extrai userId do query string
|
|
||||||
const userId = event.queryStringParameters?.userId;
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "userId é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse JSON body com base64
|
|
||||||
let fileData: string;
|
|
||||||
let contentType: string;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
fileData = body.fileData; // base64 string
|
|
||||||
contentType = body.contentType || "image/jpeg";
|
|
||||||
|
|
||||||
if (!fileData) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "fileData (base64) é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Body deve ser JSON válido com fileData em base64",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converte base64 para Buffer
|
|
||||||
const buffer = Buffer.from(fileData, "base64");
|
|
||||||
|
|
||||||
// Upload para Supabase Storage
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/storage/v1/object/avatars/${userId}/avatar`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": contentType,
|
|
||||||
"x-upsert": "true", // Sobrescreve se já existir
|
|
||||||
},
|
|
||||||
body: buffer,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error("Erro do Supabase:", data);
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: data.error || "Erro ao fazer upload no Supabase",
|
|
||||||
details: data,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
message: "Upload realizado com sucesso",
|
|
||||||
path: data.Key || data.path,
|
|
||||||
fullPath: data.Key || data.path,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro no upload do avatar:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,163 +0,0 @@
|
|||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return { statusCode: 200, headers, body: "" };
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token n<>o fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathParts = event.path.split("/");
|
|
||||||
const appointmentId =
|
|
||||||
pathParts[pathParts.length - 1] !== "consultas"
|
|
||||||
? pathParts[pathParts.length - 1]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
let url = `${SUPABASE_URL}/rest/v1/appointments`;
|
|
||||||
if (appointmentId && appointmentId !== "consultas") {
|
|
||||||
url += `?id=eq.${appointmentId}&select=*`;
|
|
||||||
} else if (event.queryStringParameters) {
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
);
|
|
||||||
url += `?${params.toString()}`;
|
|
||||||
if (!params.has("select")) {
|
|
||||||
url += url.includes("?") ? "&select=*" : "?select=*";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += "?select=*";
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader },
|
|
||||||
});
|
|
||||||
let data = await response.json();
|
|
||||||
if (
|
|
||||||
appointmentId &&
|
|
||||||
appointmentId !== "consultas" &&
|
|
||||||
Array.isArray(data) &&
|
|
||||||
data.length > 0
|
|
||||||
) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: { ...headers, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
if (!body.patient_id || !body.doctor_id || !body.scheduled_at) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigat<61>rios: patient_id, doctor_id, scheduled_at",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/rest/v1/appointments`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
let data = await response.json();
|
|
||||||
if (Array.isArray(data) && data.length > 0) data = data[0];
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: { ...headers, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "PATCH") {
|
|
||||||
if (!appointmentId || appointmentId === "consultas") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do agendamento <20> obrigat<61>rio" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`,
|
|
||||||
{
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
let data = await response.json();
|
|
||||||
if (Array.isArray(data) && data.length > 0) data = data[0];
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: { ...headers, "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "DELETE") {
|
|
||||||
if (!appointmentId || appointmentId === "consultas") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do agendamento <20> obrigat<61>rio" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/appointments?id=eq.${appointmentId}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: { apikey: SUPABASE_ANON_KEY, Authorization: authHeader },
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return { statusCode: response.status, headers, body: "" };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro:", error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Erro interno" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,100 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Create Doctor
|
|
||||||
* POST /create-doctor - Cria registro de médico com validações
|
|
||||||
* Não cria auth user - apenas registro na tabela doctors
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
// Validação dos campos obrigatórios
|
|
||||||
if (
|
|
||||||
!body.email ||
|
|
||||||
!body.full_name ||
|
|
||||||
!body.cpf ||
|
|
||||||
!body.crm ||
|
|
||||||
!body.crm_uf
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: email, full_name, cpf, crm, crm_uf",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chama a Edge Function do Supabase para criar médico
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/functions/v1/create-doctor`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de create doctor:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Create Patient
|
|
||||||
* POST /create-patient - Cria registro de paciente diretamente
|
|
||||||
* Não cria auth user - apenas registro na tabela patients
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
// Validação dos campos obrigatórios
|
|
||||||
if (
|
|
||||||
!body.full_name ||
|
|
||||||
!body.cpf ||
|
|
||||||
!body.email ||
|
|
||||||
!body.phone_mobile ||
|
|
||||||
!body.created_by
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error:
|
|
||||||
"Campos obrigatórios: full_name, cpf, email, phone_mobile, created_by",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chama REST API do Supabase para criar paciente diretamente
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/rest/v1/patients`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de create patient:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,223 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Create User With Password
|
|
||||||
* POST /create-user-with-password - Cria usuário com senha
|
|
||||||
* Usa Edge Function do Supabase (não Admin API)
|
|
||||||
* Requer permissão de admin, gestor ou secretaria
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization, apikey",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
console.error("[create-user-with-password] Token não fornecido!");
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Token de autenticação é obrigatório",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] Recebido:",
|
|
||||||
JSON.stringify({ ...body, password: "***" }, null, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Validações
|
|
||||||
if (!body.email || !body.password || !body.full_name) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: email, password, full_name",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.password.length < 6) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Senha deve ter no mínimo 6 caracteres",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Criar usuário via Edge Function do Supabase
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] Chamando Edge Function do Supabase..."
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] URL:",
|
|
||||||
`${SUPABASE_URL}/functions/v1/create-user`
|
|
||||||
);
|
|
||||||
console.log("[create-user-with-password] Payload:", {
|
|
||||||
email: body.email,
|
|
||||||
has_password: !!body.password,
|
|
||||||
full_name: body.full_name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createUserResponse = await fetch(
|
|
||||||
`${SUPABASE_URL}/functions/v1/create-user`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: body.email,
|
|
||||||
password: body.password,
|
|
||||||
full_name: body.full_name,
|
|
||||||
phone: body.phone || null,
|
|
||||||
role: body.role || "user",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] Status da resposta:",
|
|
||||||
createUserResponse.status
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] Status text:",
|
|
||||||
createUserResponse.statusText
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sempre tenta ler a resposta como JSON
|
|
||||||
let responseData;
|
|
||||||
try {
|
|
||||||
responseData = await createUserResponse.json();
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] Resposta JSON:",
|
|
||||||
JSON.stringify(responseData, null, 2)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
const responseText = await createUserResponse.text();
|
|
||||||
console.error(
|
|
||||||
"[create-user-with-password] Resposta não é JSON:",
|
|
||||||
responseText
|
|
||||||
);
|
|
||||||
console.error("[create-user-with-password] Erro ao parsear JSON:", error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro ao processar resposta do Supabase",
|
|
||||||
details: responseText,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!createUserResponse.ok) {
|
|
||||||
console.error(
|
|
||||||
"[create-user-with-password] Erro ao criar usuário:",
|
|
||||||
JSON.stringify(responseData, null, 2)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
statusCode: createUserResponse.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
error:
|
|
||||||
responseData.msg || responseData.message || "Erro ao criar usuário",
|
|
||||||
details: responseData,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verificar se a Edge Function retornou sucesso
|
|
||||||
if (!responseData.success) {
|
|
||||||
console.error(
|
|
||||||
"[create-user-with-password] Edge Function retornou erro:",
|
|
||||||
JSON.stringify(responseData, null, 2)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: responseData.error || "Erro ao criar usuário",
|
|
||||||
details: responseData,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const userData = responseData.user;
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] Usuário criado com sucesso:",
|
|
||||||
userData.id
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
"[create-user-with-password] Resposta completa:",
|
|
||||||
JSON.stringify(responseData, null, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// A Edge Function já cria o perfil e atribui a role automaticamente
|
|
||||||
// Retornar sucesso
|
|
||||||
return {
|
|
||||||
statusCode: 201,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
user: userData,
|
|
||||||
message: responseData.message || "Usuário criado com sucesso",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[create-user-with-password] Erro:", error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Create User
|
|
||||||
* POST /create-user - Cria novo usuário no sistema
|
|
||||||
* Requer permissão de admin, gestor ou secretaria
|
|
||||||
* Envia magic link automaticamente para o email
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
// create-user pode ser chamado SEM autenticação (para auto-registro)
|
|
||||||
// Se houver token, será usado; se não houver, usa apenas anon key
|
|
||||||
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[create-user] Recebido body:",
|
|
||||||
JSON.stringify(body, null, 2)
|
|
||||||
);
|
|
||||||
console.log("[create-user] Auth header presente?", !!authHeader);
|
|
||||||
|
|
||||||
// Validação dos campos obrigatórios
|
|
||||||
if (!body.email || !body.full_name) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: email, full_name",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!body.role && (!body.roles || body.roles.length === 0)) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "É necessário fornecer role ou roles",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chama a Edge Function do Supabase para criar usuário
|
|
||||||
const fetchHeaders: Record<string, string> = {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
// Se houver token de usuário autenticado, usa ele; senão usa anon key
|
|
||||||
Authorization: authHeader || `Bearer ${SUPABASE_ANON_KEY}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("[create-user] Chamando Supabase com headers:", {
|
|
||||||
hasAuthHeader: !!authHeader,
|
|
||||||
hasApikey: !!fetchHeaders.apikey,
|
|
||||||
authType: authHeader ? "User Token" : "Anon Key",
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/functions/v1/create-user`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: fetchHeaders,
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
console.log("[create-user] Resposta do Supabase:", {
|
|
||||||
status: response.status,
|
|
||||||
data: JSON.stringify(data, null, 2),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de create user:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Delete User (Hard Delete)
|
|
||||||
* POST /delete-user - Deleta usuário permanentemente
|
|
||||||
* ⚠️ OPERAÇÃO IRREVERSÍVEL - Use com extremo cuidado!
|
|
||||||
* Requer permissão de admin ou gestor
|
|
||||||
* Usa Admin API do Supabase
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_SERVICE_ROLE_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc1NDk1NDM2OSwiZXhwIjoyMDcwNTMwMzY5fQ.Dez8PQkV8vWv7VkL_fZe-lY-Xs9P5VptNvRRnhkxoXw";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization, apikey",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Token de autenticação é obrigatório",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
console.log("[delete-user] ATENÇÃO: Tentativa de hard delete:", {
|
|
||||||
userId: body.userId,
|
|
||||||
requestedBy: "via Netlify Function",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validação
|
|
||||||
if (!body.userId) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "userId é obrigatório",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Aqui deveria verificar se o usuário tem permissão de admin/gestor
|
|
||||||
// Verificando o token JWT que foi passado
|
|
||||||
|
|
||||||
// Deletar usuário via Admin API do Supabase
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/auth/v1/admin/users/${body.userId}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_SERVICE_ROLE_KEY,
|
|
||||||
Authorization: `Bearer ${SUPABASE_SERVICE_ROLE_KEY}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
console.log("[delete-user] Usuário deletado com sucesso:", body.userId);
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message: "Usuário deletado permanentemente",
|
|
||||||
userId: body.userId,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorData = await response.json();
|
|
||||||
console.error("[delete-user] Erro ao deletar:", errorData);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: errorData.msg || errorData.message || "Erro ao deletar usuário",
|
|
||||||
details: errorData,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[delete-user] Erro:", error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,217 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: doctor-availability
|
|
||||||
*
|
|
||||||
* Proxy para operações de disponibilidade dos médicos
|
|
||||||
* GET: Lista disponibilidades
|
|
||||||
* POST: Criar disponibilidade
|
|
||||||
* PATCH: Atualizar disponibilidade
|
|
||||||
* DELETE: Deletar disponibilidade
|
|
||||||
*/
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_API_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export default async (req: Request) => {
|
|
||||||
// Permitir CORS
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 204,
|
|
||||||
headers: {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
const authHeader = req.headers.get("Authorization");
|
|
||||||
|
|
||||||
// Extrair ID do path se existir
|
|
||||||
const pathParts = url.pathname.split("/");
|
|
||||||
const availabilityId = pathParts[pathParts.length - 1];
|
|
||||||
|
|
||||||
// GET: Listar disponibilidades
|
|
||||||
if (req.method === "GET") {
|
|
||||||
const select = url.searchParams.get("select") || "*";
|
|
||||||
const doctor_id = url.searchParams.get("doctor_id");
|
|
||||||
const active = url.searchParams.get("active");
|
|
||||||
|
|
||||||
const queryParams = new URLSearchParams();
|
|
||||||
queryParams.append("select", select);
|
|
||||||
if (doctor_id) queryParams.append("doctor_id", `eq.${doctor_id}`);
|
|
||||||
if (active !== null) queryParams.append("active", `eq.${active}`);
|
|
||||||
|
|
||||||
const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability?${queryParams}`;
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
apikey: SUPABASE_API_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
headers["Authorization"] = authHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(supabaseUrl, {
|
|
||||||
method: "GET",
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(data), {
|
|
||||||
status: response.status,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST: Criar disponibilidade
|
|
||||||
if (req.method === "POST") {
|
|
||||||
const body = await req.json();
|
|
||||||
|
|
||||||
const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability`;
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
apikey: SUPABASE_API_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
headers["Authorization"] = authHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(supabaseUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(data), {
|
|
||||||
status: response.status,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH: Atualizar disponibilidade
|
|
||||||
if (req.method === "PATCH") {
|
|
||||||
if (!availabilityId || availabilityId === "doctor-availability") {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ error: "Availability ID is required" }),
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await req.json();
|
|
||||||
|
|
||||||
const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability?id=eq.${availabilityId}`;
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
apikey: SUPABASE_API_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
headers["Authorization"] = authHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(supabaseUrl, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
const result = Array.isArray(data) && data.length > 0 ? data[0] : data;
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(result), {
|
|
||||||
status: response.status,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE: Deletar disponibilidade
|
|
||||||
if (req.method === "DELETE") {
|
|
||||||
if (!availabilityId || availabilityId === "doctor-availability") {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ error: "Availability ID is required" }),
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_availability?id=eq.${availabilityId}`;
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
apikey: SUPABASE_API_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
headers["Authorization"] = authHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(supabaseUrl, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: response.status,
|
|
||||||
headers: {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Método não suportado
|
|
||||||
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
||||||
status: 405,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error in doctor-availability function:", error);
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
error: "Internal server error",
|
|
||||||
details: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,169 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: doctor-exceptions
|
|
||||||
*
|
|
||||||
* Proxy para operações de exceções na agenda dos médicos
|
|
||||||
* GET: Lista exceções
|
|
||||||
* POST: Criar exceção
|
|
||||||
* DELETE: Deletar exceção
|
|
||||||
*/
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_API_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export default async (req: Request) => {
|
|
||||||
// Permitir CORS
|
|
||||||
if (req.method === "OPTIONS") {
|
|
||||||
return new Response(null, {
|
|
||||||
status: 204,
|
|
||||||
headers: {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = new URL(req.url);
|
|
||||||
const authHeader = req.headers.get("Authorization");
|
|
||||||
|
|
||||||
// Extrair ID do path se existir
|
|
||||||
const pathParts = url.pathname.split("/");
|
|
||||||
const exceptionId = pathParts[pathParts.length - 1];
|
|
||||||
|
|
||||||
// GET: Listar exceções
|
|
||||||
if (req.method === "GET") {
|
|
||||||
const select = url.searchParams.get("select") || "*";
|
|
||||||
const doctor_id = url.searchParams.get("doctor_id");
|
|
||||||
const date = url.searchParams.get("date");
|
|
||||||
|
|
||||||
const queryParams = new URLSearchParams();
|
|
||||||
queryParams.append("select", select);
|
|
||||||
if (doctor_id) queryParams.append("doctor_id", `eq.${doctor_id}`);
|
|
||||||
if (date) queryParams.append("date", `eq.${date}`);
|
|
||||||
|
|
||||||
const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_exceptions?${queryParams}`;
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
apikey: SUPABASE_API_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
headers["Authorization"] = authHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(supabaseUrl, {
|
|
||||||
method: "GET",
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(data), {
|
|
||||||
status: response.status,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST: Criar exceção
|
|
||||||
if (req.method === "POST") {
|
|
||||||
const body = await req.json();
|
|
||||||
|
|
||||||
const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_exceptions`;
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
apikey: SUPABASE_API_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
headers["Authorization"] = authHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(supabaseUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return new Response(JSON.stringify(data), {
|
|
||||||
status: response.status,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE: Deletar exceção
|
|
||||||
if (req.method === "DELETE") {
|
|
||||||
if (!exceptionId || exceptionId === "doctor-exceptions") {
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({ error: "Exception ID is required" }),
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const supabaseUrl = `${SUPABASE_URL}/rest/v1/doctor_exceptions?id=eq.${exceptionId}`;
|
|
||||||
|
|
||||||
const headers: HeadersInit = {
|
|
||||||
apikey: SUPABASE_API_KEY,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (authHeader) {
|
|
||||||
headers["Authorization"] = authHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(supabaseUrl, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: response.status,
|
|
||||||
headers: {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Método não suportado
|
|
||||||
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
||||||
status: 405,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error in doctor-exceptions function:", error);
|
|
||||||
return new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
error: "Internal server error",
|
|
||||||
details: error instanceof Error ? error.message : "Unknown error",
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,237 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Doctors CRUD
|
|
||||||
* GET /rest/v1/doctors - Lista médicos
|
|
||||||
* GET /rest/v1/doctors/{id} - Busca por ID
|
|
||||||
* POST /rest/v1/doctors - Cria médico
|
|
||||||
* PATCH /rest/v1/doctors/{id} - Atualiza médico
|
|
||||||
* DELETE /rest/v1/doctors/{id} - Deleta médico
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extrai ID da URL se houver (doctors/123 ou doctors?id=123)
|
|
||||||
const pathParts = event.path.split("/");
|
|
||||||
const doctorId =
|
|
||||||
pathParts[pathParts.length - 1] !== "doctors"
|
|
||||||
? pathParts[pathParts.length - 1]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// GET - Listar ou buscar por ID
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
let url = `${SUPABASE_URL}/rest/v1/doctors`;
|
|
||||||
|
|
||||||
if (doctorId && doctorId !== "doctors") {
|
|
||||||
// Buscar por ID específico
|
|
||||||
url += `?id=eq.${doctorId}&select=*`;
|
|
||||||
} else if (event.queryStringParameters) {
|
|
||||||
// Adiciona filtros da query string
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
);
|
|
||||||
url += `?${params.toString()}`;
|
|
||||||
|
|
||||||
// Adiciona select=* se não tiver
|
|
||||||
if (!params.has("select")) {
|
|
||||||
url += url.includes("?") ? "&select=*" : "?select=*";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += "?select=*";
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
// Se buscar por ID, retorna o objeto diretamente (não array)
|
|
||||||
if (
|
|
||||||
doctorId &&
|
|
||||||
doctorId !== "doctors" &&
|
|
||||||
Array.isArray(data) &&
|
|
||||||
data.length > 0
|
|
||||||
) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST - Criar médico
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (
|
|
||||||
!body.crm ||
|
|
||||||
!body.crm_uf ||
|
|
||||||
!body.full_name ||
|
|
||||||
!body.cpf ||
|
|
||||||
!body.email
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: crm, crm_uf, full_name, cpf, email",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/rest/v1/doctors`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
// Supabase retorna array, pega o primeiro
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH - Atualizar médico
|
|
||||||
if (event.httpMethod === "PATCH") {
|
|
||||||
if (!doctorId || doctorId === "doctors") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do médico é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/doctors?id=eq.${doctorId}`,
|
|
||||||
{
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE - Deletar médico
|
|
||||||
if (event.httpMethod === "DELETE") {
|
|
||||||
if (!doctorId || doctorId === "doctors") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do médico é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/doctors?id=eq.${doctorId}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de médicos:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Get Available Slots
|
|
||||||
* POST /functions/v1/get-available-slots - Busca horários disponíveis
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
// Validação dos campos obrigatórios
|
|
||||||
if (!body.doctor_id || !body.start_date || !body.end_date) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: doctor_id, start_date, end_date",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/functions/v1/get-available-slots`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de available slots:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,226 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Patients CRUD
|
|
||||||
* GET /rest/v1/patients - Lista pacientes
|
|
||||||
* GET /rest/v1/patients/{id} - Busca por ID
|
|
||||||
* POST /rest/v1/patients - Cria paciente
|
|
||||||
* PATCH /rest/v1/patients/{id} - Atualiza paciente
|
|
||||||
* DELETE /rest/v1/patients/{id} - Deleta paciente
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extrai ID da URL se houver
|
|
||||||
const pathParts = event.path.split("/");
|
|
||||||
const patientId =
|
|
||||||
pathParts[pathParts.length - 1] !== "patients"
|
|
||||||
? pathParts[pathParts.length - 1]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// GET - Listar ou buscar por ID
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
let url = `${SUPABASE_URL}/rest/v1/patients`;
|
|
||||||
|
|
||||||
if (patientId && patientId !== "patients") {
|
|
||||||
url += `?id=eq.${patientId}&select=*`;
|
|
||||||
} else if (event.queryStringParameters) {
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
);
|
|
||||||
url += `?${params.toString()}`;
|
|
||||||
|
|
||||||
if (!params.has("select")) {
|
|
||||||
url += url.includes("?") ? "&select=*" : "?select=*";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += "?select=*";
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (
|
|
||||||
patientId &&
|
|
||||||
patientId !== "patients" &&
|
|
||||||
Array.isArray(data) &&
|
|
||||||
data.length > 0
|
|
||||||
) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST - Criar paciente
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.full_name || !body.cpf || !body.email || !body.phone_mobile) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: full_name, cpf, email, phone_mobile",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/rest/v1/patients`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH - Atualizar paciente
|
|
||||||
if (event.httpMethod === "PATCH") {
|
|
||||||
if (!patientId || patientId === "patients") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do paciente é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/patients?id=eq.${patientId}`,
|
|
||||||
{
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE - Deletar paciente
|
|
||||||
if (event.httpMethod === "DELETE") {
|
|
||||||
if (!patientId || patientId === "patients") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do paciente é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/patients?id=eq.${patientId}`,
|
|
||||||
{
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de pacientes:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,155 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Profiles
|
|
||||||
* GET /rest/v1/profiles - Lista perfis
|
|
||||||
* GET /rest/v1/profiles/{id} - Busca por ID
|
|
||||||
* PATCH /rest/v1/profiles/{id} - Atualiza avatar_url
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, PATCH, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extrai ID da URL se houver
|
|
||||||
const pathParts = event.path.split("/");
|
|
||||||
const profileId =
|
|
||||||
pathParts[pathParts.length - 1] !== "profiles"
|
|
||||||
? pathParts[pathParts.length - 1]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// GET - Listar ou buscar por ID
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
let url = `${SUPABASE_URL}/rest/v1/profiles`;
|
|
||||||
|
|
||||||
if (profileId && profileId !== "profiles") {
|
|
||||||
url += `?id=eq.${profileId}&select=*`;
|
|
||||||
} else if (event.queryStringParameters) {
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
);
|
|
||||||
url += `?${params.toString()}`;
|
|
||||||
|
|
||||||
if (!params.has("select")) {
|
|
||||||
url += url.includes("?") ? "&select=*" : "?select=*";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += "?select=*";
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (
|
|
||||||
profileId &&
|
|
||||||
profileId !== "profiles" &&
|
|
||||||
Array.isArray(data) &&
|
|
||||||
data.length > 0
|
|
||||||
) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH - Atualizar avatar_url
|
|
||||||
if (event.httpMethod === "PATCH") {
|
|
||||||
if (!profileId || profileId === "profiles") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do perfil é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/profiles?id=eq.${profileId}`,
|
|
||||||
{
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de perfis:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Register Patient (Public)
|
|
||||||
* POST /register-patient - Registro público de paciente
|
|
||||||
* Não requer autenticação - função pública
|
|
||||||
* Validações rigorosas (CPF, rate limiting, rollback)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[register-patient] Recebido body:",
|
|
||||||
JSON.stringify(body, null, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Validação dos campos obrigatórios
|
|
||||||
if (!body.email || !body.full_name || !body.cpf || !body.phone_mobile) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: email, full_name, cpf, phone_mobile",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chama a Edge Function pública do Supabase
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/functions/v1/register-patient`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: `Bearer ${SUPABASE_ANON_KEY}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
console.log("[register-patient] Resposta do Supabase:", {
|
|
||||||
status: response.status,
|
|
||||||
data: JSON.stringify(data, null, 2),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[register-patient] Erro na API:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno do servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,197 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Reports
|
|
||||||
* GET /rest/v1/reports - Lista relatórios
|
|
||||||
* GET /rest/v1/reports/{id} - Busca por ID
|
|
||||||
* POST /rest/v1/reports - Cria relatório
|
|
||||||
* PATCH /rest/v1/reports/{id} - Atualiza relatório
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PATCH, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extrai ID da URL se houver
|
|
||||||
const pathParts = event.path.split("/");
|
|
||||||
const reportId =
|
|
||||||
pathParts[pathParts.length - 1] !== "reports"
|
|
||||||
? pathParts[pathParts.length - 1]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// GET - Listar ou buscar por ID
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
let url = `${SUPABASE_URL}/rest/v1/reports`;
|
|
||||||
|
|
||||||
if (reportId && reportId !== "reports") {
|
|
||||||
url += `?id=eq.${reportId}&select=*`;
|
|
||||||
} else if (event.queryStringParameters) {
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
);
|
|
||||||
url += `?${params.toString()}`;
|
|
||||||
|
|
||||||
if (!params.has("select")) {
|
|
||||||
url += url.includes("?") ? "&select=*" : "?select=*";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
url += "?select=*";
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (
|
|
||||||
reportId &&
|
|
||||||
reportId !== "reports" &&
|
|
||||||
Array.isArray(data) &&
|
|
||||||
data.length > 0
|
|
||||||
) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST - Criar relatório
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.patient_id) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campo obrigatório: patient_id",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/rest/v1/reports`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH - Atualizar relatório
|
|
||||||
if (event.httpMethod === "PATCH") {
|
|
||||||
if (!reportId || reportId === "reports") {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "ID do relatório é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/rest/v1/reports?id=eq.${reportId}`,
|
|
||||||
{
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = await response.json();
|
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
data = data[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de relatórios:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Request Password Reset
|
|
||||||
* POST /request-password-reset - Solicita reset de senha via email (público)
|
|
||||||
* Não requer autenticação - endpoint público
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
interface PasswordResetRequest {
|
|
||||||
email: string;
|
|
||||||
redirect_url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const body: PasswordResetRequest = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
console.log("[request-password-reset] Recebido:", {
|
|
||||||
email: body.email,
|
|
||||||
hasRedirectUrl: !!body.redirect_url,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!body.email) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Email é obrigatório" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chama a API do Supabase para enviar email de reset
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/auth/v1/recover`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: body.email,
|
|
||||||
options: {
|
|
||||||
redirectTo:
|
|
||||||
body.redirect_url ||
|
|
||||||
"https://mediconnectbrasil.netlify.app/reset-password",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
console.log("[request-password-reset] Resposta Supabase:", {
|
|
||||||
status: response.status,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Supabase sempre retorna 200 mesmo se o email não existir (por segurança)
|
|
||||||
if (response.ok) {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
success: true,
|
|
||||||
message:
|
|
||||||
"Email de reset de senha enviado com sucesso. Verifique sua caixa de entrada.",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("[request-password-reset] Erro:", error);
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro ao solicitar reset de senha",
|
|
||||||
details: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: Send SMS
|
|
||||||
* POST /functions/v1/send-sms - Envia SMS via Twilio
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
// Validação dos campos obrigatórios
|
|
||||||
if (!body.phone_number || !body.message) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Campos obrigatórios: phone_number, message",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chama a função Supabase de enviar SMS
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/functions/v1/send-sms`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de SMS:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: User Info By ID
|
|
||||||
* POST /user-info-by-id - Retorna dados de usuário específico (apenas admin/gestor)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod !== "POST") {
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.user_id) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Campo obrigatório: user_id" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chama a Edge Function do Supabase
|
|
||||||
const response = await fetch(
|
|
||||||
`${SUPABASE_URL}/functions/v1/user-info-by-id`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de user-info-by-id:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: User Info
|
|
||||||
* GET /functions/v1/user-info - Retorna informações completas do usuário autenticado
|
|
||||||
* Inclui: user, profile, roles e permissions calculadas
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Aceita tanto POST quanto GET para compatibilidade
|
|
||||||
if (event.httpMethod === "POST" || event.httpMethod === "GET") {
|
|
||||||
// Chama a Edge Function do Supabase (POST conforme doc 21/10/2025)
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/functions/v1/user-info`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de user-info:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,161 +0,0 @@
|
|||||||
/**
|
|
||||||
* Netlify Function: User Roles
|
|
||||||
* GET /rest/v1/user_roles - Lista roles de usuários
|
|
||||||
* POST /rest/v1/user_roles - Adiciona role a um usuário
|
|
||||||
* DELETE /rest/v1/user_roles - Remove role de um usuário
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { Handler, HandlerEvent } from "@netlify/functions";
|
|
||||||
|
|
||||||
const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co";
|
|
||||||
const SUPABASE_ANON_KEY =
|
|
||||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ";
|
|
||||||
|
|
||||||
export const handler: Handler = async (event: HandlerEvent) => {
|
|
||||||
const headers = {
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
||||||
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.httpMethod === "OPTIONS") {
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers,
|
|
||||||
body: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authHeader =
|
|
||||||
event.headers.authorization || event.headers.Authorization;
|
|
||||||
|
|
||||||
if (!authHeader) {
|
|
||||||
return {
|
|
||||||
statusCode: 401,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Token não fornecido" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "GET") {
|
|
||||||
let url = `${SUPABASE_URL}/rest/v1/user_roles?select=*`;
|
|
||||||
|
|
||||||
if (event.queryStringParameters) {
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
event.queryStringParameters as Record<string, string>
|
|
||||||
);
|
|
||||||
const paramsStr = params.toString();
|
|
||||||
if (paramsStr) {
|
|
||||||
url += `&${paramsStr}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "POST") {
|
|
||||||
// Adicionar nova role para um usuário
|
|
||||||
const body = JSON.parse(event.body || "{}");
|
|
||||||
|
|
||||||
if (!body.user_id || !body.role) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "user_id e role são obrigatórios" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${SUPABASE_URL}/rest/v1/user_roles`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Prefer: "return=representation",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
user_id: body.user_id,
|
|
||||||
role: body.role,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.httpMethod === "DELETE") {
|
|
||||||
// Remover role de um usuário
|
|
||||||
const params = event.queryStringParameters;
|
|
||||||
|
|
||||||
if (!params?.user_id || !params?.role) {
|
|
||||||
return {
|
|
||||||
statusCode: 400,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "user_id e role são obrigatórios" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${SUPABASE_URL}/rest/v1/user_roles?user_id=eq.${params.user_id}&role=eq.${params.role}`;
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: {
|
|
||||||
apikey: SUPABASE_ANON_KEY,
|
|
||||||
Authorization: authHeader,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: response.status,
|
|
||||||
headers: {
|
|
||||||
...headers,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ success: true }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 405,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ error: "Method Not Allowed" }),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erro na API de user roles:", error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 500,
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
error: "Erro interno no servidor",
|
|
||||||
message: error instanceof Error ? error.message : "Erro desconhecido",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
234
MEDICONNECT 2/src/components/Chatbot.tsx
Normal file
234
MEDICONNECT 2/src/components/Chatbot.tsx
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
import { useState, useRef, useEffect } from 'react';
|
||||||
|
import { MessageCircle, X, Send } from 'lucide-react';
|
||||||
|
|
||||||
|
interface Message {
|
||||||
|
id: number;
|
||||||
|
text: string;
|
||||||
|
sender: 'user' | 'bot';
|
||||||
|
timestamp: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Chatbot() {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [messages, setMessages] = useState<Message[]>([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
text: 'Olá! Como posso ajudar você hoje?',
|
||||||
|
sender: 'bot',
|
||||||
|
timestamp: new Date(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const [inputMessage, setInputMessage] = useState('');
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const scrollToBottom = () => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
scrollToBottom();
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const handleSend = async () => {
|
||||||
|
if (!inputMessage.trim()) return;
|
||||||
|
|
||||||
|
const userMessage: Message = {
|
||||||
|
id: messages.length + 1,
|
||||||
|
text: inputMessage,
|
||||||
|
sender: 'user',
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages((prev) => [...prev, userMessage]);
|
||||||
|
setInputMessage('');
|
||||||
|
setIsTyping(true);
|
||||||
|
|
||||||
|
// Simular resposta do bot
|
||||||
|
setTimeout(() => {
|
||||||
|
const botResponse = getBotResponse(inputMessage.toLowerCase());
|
||||||
|
const botMessage: Message = {
|
||||||
|
id: messages.length + 2,
|
||||||
|
text: botResponse,
|
||||||
|
sender: 'bot',
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
setMessages((prev) => [...prev, botMessage]);
|
||||||
|
setIsTyping(false);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBotResponse = (input: string): string => {
|
||||||
|
if (input.includes('agendar') || input.includes('consulta') || input.includes('marcar')) {
|
||||||
|
return 'Para agendar uma consulta, acesse o painel do paciente e clique em "Agendar Consulta". Você poderá escolher o médico, data e horário disponível.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('cancelar') || input.includes('remarcar')) {
|
||||||
|
return 'Para cancelar ou remarcar uma consulta, acesse "Minhas Consultas" no painel do paciente e clique na consulta desejada.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('pagamento') || input.includes('pagar')) {
|
||||||
|
return 'Aceitamos pagamento via PIX, cartão de crédito e débito. O pagamento pode ser realizado no momento da consulta ou através do nosso sistema online.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('senha') || input.includes('esqueci')) {
|
||||||
|
return 'Para redefinir sua senha, clique em "Esqueci minha senha" na tela de login e siga as instruções enviadas para seu e-mail.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('exame') || input.includes('resultado')) {
|
||||||
|
return 'Os resultados de exames ficam disponíveis no menu "Meus Laudos" do painel do paciente. Você receberá uma notificação quando estiverem prontos.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('horário') || input.includes('funciona')) {
|
||||||
|
return 'Nosso atendimento funciona de segunda a sexta das 8h às 18h, e sábados das 8h às 12h.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('telemedicina') || input.includes('online')) {
|
||||||
|
return 'Sim, oferecemos consultas por telemedicina! Ao agendar, selecione a opção "Teleconsulta" e você receberá o link para a videochamada.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('prontuário') || input.includes('histórico')) {
|
||||||
|
return 'Seu histórico médico completo está disponível no menu "Meu Prontuário" no painel do paciente.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.includes('suporte') || input.includes('ajuda') || input.includes('contato')) {
|
||||||
|
return 'Para suporte adicional, entre em contato conosco:\n📞 Telefone: (11) 1234-5678\n📧 Email: suporte@mediconnect.com.br\n💬 WhatsApp: (11) 98765-4321';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Desculpe, não entendi sua pergunta. Você pode perguntar sobre:\n• Agendar consultas\n• Cancelar/remarcar consultas\n• Pagamentos\n• Resultados de exames\n• Redefinir senha\n• Horário de funcionamento\n• Telemedicina\n• Contato/suporte';
|
||||||
|
};
|
||||||
|
|
||||||
|
const quickReplies = [
|
||||||
|
'Como agendar consulta?',
|
||||||
|
'Horário de funcionamento',
|
||||||
|
'Resultados de exames',
|
||||||
|
'Esqueci minha senha',
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Botão flutuante */}
|
||||||
|
{!isOpen && (
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(true)}
|
||||||
|
className="fixed bottom-6 right-6 bg-[#00a8a8] hover:bg-[#008c8c] text-white rounded-full p-4 shadow-lg transition-all duration-300 hover:scale-110 z-50"
|
||||||
|
aria-label="Abrir chat"
|
||||||
|
>
|
||||||
|
<MessageCircle size={28} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Janela do chat */}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="fixed bottom-6 right-6 w-96 h-[600px] bg-white rounded-2xl shadow-2xl flex flex-col z-50 border border-gray-200">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-gradient-to-r from-[#00a8a8] to-[#008c8c] text-white p-4 rounded-t-2xl flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="bg-white/20 p-2 rounded-full">
|
||||||
|
<MessageCircle size={24} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-lg">Assistente Virtual</h3>
|
||||||
|
<p className="text-sm text-white/80">Online</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
className="hover:bg-white/20 p-2 rounded-full transition-colors"
|
||||||
|
aria-label="Fechar chat"
|
||||||
|
>
|
||||||
|
<X size={24} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mensagens */}
|
||||||
|
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
|
||||||
|
{messages.map((message) => (
|
||||||
|
<div
|
||||||
|
key={message.id}
|
||||||
|
className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`max-w-[80%] rounded-2xl px-4 py-2 ${
|
||||||
|
message.sender === 'user'
|
||||||
|
? 'bg-[#00a8a8] text-white rounded-br-sm'
|
||||||
|
: 'bg-white text-gray-800 rounded-bl-sm shadow-sm border border-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<p className="text-sm whitespace-pre-line">{message.text}</p>
|
||||||
|
<p
|
||||||
|
className={`text-xs mt-1 ${
|
||||||
|
message.sender === 'user' ? 'text-white/70' : 'text-gray-500'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{message.timestamp.toLocaleTimeString('pt-BR', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{isTyping && (
|
||||||
|
<div className="flex justify-start">
|
||||||
|
<div className="bg-white rounded-2xl rounded-bl-sm px-4 py-3 shadow-sm border border-gray-200">
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></span>
|
||||||
|
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></span>
|
||||||
|
<span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.4s' }}></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Respostas rápidas */}
|
||||||
|
{messages.length === 1 && (
|
||||||
|
<div className="px-4 py-2 border-t border-gray-200 bg-white">
|
||||||
|
<p className="text-xs text-gray-600 mb-2">Perguntas frequentes:</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{quickReplies.map((reply, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => {
|
||||||
|
setInputMessage(reply);
|
||||||
|
handleSend();
|
||||||
|
}}
|
||||||
|
className="text-xs bg-gray-100 hover:bg-gray-200 text-gray-700 px-3 py-1.5 rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
{reply}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Input */}
|
||||||
|
<div className="p-4 border-t border-gray-200 bg-white rounded-b-2xl">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputMessage}
|
||||||
|
onChange={(e) => setInputMessage(e.target.value)}
|
||||||
|
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
|
||||||
|
placeholder="Digite sua mensagem..."
|
||||||
|
className="flex-1 border border-gray-300 rounded-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-[#00a8a8] focus:border-transparent text-sm"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSend}
|
||||||
|
disabled={!inputMessage.trim()}
|
||||||
|
className="bg-[#00a8a8] hover:bg-[#008c8c] disabled:bg-gray-300 disabled:cursor-not-allowed text-white rounded-full p-2 transition-colors"
|
||||||
|
aria-label="Enviar mensagem"
|
||||||
|
>
|
||||||
|
<Send size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@ import {
|
|||||||
Headphones,
|
Headphones,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { Chatbot } from "../components/Chatbot";
|
||||||
|
|
||||||
interface FAQ {
|
interface FAQ {
|
||||||
question: string;
|
question: string;
|
||||||
@ -404,6 +405,9 @@ const CentralAjuda: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Chatbot */}
|
||||||
|
<Chatbot />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
Headphones,
|
Headphones,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { Chatbot } from "../components/Chatbot";
|
||||||
|
|
||||||
interface FAQ {
|
interface FAQ {
|
||||||
question: string;
|
question: string;
|
||||||
@ -408,6 +409,9 @@ const CentralAjudaMedico: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Chatbot */}
|
||||||
|
<Chatbot />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const LoginPaciente: React.FC = () => {
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { loginPaciente } = useAuth();
|
const { loginComEmailSenha } = useAuth();
|
||||||
|
|
||||||
const handleLogin = async (e: React.FormEvent) => {
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -31,48 +31,19 @@ const LoginPaciente: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
console.log("[LoginPaciente] Fazendo login com email:", formData.email);
|
console.log("[LoginPaciente] Fazendo login com email:", formData.email);
|
||||||
|
|
||||||
// Fazer login via API Supabase
|
const ok = await loginComEmailSenha(formData.email, formData.senha);
|
||||||
await authService.login({
|
|
||||||
email: formData.email,
|
|
||||||
password: formData.senha,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("[LoginPaciente] Login bem-sucedido!");
|
if (ok) {
|
||||||
|
console.log("[LoginPaciente] Login bem-sucedido! Navegando para /acompanhamento");
|
||||||
// Buscar dados do paciente da API
|
toast.success("Login realizado com sucesso!");
|
||||||
const pacientes = await patientService.list();
|
navigate("/acompanhamento");
|
||||||
const paciente = pacientes.find((p: any) => p.email === formData.email);
|
|
||||||
|
|
||||||
console.log("[LoginPaciente] Paciente encontrado:", paciente);
|
|
||||||
|
|
||||||
if (paciente) {
|
|
||||||
console.log("[LoginPaciente] Paciente encontrado:", {
|
|
||||||
id: paciente.id,
|
|
||||||
nome: paciente.full_name,
|
|
||||||
email: paciente.email,
|
|
||||||
});
|
|
||||||
const ok = await loginPaciente({
|
|
||||||
id: paciente.id,
|
|
||||||
nome: paciente.full_name,
|
|
||||||
email: paciente.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
console.log("[LoginPaciente] Navegando para /acompanhamento");
|
|
||||||
navigate("/acompanhamento");
|
|
||||||
} else {
|
|
||||||
console.error("[LoginPaciente] loginPaciente retornou false");
|
|
||||||
toast.error("Erro ao processar login");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.log("[LoginPaciente] Paciente não encontrado na lista");
|
console.error("[LoginPaciente] loginComEmailSenha retornou false");
|
||||||
toast.error(
|
toast.error("Credenciais inválidas ou usuário sem permissão");
|
||||||
"Dados do paciente não encontrados. Entre em contato com o suporte."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[LoginPaciente] Erro no login:", error);
|
console.error("[LoginPaciente] Erro no login:", error);
|
||||||
toast.error("Erro ao fazer login. Tente novamente.");
|
toast.error("Erro ao fazer login. Verifique suas credenciais.");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@ -158,68 +129,6 @@ const LoginPaciente: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Login LOCAL: cria uma sessão de paciente sem chamar a API
|
|
||||||
const handleLoginLocal = async () => {
|
|
||||||
const email = formData.email.trim();
|
|
||||||
const senha = formData.senha;
|
|
||||||
|
|
||||||
console.log("[LoginPaciente] Login local - tentando com API primeiro");
|
|
||||||
|
|
||||||
// Tentar fazer login via API mesmo no modo "local"
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
// Fazer login via API Supabase
|
|
||||||
await authService.login({
|
|
||||||
email: email,
|
|
||||||
password: senha,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("[LoginPaciente] Login via API bem-sucedido!");
|
|
||||||
|
|
||||||
// Buscar dados do paciente da API
|
|
||||||
const pacientes = await patientService.list();
|
|
||||||
const paciente = pacientes.find((p: any) => p.email === email);
|
|
||||||
|
|
||||||
if (paciente) {
|
|
||||||
console.log(
|
|
||||||
"[LoginPaciente] Paciente encontrado na API:",
|
|
||||||
paciente.full_name
|
|
||||||
);
|
|
||||||
const ok = await loginPaciente({
|
|
||||||
id: paciente.id,
|
|
||||||
nome: paciente.full_name,
|
|
||||||
email: paciente.email,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
navigate("/acompanhamento");
|
|
||||||
} else {
|
|
||||||
toast.error("Erro ao processar login");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"[LoginPaciente] Paciente não encontrado na API, usando dados locais"
|
|
||||||
);
|
|
||||||
const ok = await loginPaciente({
|
|
||||||
id: email,
|
|
||||||
nome: email.split("@")[0],
|
|
||||||
email: email,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
navigate("/acompanhamento");
|
|
||||||
} else {
|
|
||||||
toast.error("Erro ao processar login");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("[LoginPaciente] Erro no login:", err);
|
|
||||||
toast.error("Erro ao fazer login");
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-white dark:from-gray-900 dark:to-gray-950 flex items-center justify-center p-4 transition-colors">
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-white dark:from-gray-900 dark:to-gray-950 flex items-center justify-center p-4 transition-colors">
|
||||||
<div className="max-w-md w-full">
|
<div className="max-w-md w-full">
|
||||||
@ -335,8 +244,7 @@ const LoginPaciente: React.FC = () => {
|
|||||||
**/}
|
**/}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="submit"
|
||||||
onClick={handleLoginLocal}
|
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="w-full bg-gradient-to-r from-blue-700 to-blue-400 text-white py-3 px-4 rounded-lg font-medium hover:from-blue-800 hover:to-blue-500 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
className="w-full bg-gradient-to-r from-blue-700 to-blue-400 text-white py-3 px-4 rounded-lg font-medium hover:from-blue-800 hover:to-blue-500 hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -72,8 +72,11 @@ class AppointmentService {
|
|||||||
* Busca agendamento por ID
|
* Busca agendamento por ID
|
||||||
*/
|
*/
|
||||||
async getById(id: string): Promise<Appointment> {
|
async getById(id: string): Promise<Appointment> {
|
||||||
const response = await apiClient.get<Appointment>(`${this.basePath}/${id}`);
|
const response = await apiClient.get<Appointment[]>(`${this.basePath}?id=eq.${id}`);
|
||||||
return response.data;
|
if (response.data && response.data.length > 0) {
|
||||||
|
return response.data[0];
|
||||||
|
}
|
||||||
|
throw new Error("Agendamento não encontrado");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,18 +92,21 @@ class AppointmentService {
|
|||||||
* Atualiza agendamento existente
|
* Atualiza agendamento existente
|
||||||
*/
|
*/
|
||||||
async update(id: string, data: UpdateAppointmentInput): Promise<Appointment> {
|
async update(id: string, data: UpdateAppointmentInput): Promise<Appointment> {
|
||||||
const response = await apiClient.patch<Appointment>(
|
const response = await apiClient.patch<Appointment[]>(
|
||||||
`${this.basePath}/${id}`,
|
`${this.basePath}?id=eq.${id}`,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
return response.data;
|
if (response.data && response.data.length > 0) {
|
||||||
|
return response.data[0];
|
||||||
|
}
|
||||||
|
throw new Error("Agendamento não encontrado");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deleta agendamento
|
* Deleta agendamento
|
||||||
*/
|
*/
|
||||||
async delete(id: string): Promise<void> {
|
async delete(id: string): Promise<void> {
|
||||||
await apiClient.delete(`${this.basePath}/${id}`);
|
await apiClient.delete(`${this.basePath}?id=eq.${id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,8 +35,11 @@ class ReportService {
|
|||||||
* Busca relatório por ID
|
* Busca relatório por ID
|
||||||
*/
|
*/
|
||||||
async getById(id: string): Promise<Report> {
|
async getById(id: string): Promise<Report> {
|
||||||
const response = await apiClient.get<Report>(`${this.basePath}/${id}`);
|
const response = await apiClient.get<Report[]>(`${this.basePath}?id=eq.${id}`);
|
||||||
return response.data;
|
if (response.data && response.data.length > 0) {
|
||||||
|
return response.data[0];
|
||||||
|
}
|
||||||
|
throw new Error("Relatório não encontrado");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,11 +56,14 @@ class ReportService {
|
|||||||
* Nota: order_number não pode ser modificado
|
* Nota: order_number não pode ser modificado
|
||||||
*/
|
*/
|
||||||
async update(id: string, data: UpdateReportInput): Promise<Report> {
|
async update(id: string, data: UpdateReportInput): Promise<Report> {
|
||||||
const response = await apiClient.patch<Report>(
|
const response = await apiClient.patch<Report[]>(
|
||||||
`${this.basePath}/${id}`,
|
`${this.basePath}?id=eq.${id}`,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
return response.data;
|
if (response.data && response.data.length > 0) {
|
||||||
|
return response.data[0];
|
||||||
|
}
|
||||||
|
throw new Error("Relatório não encontrado");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user