Implementa integrações de repositórios
This commit is contained in:
@@ -1,49 +1,73 @@
|
|||||||
# Auditoria de Implementacao e Mapeamento da API
|
# Auditoria de Implementacao e Mapeamento da API
|
||||||
|
|
||||||
Este documento resume o estado atual da integracao entre o front-end e os endpoints da API.
|
Este documento resume as APIs do Apidog conectadas no projeto `riseup_squad_03`.
|
||||||
|
|
||||||
## Integrado no front
|
## Autenticacao
|
||||||
|
|
||||||
- **Autenticacao**
|
- `POST /auth/v1/token?grant_type=password` em `authRepository.login`
|
||||||
- Login com email e senha via Supabase Auth (`/auth/v1/token`).
|
- `POST /auth/v1/otp` em `authRepository.sendMagicLink`
|
||||||
- Solicitar reset de senha: tenta `/solicitar-reset-de-senha` e usa `/auth/v1/recover` como fallback.
|
- `POST /functions/v1/request-password-reset` em `authRepository.requestPasswordReset`
|
||||||
- Dados do usuario autenticado: tenta `/informacoes-do-usuario-autenticado` e usa `/auth/v1/user` como fallback.
|
- `GET /auth/v1/user` em `authRepository.getUser` como fallback
|
||||||
- Logout: tenta `/logout`, usa `/auth/v1/logout` como fallback e sempre limpa a sessao local.
|
- `POST /functions/v1/user-info` em `authRepository.getUser`
|
||||||
|
- `POST /auth/v1/logout` em `authRepository.logout`
|
||||||
|
|
||||||
- **Pacientes**
|
## Usuarios
|
||||||
- Listar, criar, atualizar e deletar pacientes via Supabase REST.
|
|
||||||
- Criar paciente com validacao via Edge Function quando disponivel.
|
|
||||||
|
|
||||||
- **Agendamentos**
|
- `POST /functions/v1/create-user` em `userRepository.create`
|
||||||
- Listar agendamentos: tenta `GET /agendamentos` e usa Supabase REST `appointments` como fallback.
|
- `POST /functions/v1/create-user-with-password` em `userRepository.createWithPassword`
|
||||||
- Criar agendamento: tenta `POST /agendamentos` e usa Supabase REST `appointments` como fallback.
|
- `POST /functions/v1/user-info-by-id/:id` em `userRepository.getById`
|
||||||
|
- `POST /functions/v1/delete-user` em `userRepository.remove`
|
||||||
|
- Listagem de perfis via REST em `profiles` / `user_profiles` em `userRepository.getAll`
|
||||||
|
|
||||||
- **Laudos Medicos**
|
## Pacientes
|
||||||
- Listar relatorios: tenta `GET /reports` e usa Supabase REST `reports` como fallback.
|
|
||||||
- Criar relatorio: tenta `POST /reports` e usa Supabase REST `reports` como fallback.
|
|
||||||
- Atualizar relatorio: tenta `PATCH /reports/{id}`, depois `PATCH /reports`, e usa Supabase REST `reports` como fallback.
|
|
||||||
|
|
||||||
- **Medicos / Profissionais**
|
- `GET /rest/v1/patients` em `patientRepository.getAll`
|
||||||
- Listar medicos: tenta `GET /listar-medicos` e usa Supabase REST `doctors` como fallback.
|
- `POST /rest/v1/patients` em `patientRepository.create`
|
||||||
|
- `PATCH /rest/v1/patients?id=eq.ID` em `patientRepository.update`
|
||||||
|
- `DELETE /rest/v1/patients?id=eq.ID` em `patientRepository.remove`
|
||||||
|
- `POST /functions/v1/create-patient` em `patientRepository.createWithValidation`
|
||||||
|
- `POST /functions/v1/register-patient` em `patientRepository.registerPublic`
|
||||||
|
|
||||||
- **Mensageria**
|
## Medicos
|
||||||
- Enviar SMS: tenta `POST /enviar-sms-via-twilio` e usa Edge Function `send-sms` como fallback.
|
|
||||||
- O formulario agora coleta telefone quando o canal selecionado e SMS.
|
|
||||||
|
|
||||||
- **Storage**
|
- `GET /rest/v1/doctors` em `professionalRepository.getAll`
|
||||||
- Upload de avatar: tenta `/upload-avatar` e usa Supabase Storage no bucket `avatars` como fallback.
|
- `POST /functions/v1/create-doctor` em `professionalRepository.create`
|
||||||
- A tela de perfil atualiza a imagem exibida apos upload bem-sucedido.
|
|
||||||
|
|
||||||
## Ainda sem endpoint consolidado documentado
|
## Agendamentos
|
||||||
|
|
||||||
- Dashboard / Inicio (`HomePage` / `homeRepository.js`).
|
- `GET /rest/v1/appointments` em `appointmentRepository.getAll`
|
||||||
- Estatisticas e BI (`AnalyticsPage` / `analyticsRepository.js`).
|
- `POST /rest/v1/appointments` em `appointmentRepository.create`
|
||||||
- Prontuarios especificos separados de laudos (`MedicalRecordsPage` / `medicalRecordRepository.js`).
|
- `PATCH /rest/v1/appointments?id=eq.ID` em `appointmentRepository.update`
|
||||||
- Consultas isoladas fora de agendamento (`VisitsPage` / `visitRepository.js`).
|
- Cancelamento via `PATCH /rest/v1/appointments?id=eq.ID` em `appointmentRepository.cancel`
|
||||||
- Configuracoes gerais do tenant (`SettingsPage` / `settingsRepository.js`).
|
|
||||||
|
## Disponibilidade e Slots
|
||||||
|
|
||||||
|
- `GET /rest/v1/doctor_availability` em `availabilityRepository.getAll`
|
||||||
|
- `POST /rest/v1/doctor_availability` em `availabilityRepository.create`
|
||||||
|
- `PATCH /rest/v1/doctor_availability?id=eq.ID` em `availabilityRepository.update`
|
||||||
|
- `DELETE /rest/v1/doctor_availability?id=eq.ID` em `availabilityRepository.remove`
|
||||||
|
- `GET /rest/v1/doctor_exceptions` em `availabilityRepository.getExceptions`
|
||||||
|
- `POST /rest/v1/doctor_exceptions` em `availabilityRepository.createException`
|
||||||
|
- `POST /functions/v1/get-available-slots` em `availabilityRepository.getAvailableSlots`
|
||||||
|
|
||||||
|
## Reports / Laudos Medicos
|
||||||
|
|
||||||
|
- `GET /rest/v1/reports` em `reportRepository.getInitialReports`
|
||||||
|
- `POST /rest/v1/reports` em `reportRepository.create`
|
||||||
|
- `PATCH /rest/v1/reports?id=eq.ID` em `reportRepository.update`
|
||||||
|
|
||||||
|
## SMS / Comunicacao
|
||||||
|
|
||||||
|
- `POST /functions/v1/send-sms` em `communicationRepository.sendSms`
|
||||||
|
|
||||||
|
## Storage
|
||||||
|
|
||||||
|
- `POST /storage/v1/object/avatars/{path}` em `profileRepository.updateAvatar`
|
||||||
|
- `GET /storage/v1/object/avatars/{path}` em `profileRepository.downloadAvatar`
|
||||||
|
|
||||||
## Observacoes
|
## Observacoes
|
||||||
|
|
||||||
- `VITE_API_BASE_URL` define a base dos endpoints nomeados da API. Quando nao informado, o front usa `VITE_SUPABASE_FUNCTIONS_URL`.
|
- O Supabase real responde as rotas REST em `/rest/v1/...`.
|
||||||
- Os reposititorios aceitam formatos de resposta comuns como arrays diretos ou objetos com chaves `data`, `reports`, `agendamentos`, `medicos` etc.
|
- As Edge Functions reais respondem em `/functions/v1/...`.
|
||||||
- Os fallbacks existem para manter o front funcional em ambientes onde parte das Edge Functions ainda nao foi publicada.
|
- Algumas rotas curtas do Apidog retornam `404` no ambiente real; o codigo usa o caminho que respondeu em producao.
|
||||||
|
- `Schemas` no Apidog nao sao endpoints executaveis, apenas contratos de dados.
|
||||||
|
|||||||
@@ -60,6 +60,20 @@ export const authRepository = {
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async sendMagicLink(email) {
|
||||||
|
const response = await fetch(`${apiConfig.supabaseUrl}/auth/v1/otp`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAnonHeaders(),
|
||||||
|
body: JSON.stringify({ email: email?.trim() }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(await getResponseError(response, 'Erro ao enviar Magic Link.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
async getUser() {
|
async getUser() {
|
||||||
const apiResponse = await fetch(`${apiConfig.functionsUrl.replace(/\/+$/, '')}/user-info`, {
|
const apiResponse = await fetch(`${apiConfig.functionsUrl.replace(/\/+$/, '')}/user-info`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { apiConfig, getAuthenticatedHeaders } from '../config/api.js'
|
import { apiConfig, getAnonHeaders, getAuthenticatedHeaders } from '../config/api.js'
|
||||||
import { getResponseError } from './repositoryUtils.js'
|
import { getResponseError } from './repositoryUtils.js'
|
||||||
|
|
||||||
export const patientRepository = {
|
export const patientRepository = {
|
||||||
@@ -83,6 +83,29 @@ export const patientRepository = {
|
|||||||
return response.json()
|
return response.json()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async registerPublic(data) {
|
||||||
|
const body = cleanPayload({
|
||||||
|
full_name: data.name || data.full_name,
|
||||||
|
cpf: data.cpf,
|
||||||
|
email: data.email,
|
||||||
|
phone_mobile: data.phone || data.phone_mobile,
|
||||||
|
birth_date: data.birthDate || data.birth_date || null,
|
||||||
|
redirect_url: data.redirectUrl || data.redirect_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${apiConfig.functionsUrl}/register-patient`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAnonHeaders(),
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(await getResponseError(response, 'Erro ao realizar auto-cadastro de paciente.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
},
|
||||||
|
|
||||||
// 4. Atualizar paciente
|
// 4. Atualizar paciente
|
||||||
async update(patientId, data) {
|
async update(patientId, data) {
|
||||||
const body = {
|
const body = {
|
||||||
@@ -325,3 +348,9 @@ function calculateAge(birthDate) {
|
|||||||
|
|
||||||
return age
|
return age
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanPayload(payload) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(payload).filter(([, value]) => value !== undefined && value !== null && value !== ''),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { apiConfig, getAuthenticatedHeaders } from '../config/api.js'
|
import { apiConfig, getAuthenticatedHeaders } from '../config/api.js'
|
||||||
|
import { getResponseError, normalizeItem } from './repositoryUtils.js'
|
||||||
|
|
||||||
export const professionalRepository = {
|
export const professionalRepository = {
|
||||||
async getAll() {
|
async getAll() {
|
||||||
@@ -12,6 +13,29 @@ export const professionalRepository = {
|
|||||||
return (Array.isArray(data) ? data : []).map(mapProfessional)
|
return (Array.isArray(data) ? data : []).map(mapProfessional)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async create(data) {
|
||||||
|
const response = await fetch(`${apiConfig.functionsUrl}/create-doctor`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getAuthenticatedHeaders(),
|
||||||
|
body: JSON.stringify(cleanPayload({
|
||||||
|
full_name: data.fullName || data.full_name || data.name,
|
||||||
|
email: data.email,
|
||||||
|
cpf: data.cpf,
|
||||||
|
crm: data.crm,
|
||||||
|
crm_uf: data.crmUf || data.crm_uf,
|
||||||
|
phone_mobile: data.phoneMobile || data.phone_mobile || data.phone,
|
||||||
|
specialty: data.specialty || data.specialidade,
|
||||||
|
birth_date: data.birthDate || data.birth_date,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(await getResponseError(response, 'Erro ao criar médico.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapProfessional(normalizeItem(await response.json(), ['doctor']))
|
||||||
|
},
|
||||||
|
|
||||||
getCoverageMap() {
|
getCoverageMap() {
|
||||||
return {
|
return {
|
||||||
slots: ['08-12', '09-13', '10-15', '13-18', '08-14'],
|
slots: ['08-12', '09-13', '10-15', '13-18', '08-14'],
|
||||||
@@ -52,3 +76,9 @@ function mapProfessional(doctor) {
|
|||||||
function normalizeValue(value) {
|
function normalizeValue(value) {
|
||||||
return String(value || '').trim().toLowerCase()
|
return String(value || '').trim().toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanPayload(payload) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(payload).filter(([, value]) => value !== undefined && value !== null && value !== ''),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -84,6 +84,24 @@ export const profileRepository = {
|
|||||||
path: objectPath,
|
path: objectPath,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async downloadAvatar(path) {
|
||||||
|
const objectPath = String(path || '').replace(/^\/+/, '')
|
||||||
|
const response = await fetch(`${apiConfig.storageUrl}/object/avatars/${objectPath}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: getAuthenticatedHeaders({ 'Content-Type': undefined }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(await getResponseError(response, 'Falha ao baixar avatar.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
blob: await response.blob(),
|
||||||
|
contentType: response.headers.get('content-type') || 'application/octet-stream',
|
||||||
|
path: objectPath,
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeAvatarResponse(data) {
|
function normalizeAvatarResponse(data) {
|
||||||
|
|||||||
@@ -33,10 +33,9 @@ export const userRepository = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async getById(userId) {
|
async getById(userId) {
|
||||||
const response = await fetch(`${apiConfig.functionsUrl}/user-info-by-id`, {
|
const response = await fetch(`${apiConfig.functionsUrl}/user-info-by-id/${encodeURIComponent(userId)}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthenticatedHeaders(),
|
headers: getAuthenticatedHeaders(),
|
||||||
body: JSON.stringify({ user_id: userId }),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -83,7 +82,7 @@ export const userRepository = {
|
|||||||
const response = await fetch(`${apiConfig.functionsUrl}/delete-user`, {
|
const response = await fetch(`${apiConfig.functionsUrl}/delete-user`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getAuthenticatedHeaders(),
|
headers: getAuthenticatedHeaders(),
|
||||||
body: JSON.stringify({ user_id: userId }),
|
body: JSON.stringify({ userId, user_id: userId }),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user