Backend - Handlers¶
Este documento descreve a estrutura e organização dos handlers HTTP do backend, incluindo padrões de resposta, tratamento de erros e organização por módulos.
Índice¶
- Visão Geral
- Organização por Módulos
- Estrutura de Handlers
- Padrões de Resposta
- Tratamento de Erros
- Registro de Rotas
Visão Geral¶
Handlers são a camada de apresentação HTTP do backend. Eles são responsáveis por:
- Receber requisições HTTP
- Validar formato de entrada (JSON, parâmetros)
- Extrair dados do contexto (user_id, user_type)
- Chamar services apropriados
- Retornar respostas HTTP (JSON)
- Tratar erros HTTP
Localização atual: internal/modules/** (com bootstrap em internal/http/bootstrap/routes.go)
Framework: Echo v4
Organização por Módulos¶
Handlers são organizados em módulos por contexto de negócio:
internal/modules/
├── auth/ # autenticação e OAuth
├── admin/ # administração e operação
├── client/ # fluxos de cliente
│ └── courses/ # catálogo, matrículas, playback, editor (profissional) e webhooks marketplace
├── professional/ # perfil, pacientes, financeiro, disponibilidade
├── general/ # ciclos, observações, mensagens, consultas
├── plans/ # planos
└── module_access/ # controle de módulos por usuário
Atualização 2026-03-09 (Cursos e Vídeo)¶
Rotas reais de cursos/vídeo ficam em internal/modules/marketplace/courses/handler.go e nas subpastas por ator (client|professional|admin|webhooks), incluindo:
GET /marketplace/coursesGET /marketplace/courses/:idGET /marketplace/enrollments/:enrollment_id/lessons/:lesson_id/playback(com rate limit)POST /marketplace/professional/courses/:course_id/lessons/:lesson_id/video-upload-urlPOST /marketplace/professional/courses/:course_id/lessons/:lesson_id/video-from-urlGET /marketplace/professional/courses/:course_id/lessons/:lesson_id/video-statusPOST /marketplace/webhooks/mux
Pipeline Mux:
- upload/import inicia video_status=processing;
- webhook processa eventos idempotentes;
- video.asset.ready promove aula para ready e define mux_playback_id.
Estrutura de Handlers¶
Padrão de Handler¶
Cada handler segue uma estrutura padrão:
type Handler struct {
service ServiceInterface
}
func NewHandler(service ServiceInterface) *Handler {
return &Handler{service: service}
}
func (h *Handler) GetX(c echo.Context) error {
// 1. Extrair dados do contexto
userID := c.Get("user_id").(uint)
// 2. Extrair parâmetros (se necessário)
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID")
}
// 3. Chamar service
result, err := h.service.GetX(uint(id), userID)
if err != nil {
return handleError(err)
}
// 4. Retornar resposta
return c.JSON(http.StatusOK, result)
}
Extração de Dados do Contexto¶
Dados adicionados pelo middleware de autenticação:
userID := c.Get("user_id").(uint)
userType := c.Get("user_type").(string)
email := c.Get("email").(string)
Verificação Segura:
userID, ok := c.Get("user_id").(uint)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "User ID not found")
}
Bind de Dados¶
Para requisições POST/PUT, usar c.Bind():
var dto CreateRequestDTO
if err := c.Bind(&dto); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid request body")
}
Query Parameters¶
search := c.QueryParam("search")
page, _ := strconv.Atoi(c.QueryParam("page"))
limit, _ := strconv.Atoi(c.QueryParam("limit"))
Padrões de Resposta¶
Sucesso¶
200 OK:
return c.JSON(http.StatusOK, map[string]interface{}{
"data": result,
})
201 Created:
return c.JSON(http.StatusCreated, map[string]interface{}{
"id": result.ID,
"message": "Resource created successfully",
})
204 No Content:
return c.NoContent(http.StatusNoContent)
Estrutura de Resposta Padrão¶
Alguns handlers retornam dados diretamente:
return c.JSON(http.StatusOK, result)
Outros seguem estrutura padronizada:
return c.JSON(http.StatusOK, map[string]interface{}{
"data": result,
})
Tratamento de Erros¶
Função Helper de Erro¶
func handleError(err error) *echo.HTTPError {
// Erros específicos do sistema
if err.Error() == "INVALID_CREDENTIALS" {
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
}
if err.Error() == "USER_INACTIVE" {
return echo.NewHTTPError(http.StatusForbidden, "User is inactive")
}
if err.Error() == "EMAIL_NOT_VERIFIED" {
return echo.NewHTTPError(http.StatusForbidden, "Email not verified")
}
// Erro genérico
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
Códigos HTTP Comuns¶
| Código | Uso |
|---|---|
200 |
Sucesso (GET, PUT) |
201 |
Criado (POST) |
204 |
Sem conteúdo (DELETE) |
400 |
Bad Request (dados inválidos) |
401 |
Unauthorized (não autenticado) |
403 |
Forbidden (sem permissão) |
404 |
Not Found (recurso não encontrado) |
500 |
Internal Server Error (erro do servidor) |
Exemplos de Tratamento¶
Validação de Entrada:
if dto.Email == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Email is required")
}
Recurso Não Encontrado:
if errors.Is(err, gorm.ErrRecordNotFound) {
return echo.NewHTTPError(http.StatusNotFound, "Resource not found")
}
Erro de Permissão:
if cycle.UserID != userID {
return echo.NewHTTPError(http.StatusForbidden, "Access denied")
}
Registro de Rotas¶
Estrutura de Registro¶
Rotas são registradas em routes.go de forma hierárquica:
func RegisterRoutes(e *echo.Echo) {
// Rotas públicas
e.GET("/health", HealthCheck)
authRoutes.RegisterRoutes(e)
// Rotas protegidas
api := e.Group("/api")
api.Use(authMiddleware.AuthMiddleware())
// Rotas por módulo
generalRoutes.RegisterRoutes(api)
clientRoutes.RegisterRoutes(api)
professionalRoutes.RegisterRoutes(api)
adminRoutes.RegisterRoutes(api)
}
Rotas Públicas¶
Não requerem autenticação:
/health- Health check/docs- Documentação Swagger/api/auth/*- Autenticação (login, register, etc.)/api/plans(GET) - Listagem pública de planos
Rotas Protegidas¶
Requerem autenticação (JWT):
- Todas as rotas em
/api/*(exceto rotas públicas) - Middleware de autenticação aplicado em
api := e.Group("/api")
Rotas por Role¶
Admin:
adminRoutes := api.Group("")
adminRoutes.Use(role.RoleMiddleware([]string{"admin"}))
Admin ou Professional:
usersRoutes := api.Group("")
usersRoutes.Use(role.RoleMiddleware([]string{"admin", "professional"}))
usersRoutes.Use(profileMiddleware.ProfileApprovalMiddleware())
Professional Aprovado:
approvedRoutes := api.Group("")
approvedRoutes.Use(role.RoleMiddleware([]string{"professional"}))
approvedRoutes.Use(profileMiddleware.ProfileApprovalMiddleware())
Módulos de Handlers¶
Auth Handlers¶
Localização: internal/handlers/auth/
Rotas:
- POST /api/auth/login - Login
- POST /api/auth/register - Registro
- POST /api/auth/refresh - Refresh token
- POST /api/auth/forgot-password - Solicitar reset
- POST /api/auth/reset-password - Redefinir senha
- POST /api/auth/verify-email - Verificar email
- POST /api/auth/resend-verification - Reenviar verificação
- POST /api/auth/oauth/:provider - OAuth (Google, Apple)
- POST /api/auth/oauth/associate/:provider - Associar provedor
- DELETE /api/auth/oauth/disassociate/:provider - Desassociar provedor
Handlers:
- auth.go - Autenticação tradicional
- oauth.go - OAuth (Google, Apple)
Admin Handlers¶
Localização: internal/handlers/admin/
Rotas:
- GET /api/users - Listar usuários
- GET /api/users/:id - Obter usuário
- PUT /api/users/:id - Atualizar usuário
- DELETE /api/users/:id - Deletar usuário
- GET /api/users/:id/profile - Perfil completo
- GET /api/profiles/pending - Perfis pendentes
- POST /api/profiles/:id/approve - Aprovar perfil
- POST /api/profiles/:id/reject - Rejeitar perfil
- GET /api/admin/billings/sensations - Listar sensações
- POST /api/admin/billings/sensations - Criar sensação
- GET /api/admin/billings/appearances - Listar aparências
- POST /api/admin/billings/appearances - Criar aparência
- GET /api/admin/billings/symbols - Listar símbolos
- POST /api/admin/billings/symbols - Criar símbolo
- GET /api/admin/billings/day-based-rules - Listar regras
- POST /api/admin/billings/day-based-rules - Criar regra
- GET /api/admin/billings/settings - Configurações
- PUT /api/admin/billings/settings - Atualizar configurações
Handlers:
- users.go - Gerenciamento de usuários
- billings_symbols.go - Símbolos do método
- sensations.go - Sensações
- appearances.go - Aparências
- day_based_rules.go - Regras baseadas em dias
- settings.go - Configurações
Permissões: Apenas admin
Client Handlers¶
Localização: internal/handlers/client/
Rotas:
- GET /api/client/profile - Obter perfil
- POST /api/client/profile - Criar perfil
- PUT /api/client/profile - Atualizar perfil
- GET /api/client/statistics - Estatísticas (dashboard)
- GET /api/client/fertility-status - Status de fertilidade
- GET /api/client/orientador - Profissional vinculado
- POST /api/client/link-professional - Vincular profissional
- POST /api/client/unlink-professional - Desvincular profissional
- POST /api/client/change-professional - Trocar profissional
- GET /api/professionals - Listar profissionais
- GET /api/professionals/search - Buscar profissionais
- GET /api/professionals/:id - Detalhes do profissional
- GET /api/v2/marketplace/courses - Listar cursos
- GET /api/v2/marketplace/courses/:id - Detalhes do curso
- POST /api/v2/marketplace/courses/:id/complete - Marcar curso como completo
- POST /api/payments - Criar pagamento
- GET /api/payments - Listar pagamentos
Handlers:
- client_profile.go - Perfil do cliente
- professionals.go - Busca de profissionais
- courses.go - Cursos educativos
- payments.go - Pagamentos
Permissões: Apenas client
Professional Handlers¶
Localização: internal/handlers/professional/
Rotas Gerais (Professional):
- GET /api/profile/status - Status do perfil
- GET /api/profile - Obter perfil
- POST /api/profile - Criar perfil
- PUT /api/profile - Atualizar perfil
- GET /api/professional/availability - Disponibilidade
- POST /api/professional/availability - Criar slot
- POST /api/professional/availability/bulk - Criar slots em massa
- PUT /api/professional/availability/:id - Atualizar slot
- DELETE /api/professional/availability/:id - Deletar slot
Rotas Aprovadas (Professional Aprovado):
- GET /api/patients - Listar pacientes
- GET /api/patients/:patient_id/profile - Perfil do paciente
- POST /api/patients - Criar vínculo
- DELETE /api/patients/:patient_id - Remover vínculo
- PUT /api/patients/:patient_id/woman-pattern - Atualizar PBI
- PUT /api/patients/:patient_id/approve - Aprovar vínculo
- PUT /api/patients/:patient_id/reject - Rejeitar vínculo
- GET /api/professional/financial-summary - Resumo financeiro
- GET /api/professional/bank-account - Conta bancária
- PUT /api/professional/bank-account - Atualizar conta
- GET /api/professional/payments - Pagamentos
- POST /api/upload/profile-photo - Upload foto de perfil
- POST /api/upload/document - Upload documento
Handlers:
- professional_profile.go - Perfil profissional
- professional_patient.go - Vínculos com pacientes
- availability.go - Disponibilidade
- professional_financial.go - Informações financeiras
- upload.go - Upload de arquivos
Permissões: professional (algumas requerem aprovação)
General Handlers¶
Localização: internal/handlers/general/
Rotas:
- GET /api/cycles - Listar ciclos
- GET /api/cycles/active - Ciclo ativo
- POST /api/cycles - Criar ciclo
- GET /api/cycles/:id - Obter ciclo
- PUT /api/cycles/:id - Atualizar ciclo
- DELETE /api/cycles/:id - Deletar ciclo
- GET /api/cycles/:cycle_id/comments - Comentários do ciclo
- POST /api/cycles/:cycle_id/comments - Criar comentário
- DELETE /api/cycles/:cycle_id/comments/:comment_id - Deletar comentário
- GET /api/observations - Listar observações
- POST /api/observations - Criar observação
- GET /api/observations/:id - Obter observação
- PUT /api/observations/:id - Atualizar observação
- DELETE /api/observations/:id - Deletar observação
- PUT /api/observations/:id/professional-notes - Notas profissionais
- GET /api/billings/sensations - Sensações (público)
- GET /api/billings/appearances - Aparências (público)
- GET /api/billings/symbols - Símbolos (público)
- GET /api/symbols/allowed - Símbolos permitidos
- GET /api/messages/conversations - Conversas
- GET /api/messages/:user_id - Mensagens com usuário
- POST /api/messages - Enviar mensagem
- PUT /api/messages/:id/read - Marcar como lida
- GET /api/messages/unread-count - Contagem não lidas
- GET /api/appointments - Listar agendamentos
- POST /api/appointments - Criar agendamento
- GET /api/appointments/:id - Obter agendamento
- PUT /api/appointments/:id/cancel - Cancelar agendamento
- POST /api/appointments/:id/confirm - Confirmar agendamento
- POST /api/appointments/:id/reject - Rejeitar agendamento
- POST /api/appointments/:id/video-call - Videochamada
- GET /api/notifications - Notificações
- PUT /api/notifications/:id/read - Marcar como lida
- GET /api/accounts - Listar contas
- POST /api/accounts - Criar conta
- GET /api/accounts/:id - Obter conta
- PUT /api/accounts/:id - Atualizar conta
- DELETE /api/accounts/:id - Deletar conta
- GET /api/subscriptions - Listar assinaturas
- POST /api/subscriptions - Criar assinatura
- GET /api/subscriptions/:id - Obter assinatura
- PUT /api/subscriptions/:id - Atualizar assinatura
- DELETE /api/subscriptions/:id - Deletar assinatura
- GET /api/work-schedule - Horário de trabalho
- POST /api/work-schedule - Criar/atualizar horário
- PUT /api/work-schedule - Criar/atualizar horário
- POST /api/sync/push - Sincronização push (mobile)
- GET /api/sync/pull - Sincronização pull (mobile)
Handlers:
- cycles.go - Ciclos menstruais
- observations.go - Observações diárias
- cycle_comments.go - Comentários em ciclos
- messages.go - Mensagens
- appointments.go - Agendamentos
- notifications.go - Notificações
- subscriptions.go - Assinaturas
- accounts/accounts.go - Contas financeiras
- billings_data.go - Dados do método (sensações, aparências, símbolos)
- symbols.go - Símbolos permitidos
- work_schedule.go - Horário de trabalho
- sync.go - Sincronização mobile
Permissões: Qualquer usuário autenticado (algumas rotas têm restrições adicionais)
Plans Handlers¶
Localização: internal/handlers/plans/
Rotas Públicas:
- GET /api/plans - Listar planos (público)
Rotas Admin:
- POST /api/plans - Criar plano
- PUT /api/plans/:id - Atualizar plano
- DELETE /api/plans/:id - Deletar plano
Handlers:
- plans.go - CRUD de planos
Inicialização de Handlers¶
Padrão de Inicialização¶
Handlers são inicializados com suas dependências:
// Repository
accountRepo := accountRepo.NewGormAccountRepository(database.DB)
// Service
accountService := accountService.NewAccountService(accountRepo)
// Handler
accountHandler := accounts.NewHandler(accountService)
// Registrar rotas
api.GET("/accounts", accountHandler.GetAccounts)
Evitar Ciclos de Importação¶
Alguns handlers são registrados diretamente em routes.go para evitar ciclos de importação:
// Em routes.go
profileRepo := profRepo.NewGormProfessionalProfileRepository(database.DB)
userRepo := authRepo.NewGormAuthRepository(database.DB)
profileService := profService.NewProfessionalProfileService(profileRepo, userRepo)
profileHandler := professionalRoutes.NewProfessionalProfileHandler(profileService)
api.GET("/profile", profileHandler.GetProfessionalProfile)
Storage (R2)¶
Handlers que precisam de storage (upload de arquivos) usam r2Storage:
// Configurado em cmd/api/main.go
handlers.SetR2Storage(r2Storage)
// Usado em handlers
r2Storage := handlers.GetR2Storage()
url, err := r2Storage.UploadFile(file, "profiles/", filename)
Handlers que usam R2:
- professional/upload.go - Upload de fotos e documentos
- client/client_profile.go - Upload de foto de perfil
- admin/ - Upload de imagens de símbolos (futuro)
Testes¶
Handlers têm testes em arquivos *_test.go:
Exemplos:
- auth/auth_test.go
- client/client_profile_test.go
- professional/professional_patient_test.go
- general/cycles_test.go
Padrão de Teste:
func TestHandler_GetX(t *testing.T) {
// Setup
// Mock service
// Test cases
// Assertions
}
Boas Práticas¶
1. Validação de Entrada¶
Sempre validar dados de entrada:
if dto.Email == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Email is required")
}
2. Extração Segura do Contexto¶
Sempre verificar se dados existem no contexto:
userID, ok := c.Get("user_id").(uint)
if !ok {
return echo.NewHTTPError(http.StatusUnauthorized, "User ID not found")
}
3. Tratamento de Erros¶
Tratar erros específicos e retornar códigos HTTP apropriados:
if errors.Is(err, gorm.ErrRecordNotFound) {
return echo.NewHTTPError(http.StatusNotFound, "Resource not found")
}
4. Não Implementar Lógica de Negócio¶
Handlers devem apenas: - Validar formato - Chamar services - Retornar respostas
❌ ERRADO:
func (h *Handler) CreateCycle(c echo.Context) error {
// Lógica de negócio no handler
if cycle.StartDate.Before(time.Now().AddDate(0, 0, -30)) {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid date")
}
// ...
}
✅ CORRETO:
func (h *Handler) CreateCycle(c echo.Context) error {
var dto CreateCycleDTO
if err := c.Bind(&dto); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid request")
}
cycle, err := h.service.CreateCycle(userID, dto)
if err != nil {
return handleError(err)
}
return c.JSON(http.StatusCreated, cycle)
}
5. Documentação¶
Handlers devem estar documentados no OpenAPI (openapi.yaml).