riseup-squad21/services/api/apiService.ts
2025-10-23 01:39:29 -03:00

221 lines
9.7 KiB
TypeScript

// apiService.ts (V4)
import {
AuthResponse, UserProfile, Patient, Doctor, Appointment, NewAppointmentPayload,
AvailableSlot, Report, ReportInput, DoctorAvailability, DoctorException,
NetworkError, LoginResponse, SendMagicLinkResponse, LogoutResponse,
GetCurrentUserResponse, RequestPasswordResetResponse, CreateUserWithPasswordResponse,
HardDeleteUserResponse, RegisterPatientResponse, GetAvailableSlotsResponse,
CreateAppointmentResponse, CancelAppointmentResponse, CreateReportResponse,
UpdateReportResponse, ListResponse
} from './types';
// Ação Futura: Mover estas chaves para variáveis de ambiente.
const BASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://yuanqfswhberkoevtmfr.supabase.co';
const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl1YW5xZnN3aGJlcmtvZXZ0bWZyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQ5NTQzNjksImV4cCI6MjA3MDUzMDM2OX0.g8Fm4XAvtX46zifBZnYVH4tVuQkqUH6Ia9CXQj4DztQ';
/**
* Cliente de API base que retorna uma união discriminada para todos os cenários.
*/
async function apiClient<T>(endpoint: string, options: RequestInit = {}, isPublic: boolean = false): Promise<{ status: number; data: T } | NetworkError> {
const headers = new Headers(options.headers || {});
headers.set('apikey', API_KEY);
headers.set('Content-Type', 'application/json');
if (!isPublic) {
try {
const authSession = localStorage.getItem('supabase.auth.token');
if (authSession) {
const session = JSON.parse(authSession);
// A estrutura do token pode variar, ajuste se necessário
const token = session?.currentSession?.access_token || session?.access_token;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
}
} catch (error) {
console.error("Falha ao ler o token de autenticação do localStorage.", error);
}
}
const config: RequestInit = { ...options, headers };
try {
const response = await fetch(`${BASE_URL}${endpoint}`, config);
if (response.status === 204) {
return { status: response.status, data: undefined as T };
}
const data = await response.json().catch(() => ({}));
return {
status: response.status,
data: data as T,
};
} catch (error) {
if (error instanceof Error) {
return { status: 'network_error', error };
}
return { status: 'network_error', error: new Error('Erro de rede desconhecido') };
}
}
// V4 CHANGE: Adicionamos uma asserção de tipo `as Promise<...>` em cada função.
// Isso informa ao TypeScript que confiamos que o retorno do apiClient corresponderá
// à união discriminada específica que definimos para cada endpoint.
// --- SERVIÇOS DE AUTENTICAÇÃO ---
export const authService = {
login: (credentials: { email: string; password: string }): Promise<LoginResponse> => {
return apiClient('/auth/v1/token?grant_type=password', {
method: 'POST',
body: JSON.stringify(credentials),
}, true) as Promise<LoginResponse>;
},
sendMagicLink: (email: string): Promise<SendMagicLinkResponse> => {
return apiClient('/auth/v1/otp', {
method: 'POST',
body: JSON.stringify({ email }),
}, true) as Promise<SendMagicLinkResponse>;
},
logout: (): Promise<LogoutResponse> => {
return apiClient('/auth/v1/logout', { method: 'POST' }) as Promise<LogoutResponse>;
},
getCurrentUser: (): Promise<GetCurrentUserResponse> => {
return apiClient('/functions/v1/user-info', { method: 'POST' }) as Promise<GetCurrentUserResponse>;
},
};
// --- SERVIÇOS DE USUÁRIOS ---
export const userService = {
requestPasswordReset: (email: string, redirectUrl?: string): Promise<RequestPasswordResetResponse> => {
return apiClient('/request-password-reset', {
method: 'POST',
body: JSON.stringify({ email, redirect_url: redirectUrl }),
}, true) as Promise<RequestPasswordResetResponse>;
},
createUserWithPassword: (payload: object): Promise<CreateUserWithPasswordResponse> => {
return apiClient('/create-user-with-password', {
method: 'POST',
body: JSON.stringify(payload),
}) as Promise<CreateUserWithPasswordResponse>;
},
hardDeleteUser_DANGEROUS: (userId: string): Promise<HardDeleteUserResponse> => {
return apiClient('/delete-user', {
method: 'POST',
body: JSON.stringify({ userId }),
}) as Promise<HardDeleteUserResponse>;
},
deactivateUser: (userId: string): Promise<any> => {
return apiClient(`/rest/v1/profiles?id=eq.${userId}`, {
method: 'PATCH',
headers: { Prefer: 'return=representation' },
body: JSON.stringify({ disabled: true }),
});
}
};
// --- SERVIÇOS DE PACIENTES ---
export const patientService = {
registerPatient: (payload: { email: string; full_name: string; phone_mobile: string; cpf: string; birth_date?: string }): Promise<RegisterPatientResponse> => {
return apiClient('/functions/v1/register-patient', {
method: 'POST',
body: JSON.stringify(payload),
}, true) as Promise<RegisterPatientResponse>;
},
list: (filters: { fullName?: string; cpf?: string; limit?: number; offset?: number } = {}): Promise<ListResponse<Patient>> => {
const query = new URLSearchParams();
if (filters.fullName) query.set('full_name', `ilike.*${filters.fullName}*`);
if (filters.cpf) query.set('cpf', `eq.${filters.cpf}`);
if (filters.limit) query.set('limit', String(filters.limit));
if (filters.offset) query.set('offset', String(filters.offset));
return apiClient(`/rest/v1/patients?${query.toString()}`) as Promise<ListResponse<Patient>>;
},
create: (payload: Omit<Patient, 'id' | 'created_at' | 'updated_at'>): Promise<any> => {
return apiClient('/rest/v1/patients', {
method: 'POST',
headers: { Prefer: 'return=representation' },
body: JSON.stringify(payload),
});
},
};
// --- SERVIÇOS DE MÉDICOS ---
export const doctorService = {
list: (filters: { specialty?: string; active?: boolean } = {}): Promise<ListResponse<Doctor>> => {
const query = new URLSearchParams({ select: '*' });
if (filters.specialty) query.set('specialty', `eq.${filters.specialty}`);
if (filters.active !== undefined) query.set('active', `eq.${filters.active}`);
return apiClient(`/rest/v1/doctors?${query.toString()}`) as Promise<ListResponse<Doctor>>;
},
};
// --- SERVIÇOS DE AGENDAMENTO E DISPONIBILIDADE ---
export const scheduleService = {
getAvailableSlots: (doctorId: string, date: string): Promise<GetAvailableSlotsResponse> => {
return apiClient('/functions/v1/get-available-slots', {
method: 'POST',
body: JSON.stringify({ doctor_id: doctorId, date }),
}) as Promise<GetAvailableSlotsResponse>;
},
createAppointment: (payload: NewAppointmentPayload): Promise<CreateAppointmentResponse> => {
return apiClient('/rest/v1/appointments', {
method: 'POST',
headers: { Prefer: 'return=representation' },
body: JSON.stringify(payload),
}) as Promise<CreateAppointmentResponse>;
},
listAppointments: (filters: { doctorId?: string; patientId?: string; status?: string }): Promise<ListResponse<Appointment>> => {
const query = new URLSearchParams();
if (filters.doctorId) query.set('doctor_id', `eq.${filters.doctorId}`);
if (filters.patientId) query.set('patient_id', `eq.${filters.patientId}`);
if (filters.status) query.set('status', `eq.${filters.status}`);
return apiClient(`/rest/v1/appointments?${query.toString()}`) as Promise<ListResponse<Appointment>>;
},
cancelAppointment: (appointmentId: string, reason: string): Promise<CancelAppointmentResponse> => {
return apiClient(`/rest/v1/appointments?id=eq.${appointmentId}`, {
method: 'PATCH',
headers: { Prefer: 'return=representation' },
body: JSON.stringify({
status: 'cancelled',
cancellation_reason: reason,
cancelled_at: new Date().toISOString(),
}),
}) as Promise<CancelAppointmentResponse>;
},
listAvailability: (filters: { doctorId?: string } = {}): Promise<ListResponse<DoctorAvailability>> => {
const query = new URLSearchParams();
if (filters.doctorId) query.set('doctor_id', `eq.${filters.doctorId}`);
return apiClient(`/rest/v1/doctor_availability?${query.toString()}`) as Promise<ListResponse<DoctorAvailability>>;
},
listExceptions: (filters: { doctorId?: string; date?: string } = {}): Promise<ListResponse<DoctorException>> => {
const query = new URLSearchParams();
if (filters.doctorId) query.set('doctor_id', `eq.${filters.doctorId}`);
if (filters.date) query.set('date', `eq.${filters.date}`);
return apiClient(`/rest/v1/doctor_exceptions?${query.toString()}`) as Promise<ListResponse<DoctorException>>;
},
};
// --- SERVIÇOS DE LAUDOS (REPORTS) ---
export const reportService = {
list: (filters: { patientId?: string; createdBy?: string }): Promise<ListResponse<Report>> => {
const query = new URLSearchParams({ order: 'created_at.desc' });
if (filters.patientId) query.set('patient_id', `eq.${filters.patientId}`);
if (filters.createdBy) query.set('created_by', `eq.${filters.createdBy}`);
return apiClient(`/rest/v1/reports?${query.toString()}`) as Promise<ListResponse<Report>>;
},
create: (payload: ReportInput): Promise<CreateReportResponse> => {
return apiClient('/rest/v1/reports', {
method: 'POST',
headers: { Prefer: 'return=representation' },
body: JSON.stringify(payload),
}) as Promise<CreateReportResponse>;
},
update: (reportId: string, payload: Partial<ReportInput>): Promise<UpdateReportResponse> => {
return apiClient(`/rest/v1/reports?id=eq.${reportId}`, {
method: 'PATCH',
headers: { Prefer: 'return=representation' },
body: JSON.stringify(payload),
}) as Promise<UpdateReportResponse>;
},
};