- Adiciona coluna `tipo_profissional` à tabela `usuarios` - Atualiza handlers e services do Backend Go para persistir o tipo - Atualiza registro no Frontend para enviar o nome da função (ex: "Cinegrafista") - Corrige uploads S3 para compatibilidade com Civo (PathStyle) - Script para definir política pública de leitura no bucket S3 - Adiciona fallback para imagens de avatar na Navbar
639 lines
18 KiB
Go
639 lines
18 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"`
|
|
Nome string `json:"nome" binding:"required"`
|
|
Telefone string `json:"telefone"`
|
|
Role string `json:"role" binding:"required"`
|
|
EmpresaID string `json:"empresa_id"`
|
|
TipoProfissional string `json:"tipo_profissional"` // New field
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
user, err := h.service.Register(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome, req.Telefone, req.TipoProfissional, empresaIDPtr, profData)
|
|
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"`
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
resp := loginResponse{
|
|
AccessToken: tokenPair.AccessToken,
|
|
ExpiresAt: "2025-...", // logic to calculate if needed, or remove field
|
|
User: userResponse{
|
|
ID: uuid.UUID(user.ID.Bytes).String(),
|
|
Email: user.Email,
|
|
Role: user.Role,
|
|
Ativo: user.Ativo,
|
|
Name: user.Nome,
|
|
Phone: user.Whatsapp,
|
|
CompanyID: companyID,
|
|
CompanyName: companyName,
|
|
},
|
|
}
|
|
|
|
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": profData.FuncaoNome.String,
|
|
"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()
|
|
}
|
|
|
|
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,
|
|
},
|
|
}
|
|
// Note: We are not returning AccessToken/ExpiresAt here as they are already set/active.
|
|
// But to match loginResponse structure we can leave them empty or fill appropriately if we were refreshing.
|
|
// For session restore, we mainly need the User object.
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// 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) {
|
|
users, err := h.service.ListPendingUsers(c.Request.Context())
|
|
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,
|
|
}
|
|
}
|
|
|
|
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
|
|
user, err := h.service.AdminCreateUser(c.Request.Context(), req.Email, req.Senha, req.Role, req.Nome)
|
|
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) {
|
|
users, err := h.service.ListUsers(c.Request.Context())
|
|
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 {
|
|
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,
|
|
}
|
|
}
|
|
|
|
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,
|
|
CompanyID: empresaID,
|
|
CompanyName: empresaNome,
|
|
},
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|