diff --git a/docs/repository-api-audit.md b/docs/repository-api-audit.md index 4943b59..3f780e8 100644 --- a/docs/repository-api-audit.md +++ b/docs/repository-api-audit.md @@ -1,49 +1,73 @@ # 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** - - Login com email e senha via Supabase Auth (`/auth/v1/token`). - - Solicitar reset de senha: tenta `/solicitar-reset-de-senha` e usa `/auth/v1/recover` como fallback. - - Dados do usuario autenticado: tenta `/informacoes-do-usuario-autenticado` e usa `/auth/v1/user` como fallback. - - Logout: tenta `/logout`, usa `/auth/v1/logout` como fallback e sempre limpa a sessao local. +- `POST /auth/v1/token?grant_type=password` em `authRepository.login` +- `POST /auth/v1/otp` em `authRepository.sendMagicLink` +- `POST /functions/v1/request-password-reset` em `authRepository.requestPasswordReset` +- `GET /auth/v1/user` em `authRepository.getUser` como fallback +- `POST /functions/v1/user-info` em `authRepository.getUser` +- `POST /auth/v1/logout` em `authRepository.logout` -- **Pacientes** - - Listar, criar, atualizar e deletar pacientes via Supabase REST. - - Criar paciente com validacao via Edge Function quando disponivel. +## Usuarios -- **Agendamentos** - - Listar agendamentos: tenta `GET /agendamentos` e usa Supabase REST `appointments` como fallback. - - Criar agendamento: tenta `POST /agendamentos` e usa Supabase REST `appointments` como fallback. +- `POST /functions/v1/create-user` em `userRepository.create` +- `POST /functions/v1/create-user-with-password` em `userRepository.createWithPassword` +- `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** - - 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. +## Pacientes -- **Medicos / Profissionais** - - Listar medicos: tenta `GET /listar-medicos` e usa Supabase REST `doctors` como fallback. +- `GET /rest/v1/patients` em `patientRepository.getAll` +- `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** - - 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. +## Medicos -- **Storage** - - Upload de avatar: tenta `/upload-avatar` e usa Supabase Storage no bucket `avatars` como fallback. - - A tela de perfil atualiza a imagem exibida apos upload bem-sucedido. +- `GET /rest/v1/doctors` em `professionalRepository.getAll` +- `POST /functions/v1/create-doctor` em `professionalRepository.create` -## Ainda sem endpoint consolidado documentado +## Agendamentos -- Dashboard / Inicio (`HomePage` / `homeRepository.js`). -- Estatisticas e BI (`AnalyticsPage` / `analyticsRepository.js`). -- Prontuarios especificos separados de laudos (`MedicalRecordsPage` / `medicalRecordRepository.js`). -- Consultas isoladas fora de agendamento (`VisitsPage` / `visitRepository.js`). -- Configuracoes gerais do tenant (`SettingsPage` / `settingsRepository.js`). +- `GET /rest/v1/appointments` em `appointmentRepository.getAll` +- `POST /rest/v1/appointments` em `appointmentRepository.create` +- `PATCH /rest/v1/appointments?id=eq.ID` em `appointmentRepository.update` +- Cancelamento via `PATCH /rest/v1/appointments?id=eq.ID` em `appointmentRepository.cancel` + +## 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 -- `VITE_API_BASE_URL` define a base dos endpoints nomeados da API. Quando nao informado, o front usa `VITE_SUPABASE_FUNCTIONS_URL`. -- Os reposititorios aceitam formatos de resposta comuns como arrays diretos ou objetos com chaves `data`, `reports`, `agendamentos`, `medicos` etc. -- Os fallbacks existem para manter o front funcional em ambientes onde parte das Edge Functions ainda nao foi publicada. +- O Supabase real responde as rotas REST em `/rest/v1/...`. +- As Edge Functions reais respondem em `/functions/v1/...`. +- 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. diff --git a/src/repositories/authRepository.js b/src/repositories/authRepository.js index 151f148..bd1f7ff 100644 --- a/src/repositories/authRepository.js +++ b/src/repositories/authRepository.js @@ -60,6 +60,20 @@ export const authRepository = { 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() { const apiResponse = await fetch(`${apiConfig.functionsUrl.replace(/\/+$/, '')}/user-info`, { method: 'POST', diff --git a/src/repositories/patientRepository.js b/src/repositories/patientRepository.js index 45cea0e..3869f01 100644 --- a/src/repositories/patientRepository.js +++ b/src/repositories/patientRepository.js @@ -1,4 +1,4 @@ -import { apiConfig, getAuthenticatedHeaders } from '../config/api.js' +import { apiConfig, getAnonHeaders, getAuthenticatedHeaders } from '../config/api.js' import { getResponseError } from './repositoryUtils.js' export const patientRepository = { @@ -83,6 +83,29 @@ export const patientRepository = { 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 async update(patientId, data) { const body = { @@ -325,3 +348,9 @@ function calculateAge(birthDate) { return age } + +function cleanPayload(payload) { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null && value !== ''), + ) +} diff --git a/src/repositories/professionalRepository.js b/src/repositories/professionalRepository.js index 4b35d30..e1d408f 100644 --- a/src/repositories/professionalRepository.js +++ b/src/repositories/professionalRepository.js @@ -1,4 +1,5 @@ import { apiConfig, getAuthenticatedHeaders } from '../config/api.js' +import { getResponseError, normalizeItem } from './repositoryUtils.js' export const professionalRepository = { async getAll() { @@ -12,6 +13,29 @@ export const professionalRepository = { 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() { return { slots: ['08-12', '09-13', '10-15', '13-18', '08-14'], @@ -52,3 +76,9 @@ function mapProfessional(doctor) { function normalizeValue(value) { return String(value || '').trim().toLowerCase() } + +function cleanPayload(payload) { + return Object.fromEntries( + Object.entries(payload).filter(([, value]) => value !== undefined && value !== null && value !== ''), + ) +} diff --git a/src/repositories/profileRepository.js b/src/repositories/profileRepository.js index c5a4520..038083b 100644 --- a/src/repositories/profileRepository.js +++ b/src/repositories/profileRepository.js @@ -84,6 +84,24 @@ export const profileRepository = { 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) { diff --git a/src/repositories/userRepository.js b/src/repositories/userRepository.js index dadc9b8..c87881a 100644 --- a/src/repositories/userRepository.js +++ b/src/repositories/userRepository.js @@ -33,10 +33,9 @@ export const userRepository = { }, 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', headers: getAuthenticatedHeaders(), - body: JSON.stringify({ user_id: userId }), }) if (!response.ok) { @@ -83,7 +82,7 @@ export const userRepository = { const response = await fetch(`${apiConfig.functionsUrl}/delete-user`, { method: 'POST', headers: getAuthenticatedHeaders(), - body: JSON.stringify({ user_id: userId }), + body: JSON.stringify({ userId, user_id: userId }), }) if (!response.ok) {