Ir para o conteúdo

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

  1. Visão Geral
  2. Organização por Módulos
  3. Estrutura de Handlers
  4. Padrões de Resposta
  5. Tratamento de Erros
  6. 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/courses
  • GET /marketplace/courses/:id
  • GET /marketplace/enrollments/:enrollment_id/lessons/:lesson_id/playback (com rate limit)
  • POST /marketplace/professional/courses/:course_id/lessons/:lesson_id/video-upload-url
  • POST /marketplace/professional/courses/:course_id/lessons/:lesson_id/video-from-url
  • GET /marketplace/professional/courses/:course_id/lessons/:lesson_id/video-status
  • POST /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).


Referências