photum/backend/internal/auth/handler.go
NANDO9322 9c6ee3afdb feat: habilita edição de perfil para clientes e corrige carga de dados
Backend:
- Adiciona endpoint `PUT /api/me` para permitir atualização de dados do usuário logado.
- Implementa query `UpdateCadastroCliente` e função de serviço [UpdateClientData]para persistir alterações de clientes.
- Atualiza handlers [Me], [Login] e [ListPending] para incluir e mapear corretamente campos de cliente (CPF, Endereço, Telefone).
- Corrige mapeamento do campo `phone` na struct de resposta do usuário.

Frontend:
- Habilita o formulário de edição em [Profile.tsx] para usuários do tipo 'CLIENTE' (Event Owner).
- Adiciona função [updateUserProfile] em [apiService.ts] para consumir o novo endpoint.
- Atualiza [AuthContext] para persistir campos do cliente (CPF, Endereço, etc.) durante a restauração de sessão ([restoreSession], corrigindo o bug de perfil vazio ao recarregar a página.
- Padroniza envio de dados no Registro e Aprovação para usar `snake_case` (ex: `cpf_cnpj`, `professional_type`).
- Atualiza tipos em [types.ts] para incluir campos de endereço e documentos.
2026-02-09 00:56:09 -03:00

878 lines
26 KiB
Go

package auth
import (
"net/http"
"strings"
"photum-backend/internal/profissionais"
"photum-backend/internal/storage"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Handler struct {
service *Service
s3Service *storage.S3Service
}
func NewHandler(service *Service, s3Service *storage.S3Service) *Handler {
return &Handler{service: service, s3Service: s3Service}
}
type uploadURLRequest struct {
Filename string `json:"filename" binding:"required"`
ContentType string `json:"content_type" binding:"required"`
}
// GetUploadURL godoc
// @Summary Get S3 Presigned URL for upload
// @Description Get a pre-signed URL to upload a file directly to S3/Civo
// @Tags auth
// @Accept json
// @Produce json
// @Param request body uploadURLRequest true "Upload URL Request"
// @Success 200 {object} map[string]string
// @Router /auth/upload-url [post]
func (h *Handler) GetUploadURL(c *gin.Context) {
var req uploadURLRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
uploadURL, publicURL, err := h.s3Service.GeneratePresignedURL(req.Filename, req.ContentType)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"upload_url": uploadURL,
"public_url": publicURL,
})
}
type registerRequest struct {
Email string `json:"email" binding:"required,email"`
Senha string `json:"senha" binding:"required,min=6"`
Role string `json:"role" binding:"required"`
Nome string `json:"nome" binding:"required"`
Telefone string `json:"telefone"`
TipoProfissional string `json:"professional_type"`
EmpresaID string `json:"empresa_id"`
Regiao string `json:"regiao"`
CpfCnpj string `json:"cpf_cnpj"`
Cep string `json:"cep"`
Endereco string `json:"endereco"`
Numero string `json:"numero"`
Complemento string `json:"complemento"`
Bairro string `json:"bairro"`
Cidade string `json:"cidade"`
Estado string `json:"estado"`
}
// Register godoc
// @Summary Register a new user
// @Description Register a new user with email, password, name, phone, role and professional type
// @Tags auth
// @Accept json
// @Produce json
// @Param request body registerRequest true "Register Request"
// @Success 201 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /auth/register [post]
func (h *Handler) Register(c *gin.Context) {
var req registerRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Create professional data only if role is appropriate
var profData *profissionais.CreateProfissionalInput
if req.Role == "BUSINESS_OWNER" {
profData = &profissionais.CreateProfissionalInput{
Nome: req.Nome,
Whatsapp: &req.Telefone,
}
}
var empresaIDPtr *string
if req.EmpresaID != "" {
empresaIDPtr = &req.EmpresaID
}
regiao := c.GetString("regiao")
if regiao == "" {
regiao = c.GetHeader("x-regiao")
}
// Default to SP if still empty
if regiao == "" {
regiao = "SP"
}
user, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.Telefone, req.TipoProfissional, empresaIDPtr, profData, regiao, req.CpfCnpj, req.Cep, req.Endereco, req.Numero, req.Complemento, req.Bairro, req.Cidade, req.Estado)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Auto-login after registration
tokenPair, _, _, err := h.service.Login(c.Request.Context(), req.Email, req.Senha)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "user created but failed to auto-login"})
return
}
http.SetCookie(c.Writer, &http.Cookie{
Name: "refresh_token",
Value: tokenPair.RefreshToken,
Path: "/auth/refresh",
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteStrictMode,
MaxAge: 30 * 24 * 60 * 60,
})
// Set access_token cookie for fallback
http.SetCookie(c.Writer, &http.Cookie{
Name: "access_token",
Value: tokenPair.AccessToken,
Path: "/",
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteStrictMode,
MaxAge: 15 * 60,
})
c.JSON(http.StatusCreated, gin.H{
"message": "user created",
"access_token": tokenPair.AccessToken,
"user": gin.H{
"id": uuid.UUID(user.ID.Bytes).String(),
"email": user.Email,
"role": user.Role,
"ativo": user.Ativo,
},
})
}
type loginRequest struct {
Email string `json:"email" binding:"required,email" example:"admin@photum.com"`
Senha string `json:"senha" binding:"required,min=6" example:"123456"`
}
type loginResponse struct {
AccessToken string `json:"access_token"`
ExpiresAt string `json:"expires_at"`
User userResponse `json:"user"`
Profissional interface{} `json:"profissional,omitempty"`
}
type userResponse struct {
ID string `json:"id"`
Email string `json:"email"`
Role string `json:"role"`
Ativo bool `json:"ativo"`
Name string `json:"name,omitempty"`
Phone string `json:"phone,omitempty"`
CompanyID string `json:"company_id,omitempty"`
CompanyName string `json:"company_name,omitempty"`
AllowedRegions []string `json:"allowed_regions"`
CpfCnpj string `json:"cpf_cnpj,omitempty"`
Cep string `json:"cep,omitempty"`
Endereco string `json:"endereco,omitempty"`
Numero string `json:"numero,omitempty"`
Complemento string `json:"complemento,omitempty"`
Bairro string `json:"bairro,omitempty"`
Cidade string `json:"cidade,omitempty"`
Estado string `json:"estado,omitempty"`
}
// Login godoc
// @Summary Login
// @Description Login with email and password
// @Tags auth
// @Accept json
// @Produce json
// @Param request body loginRequest true "Login Request"
// @Success 200 {object} loginResponse
// @Failure 401 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /auth/login [post]
func (h *Handler) Login(c *gin.Context) {
var req loginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
tokenPair, user, profData, err := h.service.Login(c.Request.Context(), req.Email, req.Senha)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
return
}
http.SetCookie(c.Writer, &http.Cookie{
Name: "refresh_token",
Value: tokenPair.RefreshToken,
Path: "/auth/refresh",
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteStrictMode,
MaxAge: 30 * 24 * 60 * 60,
})
// Set access_token cookie for fallback
http.SetCookie(c.Writer, &http.Cookie{
Name: "access_token",
Value: tokenPair.AccessToken,
Path: "/",
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteStrictMode,
MaxAge: 15 * 60, // 15 mins
})
// Handle Nullable Fields
var companyID, companyName string
if user.EmpresaID.Valid {
companyID = uuid.UUID(user.EmpresaID.Bytes).String()
}
if user.EmpresaNome.Valid {
companyName = user.EmpresaNome.String
}
// Prepare response
uResp := userResponse{
ID: uuid.UUID(user.ID.Bytes).String(),
Email: user.Email,
Role: user.Role,
Ativo: user.Ativo, // Added this back from original
Name: user.Nome,
CompanyID: companyID,
CompanyName: companyName,
AllowedRegions: user.RegioesPermitidas,
Phone: "",
}
// Helper to get phone from profData or Client Data
if user.Role == "PHOTOGRAPHER" && profData != nil {
if profData.Whatsapp.Valid {
uResp.Phone = profData.Whatsapp.String
}
} else if user.Role == "EVENT_OWNER" {
// Fetch Client Data
clientData, err := h.service.GetClientData(c.Request.Context(), uResp.ID)
if err == nil && clientData != nil {
if clientData.Telefone.Valid {
uResp.Phone = clientData.Telefone.String
}
if clientData.CpfCnpj.Valid {
uResp.CpfCnpj = clientData.CpfCnpj.String
}
if clientData.Cep.Valid {
uResp.Cep = clientData.Cep.String
}
if clientData.Endereco.Valid {
uResp.Endereco = clientData.Endereco.String
}
if clientData.Numero.Valid {
uResp.Numero = clientData.Numero.String
}
if clientData.Complemento.Valid {
uResp.Complemento = clientData.Complemento.String
}
if clientData.Bairro.Valid {
uResp.Bairro = clientData.Bairro.String
}
if clientData.Cidade.Valid {
uResp.Cidade = clientData.Cidade.String
}
if clientData.Estado.Valid {
uResp.Estado = clientData.Estado.String
}
// Use client name if available ?? Or user name is fine.
// Usually user.Name comes from `usuarios` table, but `cadastro_clientes` also has nome.
// Let's stick to user.Name for consistency, usually they should be same.
}
}
resp := loginResponse{
AccessToken: tokenPair.AccessToken,
ExpiresAt: "2025-...",
User: uResp,
}
if profData != nil {
resp.Profissional = map[string]interface{}{
"id": uuid.UUID(profData.ID.Bytes).String(),
"nome": profData.Nome,
"funcao_profissional_id": uuid.UUID(profData.FuncaoProfissionalID.Bytes).String(),
"funcao_profissional": "", // Deprecated/Removed from query
"functions": profData.Functions,
"equipamentos": profData.Equipamentos.String,
"avatar_url": profData.AvatarUrl.String,
}
}
c.JSON(http.StatusOK, resp)
}
// Refresh godoc
// @Summary Refresh access token
// @Description Get a new access token using a valid refresh token
// @Tags auth
// @Accept json
// @Produce json
// @Param refresh_token body string false "Refresh Token"
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} map[string]string
// @Router /auth/refresh [post]
func (h *Handler) Refresh(c *gin.Context) {
refreshToken, err := c.Cookie("refresh_token")
if err != nil {
var req struct {
RefreshToken string `json:"refresh_token"`
}
if err := c.ShouldBindJSON(&req); err == nil {
refreshToken = req.RefreshToken
}
}
if refreshToken == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "refresh token required"})
return
}
accessToken, accessExp, err := h.service.Refresh(c.Request.Context(), refreshToken)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid refresh token"})
return
}
c.JSON(http.StatusOK, gin.H{
"access_token": accessToken,
"expires_at": accessExp,
})
}
// Logout godoc
// @Summary Logout user
// @Description Revoke refresh token and clear cookie
// @Tags auth
// @Accept json
// @Produce json
// @Param refresh_token body string false "Refresh Token"
// @Success 200 {object} map[string]string
// @Router /auth/logout [post]
func (h *Handler) Logout(c *gin.Context) {
refreshToken, err := c.Cookie("refresh_token")
if err != nil {
var req struct {
RefreshToken string `json:"refresh_token"`
}
if err := c.ShouldBindJSON(&req); err == nil {
refreshToken = req.RefreshToken
}
}
if refreshToken != "" {
_ = h.service.Logout(c.Request.Context(), refreshToken)
}
c.SetCookie("refresh_token", "", -1, "/", "", false, true)
c.JSON(http.StatusOK, gin.H{"message": "logged out"})
}
// Me godoc
// @Summary Get current user
// @Description Get current authenticated user
// @Tags auth
// @Accept json
// @Produce json
// @Success 200 {object} loginResponse
// @Router /api/me [get]
func (h *Handler) Me(c *gin.Context) {
// User ID comes from context (AuthMiddleware)
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
// We can fetch fresh user data
user, err := h.service.GetUser(c.Request.Context(), userID.(string))
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found"})
return
}
var empresaNome string
if user.EmpresaNome.Valid {
empresaNome = user.EmpresaNome.String
}
var empresaID string
if user.EmpresaID.Valid {
empresaID = uuid.UUID(user.EmpresaID.Bytes).String()
}
// Ensure valid slice for JSON response (avoid null)
allowedRegions := make([]string, 0)
if user.RegioesPermitidas != nil {
allowedRegions = append(allowedRegions, user.RegioesPermitidas...)
}
uResp := userResponse{
ID: uuid.UUID(user.ID.Bytes).String(),
Email: user.Email,
Role: user.Role,
Ativo: user.Ativo,
Name: user.Nome,
Phone: user.Whatsapp, // Default to user.Whatsapp
CompanyName: empresaNome,
CompanyID: empresaID,
AllowedRegions: allowedRegions,
}
if user.Role == "EVENT_OWNER" {
clientData, err := h.service.GetClientData(c.Request.Context(), uResp.ID)
if err == nil && clientData != nil {
if clientData.Telefone.Valid {
uResp.Phone = clientData.Telefone.String
}
if clientData.CpfCnpj.Valid {
uResp.CpfCnpj = clientData.CpfCnpj.String
}
if clientData.Cep.Valid {
uResp.Cep = clientData.Cep.String
}
if clientData.Endereco.Valid {
uResp.Endereco = clientData.Endereco.String
}
if clientData.Numero.Valid {
uResp.Numero = clientData.Numero.String
}
if clientData.Complemento.Valid {
uResp.Complemento = clientData.Complemento.String
}
if clientData.Bairro.Valid {
uResp.Bairro = clientData.Bairro.String
}
if clientData.Cidade.Valid {
uResp.Cidade = clientData.Cidade.String
}
if clientData.Estado.Valid {
uResp.Estado = clientData.Estado.String
}
}
}
resp := loginResponse{
User: uResp,
}
if user.Role == "PHOTOGRAPHER" || user.Role == "BUSINESS_OWNER" {
regiao := c.GetString("regiao")
if regiao == "" {
regiao = c.GetHeader("x-regiao")
}
profData, err := h.service.GetProfessionalByUserID(c.Request.Context(), uuid.UUID(user.ID.Bytes).String())
if err == nil && profData != nil {
// Update phone from professional data if valid
if profData.Whatsapp.Valid {
resp.User.Phone = profData.Whatsapp.String
}
resp.Profissional = map[string]interface{}{
"id": uuid.UUID(profData.ID.Bytes).String(),
"nome": profData.Nome,
"funcao_profissional_id": uuid.UUID(profData.FuncaoProfissionalID.Bytes).String(),
"funcao_profissional": "", // Deprecated
"functions": profData.Functions,
"equipamentos": profData.Equipamentos.String,
"avatar_url": profData.AvatarUrl.String,
}
}
}
c.JSON(http.StatusOK, resp)
}
type updateMeRequest struct {
Nome string `json:"name"`
Telefone string `json:"phone"`
CpfCnpj string `json:"cpf_cnpj"`
Cep string `json:"cep"`
Endereco string `json:"endereco"`
Numero string `json:"numero"`
Complemento string `json:"complemento"`
Bairro string `json:"bairro"`
Cidade string `json:"cidade"`
Estado string `json:"estado"`
}
// UpdateMe godoc
// @Summary Update current user profile
// @Description Update profile information (Name, Phone, Address). Currently for EVENT_OWNER.
// @Tags auth
// @Accept json
// @Produce json
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Router /api/me [put]
func (h *Handler) UpdateMe(c *gin.Context) {
userID, exists := c.Get("userID")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
var req updateMeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload: " + err.Error()})
return
}
// For now, we assume this is mostly for Clients (EVENT_OWNER)
// based on the fields provided (address, cpf, etc).
// Future: Check role and call appropriate service if needed.
// But UpdateClientData uses "UpdateCadastroCliente" which is linked to user_id.
// If the user does not have a client record, it might fail or do nothing if query uses WHERE exists?
// The query uses UPDATE, so if no row, no update.
// We should probably check role or just try to update.
// But to be safe, let's just call UpdateClientData.
// If the user is a Photographer, they should use the /profissionais/me PUT (if exists) or similar.
// But wait, the user complaint is about Clients.
err := h.service.UpdateClientData(c.Request.Context(), userID.(string), UpdateClientInput{
Nome: req.Nome,
Telefone: req.Telefone,
CpfCnpj: req.CpfCnpj,
Cep: req.Cep,
Endereco: req.Endereco,
Numero: req.Numero,
Complemento: req.Complemento,
Bairro: req.Bairro,
Cidade: req.Cidade,
Estado: req.Estado,
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update profile: " + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "profile updated successfully"})
}
// ListPending godoc
// @Summary List pending users
// @Description List users with ativo=false
// @Tags admin
// @Accept json
// @Produce json
// @Success 200 {array} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Security BearerAuth
// @Router /api/admin/users/pending [get]
func (h *Handler) ListPending(c *gin.Context) {
regiao := c.GetString("regiao")
if regiao == "" {
regiao = c.GetHeader("x-regiao")
}
users, err := h.service.ListPendingUsers(c.Request.Context(), regiao)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Map to response
// The generated type ListUsuariosPendingRow fields are:
// ID pgtype.UUID
// Email string
// Role string
// Ativo bool
// CriadoEm pgtype.Timestamptz
// Nome pgtype.Text
// Whatsapp pgtype.Text
resp := make([]map[string]interface{}, len(users))
for i, u := range users {
nome := u.Nome
whatsapp := u.Whatsapp
var empresaNome string
if u.EmpresaNome.Valid {
empresaNome = u.EmpresaNome.String
}
resp[i] = map[string]interface{}{
"id": uuid.UUID(u.ID.Bytes).String(),
"email": u.Email,
"role": u.Role,
"ativo": u.Ativo,
"created_at": u.CriadoEm.Time,
"name": nome, // Mapped to name for frontend compatibility
"phone": whatsapp,
"company_name": empresaNome,
"professional_type": u.TipoProfissional.String, // Add this
}
}
c.JSON(http.StatusOK, resp)
}
// Approve godoc
// @Summary Approve user
// @Description Set user ativo=true
// @Tags admin
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security BearerAuth
// @Router /api/admin/users/{id}/approve [patch]
func (h *Handler) Approve(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
return
}
err := h.service.ApproveUser(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "user approved"})
}
// AdminCreateUser godoc
// @Summary Create user (Admin)
// @Description Create a new user with specific role (Admin only)
// @Tags admin
// @Accept json
// @Produce json
// @Param request body registerRequest true "Create User Request"
// @Success 201 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security BearerAuth
// @Router /api/admin/users [post]
func (h *Handler) AdminCreateUser(c *gin.Context) {
var req registerRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Just reuse the request struct but call AdminCreateUser service
regiao := c.GetString("regiao")
// If Admin explicitly sends regiao in body, use it (override context)
if req.Regiao != "" {
regiao = req.Regiao
}
if regiao == "" {
// Fallback or Error? Admin creation usually implies target region.
// Let's assume header is present or default.
regiao = "SP" // Default for now if missing? Or error?
}
user, err := h.service.AdminCreateUser(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.TipoProfissional, true, regiao, req.EmpresaID, req.Telefone, req.CpfCnpj, req.Cep, req.Endereco, req.Numero, req.Complemento, req.Bairro, req.Cidade, req.Estado)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
c.JSON(http.StatusConflict, gin.H{"error": "email already registered"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{
"message": "user created",
"id": uuid.UUID(user.ID.Bytes).String(),
"email": user.Email,
})
}
type updateRoleRequest struct {
Role string `json:"role" binding:"required"`
}
// UpdateRole godoc
// @Summary Update user role
// @Description Update user role (Admin only)
// @Tags admin
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Param request body updateRoleRequest true "Update Role Request"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security BearerAuth
// @Router /api/admin/users/{id}/role [patch]
func (h *Handler) UpdateRole(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
return
}
var req updateRoleRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err := h.service.UpdateUserRole(c.Request.Context(), id, req.Role)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "role updated"})
}
// DeleteUser godoc
// @Summary Delete user
// @Description Delete user (Admin only)
// @Tags admin
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security BearerAuth
// @Router /api/admin/users/{id} [delete]
func (h *Handler) DeleteUser(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
return
}
err := h.service.DeleteUser(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "user deleted"})
}
// ListUsers godoc
// @Summary List all users
// @Description List all users (Admin only)
// @Tags admin
// @Accept json
// @Produce json
// @Success 200 {array} map[string]interface{}
// @Failure 500 {object} map[string]string
// @Security BearerAuth
// @Router /api/admin/users [get]
func (h *Handler) ListUsers(c *gin.Context) {
regiao := c.GetString("regiao")
if regiao == "" {
regiao = c.GetHeader("x-regiao")
}
users, err := h.service.ListUsers(c.Request.Context(), regiao)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
resp := make([]map[string]interface{}, len(users))
for i, u := range users {
empresaId := ""
if u.EmpresaID.Valid {
empresaId = uuid.UUID(u.EmpresaID.Bytes).String()
}
resp[i] = map[string]interface{}{
"id": uuid.UUID(u.ID.Bytes).String(),
"email": u.Email,
"role": u.Role,
"ativo": u.Ativo,
"created_at": u.CriadoEm.Time,
"name": u.Nome,
"phone": u.Whatsapp,
"company_name": u.EmpresaNome.String,
"company_id": empresaId,
"professional_type": u.TipoProfissional.String,
}
}
c.JSON(http.StatusOK, resp)
}
// GetUser godoc
// @Summary Get user by ID
// @Description Get user details by ID (Admin only)
// @Tags admin
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
// @Security BearerAuth
// @Router /api/admin/users/{id} [get]
func (h *Handler) GetUser(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id required"})
return
}
user, err := h.service.GetUser(c.Request.Context(), id)
if err != nil {
if strings.Contains(err.Error(), "no rows") {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
var empresaNome string
if user.EmpresaNome.Valid {
empresaNome = user.EmpresaNome.String
}
var empresaID string
if user.EmpresaID.Valid {
empresaID = uuid.UUID(user.EmpresaID.Bytes).String()
}
resp := loginResponse{
User: userResponse{
ID: uuid.UUID(user.ID.Bytes).String(),
Email: user.Email,
Role: user.Role,
Ativo: user.Ativo,
Name: user.Nome,
Phone: user.Whatsapp,
CompanyName: empresaNome,
CompanyID: empresaID,
AllowedRegions: user.RegioesPermitidas,
},
}
c.JSON(http.StatusOK, resp)
}