- Validação cross-tenant no login e rotas protegidas
- File serving via /api/files/{bucket}/{path} (eliminação DNS)
- Mensagens de erro humanizadas inline (sem pop-ups)
- Middleware tenant detection via headers customizados
- Upload de logos retorna URLs via API
- README atualizado com changelog v1.4 completo
231 lines
7.2 KiB
Go
231 lines
7.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
|
|
"aggios-app/backend/internal/api/middleware"
|
|
"aggios-app/backend/internal/config"
|
|
"aggios-app/backend/internal/repository"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type AgencyHandler struct {
|
|
tenantRepo *repository.TenantRepository
|
|
config *config.Config
|
|
}
|
|
|
|
func NewAgencyHandler(tenantRepo *repository.TenantRepository, cfg *config.Config) *AgencyHandler {
|
|
return &AgencyHandler{
|
|
tenantRepo: tenantRepo,
|
|
config: cfg,
|
|
}
|
|
}
|
|
|
|
type AgencyProfileResponse struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
CNPJ string `json:"cnpj"`
|
|
Email string `json:"email"`
|
|
Phone string `json:"phone"`
|
|
Website string `json:"website"`
|
|
Address string `json:"address"`
|
|
Neighborhood string `json:"neighborhood"`
|
|
Number string `json:"number"`
|
|
Complement string `json:"complement"`
|
|
City string `json:"city"`
|
|
State string `json:"state"`
|
|
Zip string `json:"zip"`
|
|
RazaoSocial string `json:"razao_social"`
|
|
Description string `json:"description"`
|
|
Industry string `json:"industry"`
|
|
TeamSize string `json:"team_size"`
|
|
PrimaryColor string `json:"primary_color"`
|
|
SecondaryColor string `json:"secondary_color"`
|
|
LogoURL string `json:"logo_url"`
|
|
LogoHorizontalURL string `json:"logo_horizontal_url"`
|
|
}
|
|
|
|
type UpdateAgencyProfileRequest struct {
|
|
Name string `json:"name"`
|
|
CNPJ string `json:"cnpj"`
|
|
Email string `json:"email"`
|
|
Phone string `json:"phone"`
|
|
Website string `json:"website"`
|
|
Address string `json:"address"`
|
|
Neighborhood string `json:"neighborhood"`
|
|
Number string `json:"number"`
|
|
Complement string `json:"complement"`
|
|
City string `json:"city"`
|
|
State string `json:"state"`
|
|
Zip string `json:"zip"`
|
|
RazaoSocial string `json:"razao_social"`
|
|
Description string `json:"description"`
|
|
Industry string `json:"industry"`
|
|
TeamSize string `json:"team_size"`
|
|
PrimaryColor string `json:"primary_color"`
|
|
SecondaryColor string `json:"secondary_color"`
|
|
LogoURL string `json:"logo_url"`
|
|
LogoHorizontalURL string `json:"logo_horizontal_url"`
|
|
}
|
|
|
|
// GetProfile returns the current agency profile
|
|
func (h *AgencyHandler) GetProfile(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
// Get tenant from context (set by auth middleware)
|
|
tenantID := r.Context().Value(middleware.TenantIDKey)
|
|
|
|
if tenantID == nil {
|
|
http.Error(w, "Tenant not found in context", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
// Parse tenant ID
|
|
tid, err := uuid.Parse(tenantID.(string))
|
|
if err != nil {
|
|
http.Error(w, "Invalid tenant ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get tenant from database
|
|
tenant, err := h.tenantRepo.FindByID(tid)
|
|
if err != nil {
|
|
http.Error(w, "Error fetching profile", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if tenant == nil {
|
|
http.Error(w, "Tenant not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
log.Printf("🔍 GetProfile for tenant %s: Found %s", tid, tenant.Name)
|
|
log.Printf("📄 Tenant Data: Address=%s, Number=%s, TeamSize=%s, RazaoSocial=%s",
|
|
tenant.Address, tenant.Number, tenant.TeamSize, tenant.RazaoSocial)
|
|
|
|
response := AgencyProfileResponse{
|
|
ID: tenant.ID.String(),
|
|
Name: tenant.Name,
|
|
CNPJ: tenant.CNPJ,
|
|
Email: tenant.Email,
|
|
Phone: tenant.Phone,
|
|
Website: tenant.Website,
|
|
Address: tenant.Address,
|
|
Neighborhood: tenant.Neighborhood,
|
|
Number: tenant.Number,
|
|
Complement: tenant.Complement,
|
|
City: tenant.City,
|
|
State: tenant.State,
|
|
Zip: tenant.Zip,
|
|
RazaoSocial: tenant.RazaoSocial,
|
|
Description: tenant.Description,
|
|
Industry: tenant.Industry,
|
|
TeamSize: tenant.TeamSize,
|
|
PrimaryColor: tenant.PrimaryColor,
|
|
SecondaryColor: tenant.SecondaryColor,
|
|
LogoURL: tenant.LogoURL,
|
|
LogoHorizontalURL: tenant.LogoHorizontalURL,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|
|
// UpdateProfile updates the current agency profile
|
|
func (h *AgencyHandler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPut && r.Method != http.MethodPatch {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
// Get tenant from context (set by auth middleware)
|
|
tenantID := r.Context().Value(middleware.TenantIDKey)
|
|
if tenantID == nil {
|
|
http.Error(w, "Tenant not found", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
var req UpdateAgencyProfileRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Parse tenant ID
|
|
tid, err := uuid.Parse(tenantID.(string))
|
|
if err != nil {
|
|
http.Error(w, "Invalid tenant ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Prepare updates
|
|
updates := map[string]interface{}{
|
|
"name": req.Name,
|
|
"cnpj": req.CNPJ,
|
|
"razao_social": req.RazaoSocial,
|
|
"email": req.Email,
|
|
"phone": req.Phone,
|
|
"website": req.Website,
|
|
"address": req.Address,
|
|
"neighborhood": req.Neighborhood,
|
|
"number": req.Number,
|
|
"complement": req.Complement,
|
|
"city": req.City,
|
|
"state": req.State,
|
|
"zip": req.Zip,
|
|
"description": req.Description,
|
|
"industry": req.Industry,
|
|
"team_size": req.TeamSize,
|
|
"primary_color": req.PrimaryColor,
|
|
"secondary_color": req.SecondaryColor,
|
|
"logo_url": req.LogoURL,
|
|
"logo_horizontal_url": req.LogoHorizontalURL,
|
|
}
|
|
|
|
// Update in database
|
|
if err := h.tenantRepo.UpdateProfile(tid, updates); err != nil {
|
|
http.Error(w, "Error updating profile", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Fetch updated data
|
|
tenant, err := h.tenantRepo.FindByID(tid)
|
|
if err != nil {
|
|
http.Error(w, "Error fetching updated profile", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response := AgencyProfileResponse{
|
|
ID: tenant.ID.String(),
|
|
Name: tenant.Name,
|
|
CNPJ: tenant.CNPJ,
|
|
Email: tenant.Email,
|
|
Phone: tenant.Phone,
|
|
Website: tenant.Website,
|
|
Address: tenant.Address,
|
|
Neighborhood: tenant.Neighborhood,
|
|
Number: tenant.Number,
|
|
Complement: tenant.Complement,
|
|
City: tenant.City,
|
|
State: tenant.State,
|
|
Zip: tenant.Zip,
|
|
RazaoSocial: tenant.RazaoSocial,
|
|
Description: tenant.Description,
|
|
Industry: tenant.Industry,
|
|
TeamSize: tenant.TeamSize,
|
|
PrimaryColor: tenant.PrimaryColor,
|
|
SecondaryColor: tenant.SecondaryColor,
|
|
LogoURL: tenant.LogoURL,
|
|
LogoHorizontalURL: tenant.LogoHorizontalURL,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(response)
|
|
}
|
|
|