diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 3f14673..70e0e7c 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -163,6 +163,7 @@ func main() { { profGroup.POST("", profissionaisHandler.Create) profGroup.GET("", profissionaisHandler.List) + profGroup.GET("/me", profissionaisHandler.Me) profGroup.GET("/:id", profissionaisHandler.Get) profGroup.PUT("/:id", profissionaisHandler.Update) profGroup.DELETE("/:id", profissionaisHandler.Delete) diff --git a/backend/internal/profissionais/handler.go b/backend/internal/profissionais/handler.go index 867874c..8dd471b 100644 --- a/backend/internal/profissionais/handler.go +++ b/backend/internal/profissionais/handler.go @@ -159,6 +159,38 @@ func toResponse(p interface{}) ProfissionalResponse { Email: fromPgText(v.Email), AvatarURL: fromPgText(v.AvatarUrl), } + case generated.GetProfissionalByUsuarioIDRow: + 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: "", + Functions: toJSONRaw(v.Functions), + 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{} } @@ -298,6 +330,47 @@ func (h *Handler) List(c *gin.Context) { c.JSON(http.StatusOK, response) } +// Me godoc +// @Summary Get current authenticated profissional +// @Description Get the profissional profile associated with the current user +// @Tags profissionais +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} ProfissionalResponse +// @Failure 401 {object} map[string]string +// @Failure 404 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /api/profissionais/me [get] +func (h *Handler) Me(c *gin.Context) { + 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"}) + return + } + + prof, err := h.service.GetByUserID(c.Request.Context(), userIDStr) + if err != nil { + // Checks if error is "no rows in result set" -> 404 + if err.Error() == "no rows in result set" { + c.JSON(http.StatusNotFound, gin.H{"error": "professional profile not found"}) + return + } + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Reuse toResponse which handles different types via interface{} check + // We need to add case for GetProfissionalByUsuarioIDRow in toResponse function + c.JSON(http.StatusOK, toResponse(*prof)) +} + // Get godoc // @Summary Get profissional by ID // @Description Get a profissional by ID diff --git a/backend/internal/profissionais/service.go b/backend/internal/profissionais/service.go index b1a907e..46642c6 100644 --- a/backend/internal/profissionais/service.go +++ b/backend/internal/profissionais/service.go @@ -246,6 +246,20 @@ func (s *Service) Update(ctx context.Context, id string, input UpdateProfissiona return &prof, nil } +func (s *Service) GetByUserID(ctx context.Context, userID string) (*generated.GetProfissionalByUsuarioIDRow, error) { + uuidVal, err := uuid.Parse(userID) + if err != nil { + return nil, errors.New("invalid user id") + } + + prof, err := s.queries.GetProfissionalByUsuarioID(ctx, pgtype.UUID{Bytes: uuidVal, Valid: true}) + if err != nil { + return nil, err + } + + return &prof, nil +} + func (s *Service) Delete(ctx context.Context, id string) error { fmt.Printf("[DEBUG] Deleting Professional: %s\n", id) uuidVal, err := uuid.Parse(id) diff --git a/frontend/App.tsx b/frontend/App.tsx index cabe841..8dc3280 100644 --- a/frontend/App.tsx +++ b/frontend/App.tsx @@ -33,6 +33,7 @@ import { Button } from "./components/Button"; import { X } from "lucide-react"; import { ShieldAlert } from "lucide-react"; import ProfessionalStatement from "./pages/ProfessionalStatement"; +import { ProfilePage } from "./pages/Profile"; // Componente de acesso negado const AccessDenied: React.FC = () => { @@ -740,6 +741,15 @@ const AppContent: React.FC = () => { } /> + + + + + } + /> {/* Rota padrão - redireciona para home */} } /> diff --git a/frontend/components/Navbar.tsx b/frontend/components/Navbar.tsx index 8abca00..f5a1a62 100644 --- a/frontend/components/Navbar.tsx +++ b/frontend/components/Navbar.tsx @@ -144,7 +144,7 @@ export const Navbar: React.FC = ({ onNavigate, currentPage }) => { return ( <> -