package profissionais import ( "net/http" "photum-backend/internal/db/generated" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" ) type Handler struct { service *Service } func NewHandler(service *Service) *Handler { return &Handler{service: service} } // ProfissionalResponse struct for Swagger and JSON response type ProfissionalResponse struct { ID string `json:"id"` UsuarioID string `json:"usuario_id"` Nome string `json:"nome"` FuncaoProfissional string `json:"funcao_profissional"` // Now returns name from join FuncaoProfissionalID string `json:"funcao_profissional_id"` Endereco *string `json:"endereco"` Cidade *string `json:"cidade"` Uf *string `json:"uf"` Whatsapp *string `json:"whatsapp"` CpfCnpjTitular *string `json:"cpf_cnpj_titular"` Banco *string `json:"banco"` Agencia *string `json:"agencia"` ContaPix *string `json:"conta_pix"` CarroDisponivel *bool `json:"carro_disponivel"` TemEstudio *bool `json:"tem_estudio"` QtdEstudio *int `json:"qtd_estudio"` TipoCartao *string `json:"tipo_cartao"` Observacao *string `json:"observacao"` QualTec *int `json:"qual_tec"` EducacaoSimpatia *int `json:"educacao_simpatia"` DesempenhoEvento *int `json:"desempenho_evento"` DispHorario *int `json:"disp_horario"` Media *float64 `json:"media"` TabelaFree *string `json:"tabela_free"` ExtraPorEquipamento *bool `json:"extra_por_equipamento"` Equipamentos *string `json:"equipamentos"` Email *string `json:"email"` AvatarURL *string `json:"avatar_url"` } func toResponse(p interface{}) ProfissionalResponse { // Handle different types returned by queries (Create returns CadastroProfissionai, List/Get returns Row with join) // This is a bit hacky, ideally we'd have a unified model or separate response mappers. // For now, let's check type. switch v := p.(type) { case generated.CadastroProfissionai: return ProfissionalResponse{ ID: uuid.UUID(v.ID.Bytes).String(), UsuarioID: uuid.UUID(v.UsuarioID.Bytes).String(), Nome: v.Nome, FuncaoProfissionalID: uuid.UUID(v.FuncaoProfissionalID.Bytes).String(), // FuncaoProfissional name is not available in simple insert return without extra query or join FuncaoProfissional: "", Endereco: fromPgText(v.Endereco), Cidade: fromPgText(v.Cidade), Uf: fromPgText(v.Uf), Whatsapp: fromPgText(v.Whatsapp), CpfCnpjTitular: fromPgText(v.CpfCnpjTitular), Banco: fromPgText(v.Banco), Agencia: fromPgText(v.Agencia), ContaPix: fromPgText(v.ContaPix), CarroDisponivel: fromPgBool(v.CarroDisponivel), TemEstudio: fromPgBool(v.TemEstudio), QtdEstudio: fromPgInt4(v.QtdEstudio), TipoCartao: fromPgText(v.TipoCartao), Observacao: fromPgText(v.Observacao), QualTec: fromPgInt4(v.QualTec), EducacaoSimpatia: fromPgInt4(v.EducacaoSimpatia), DesempenhoEvento: fromPgInt4(v.DesempenhoEvento), DispHorario: fromPgInt4(v.DispHorario), Media: fromPgNumeric(v.Media), TabelaFree: fromPgText(v.TabelaFree), ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento), Equipamentos: fromPgText(v.Equipamentos), Email: fromPgText(v.Email), AvatarURL: fromPgText(v.AvatarUrl), } case generated.ListProfissionaisRow: email := fromPgText(v.Email) if email == nil { email = fromPgText(v.UsuarioEmail) } return ProfissionalResponse{ ID: uuid.UUID(v.ID.Bytes).String(), UsuarioID: uuid.UUID(v.UsuarioID.Bytes).String(), Nome: v.Nome, FuncaoProfissionalID: uuid.UUID(v.FuncaoProfissionalID.Bytes).String(), FuncaoProfissional: v.FuncaoNome.String, // From join Endereco: fromPgText(v.Endereco), Cidade: fromPgText(v.Cidade), Uf: fromPgText(v.Uf), Whatsapp: fromPgText(v.Whatsapp), CpfCnpjTitular: fromPgText(v.CpfCnpjTitular), Banco: fromPgText(v.Banco), Agencia: fromPgText(v.Agencia), ContaPix: fromPgText(v.ContaPix), CarroDisponivel: fromPgBool(v.CarroDisponivel), TemEstudio: fromPgBool(v.TemEstudio), QtdEstudio: fromPgInt4(v.QtdEstudio), TipoCartao: fromPgText(v.TipoCartao), Observacao: fromPgText(v.Observacao), QualTec: fromPgInt4(v.QualTec), EducacaoSimpatia: fromPgInt4(v.EducacaoSimpatia), DesempenhoEvento: fromPgInt4(v.DesempenhoEvento), DispHorario: fromPgInt4(v.DispHorario), Media: fromPgNumeric(v.Media), TabelaFree: fromPgText(v.TabelaFree), ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento), Equipamentos: fromPgText(v.Equipamentos), Email: email, AvatarURL: fromPgText(v.AvatarUrl), } case generated.GetProfissionalByIDRow: return ProfissionalResponse{ ID: uuid.UUID(v.ID.Bytes).String(), UsuarioID: uuid.UUID(v.UsuarioID.Bytes).String(), Nome: v.Nome, FuncaoProfissionalID: uuid.UUID(v.FuncaoProfissionalID.Bytes).String(), FuncaoProfissional: v.FuncaoNome.String, // From join Endereco: fromPgText(v.Endereco), Cidade: fromPgText(v.Cidade), Uf: fromPgText(v.Uf), Whatsapp: fromPgText(v.Whatsapp), CpfCnpjTitular: fromPgText(v.CpfCnpjTitular), Banco: fromPgText(v.Banco), Agencia: fromPgText(v.Agencia), ContaPix: fromPgText(v.ContaPix), CarroDisponivel: fromPgBool(v.CarroDisponivel), TemEstudio: fromPgBool(v.TemEstudio), QtdEstudio: fromPgInt4(v.QtdEstudio), TipoCartao: fromPgText(v.TipoCartao), Observacao: fromPgText(v.Observacao), QualTec: fromPgInt4(v.QualTec), EducacaoSimpatia: fromPgInt4(v.EducacaoSimpatia), DesempenhoEvento: fromPgInt4(v.DesempenhoEvento), DispHorario: fromPgInt4(v.DispHorario), Media: fromPgNumeric(v.Media), TabelaFree: fromPgText(v.TabelaFree), ExtraPorEquipamento: fromPgBool(v.ExtraPorEquipamento), Equipamentos: fromPgText(v.Equipamentos), Email: fromPgText(v.Email), AvatarURL: fromPgText(v.AvatarUrl), } default: return ProfissionalResponse{} } } // Helpers for conversion func fromPgText(t pgtype.Text) *string { if !t.Valid { return nil } return &t.String } func fromPgBool(b pgtype.Bool) *bool { if !b.Valid { return nil } return &b.Bool } func fromPgInt4(i pgtype.Int4) *int { if !i.Valid { return nil } val := int(i.Int32) return &val } func fromPgNumeric(n pgtype.Numeric) *float64 { if !n.Valid { return nil } f, _ := n.Float64Value() val := f.Float64 return &val } // Create godoc // @Summary Create a new profissional // @Description Create a new profissional record // @Tags profissionais // @Accept json // @Produce json // @Security BearerAuth // @Param request body CreateProfissionalInput true "Create Profissional Request" // @Success 201 {object} ProfissionalResponse // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/profissionais [post] func (h *Handler) Create(c *gin.Context) { var input CreateProfissionalInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } userID, exists := c.Get("userID") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"}) return } userIDStr, ok := userID.(string) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user id type in context"}) return } // Security: Only allow TargetUserID if user is ADMIN or OWNER if input.TargetUserID != nil && *input.TargetUserID != "" { userRole, exists := c.Get("role") if !exists { // Should validation fail? Or just ignore target? // Safer to ignore target user ID if role not found input.TargetUserID = nil } else { roleStr, ok := userRole.(string) if !ok || (roleStr != "SUPERADMIN" && roleStr != "BUSINESS_OWNER") { input.TargetUserID = nil } } } prof, err := h.service.Create(c.Request.Context(), userIDStr, input) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, toResponse(*prof)) } // List godoc // @Summary List profissionais // @Description List all profissionais // @Tags profissionais // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {array} ProfissionalResponse // @Failure 500 {object} map[string]string // @Router /api/profissionais [get] func (h *Handler) List(c *gin.Context) { profs, err := h.service.List(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } var response []ProfissionalResponse for _, p := range profs { response = append(response, toResponse(p)) } c.JSON(http.StatusOK, response) } // Get godoc // @Summary Get profissional by ID // @Description Get a profissional by ID // @Tags profissionais // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "Profissional ID" // @Success 200 {object} ProfissionalResponse // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/profissionais/{id} [get] func (h *Handler) Get(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "id required"}) return } prof, err := h.service.GetByID(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, toResponse(*prof)) } // Update godoc // @Summary Update profissional // @Description Update a profissional by ID // @Tags profissionais // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "Profissional ID" // @Param request body UpdateProfissionalInput true "Update Profissional Request" // @Success 200 {object} ProfissionalResponse // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/profissionais/{id} [put] func (h *Handler) Update(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "id required"}) return } var input UpdateProfissionalInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } prof, err := h.service.Update(c.Request.Context(), id, input) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, toResponse(*prof)) } // Delete godoc // @Summary Delete profissional // @Description Delete a profissional by ID // @Tags profissionais // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "Profissional ID" // @Success 200 {object} map[string]string // @Failure 400 {object} map[string]string // @Failure 500 {object} map[string]string // @Router /api/profissionais/{id} [delete] func (h *Handler) Delete(c *gin.Context) { id := c.Param("id") if id == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "id required"}) return } err := h.service.Delete(c.Request.Context(), id) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "deleted"}) }