package service import ( "aggios-app/backend/internal/config" "aggios-app/backend/internal/domain" "aggios-app/backend/internal/repository" "database/sql" "fmt" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" ) // AgencyService handles agency registration and management type AgencyService struct { userRepo *repository.UserRepository tenantRepo *repository.TenantRepository cfg *config.Config db *sql.DB } // NewAgencyService creates a new agency service func NewAgencyService(userRepo *repository.UserRepository, tenantRepo *repository.TenantRepository, cfg *config.Config, db *sql.DB) *AgencyService { return &AgencyService{ userRepo: userRepo, tenantRepo: tenantRepo, cfg: cfg, db: db, } } // RegisterAgency creates a new agency (tenant) and its admin user // Only SUPERADMIN can call this func (s *AgencyService) RegisterAgency(req domain.RegisterAgencyRequest) (*domain.Tenant, *domain.User, error) { // Validate password if len(req.AdminPassword) < s.cfg.Security.PasswordMinLength { return nil, nil, ErrWeakPassword } // Check if subdomain is available exists, err := s.tenantRepo.SubdomainExists(req.Subdomain) if err != nil { return nil, nil, err } if exists { return nil, nil, ErrSubdomainTaken } // Check if admin email already exists emailExists, err := s.userRepo.EmailExists(req.AdminEmail) if err != nil { return nil, nil, err } if emailExists { return nil, nil, ErrEmailAlreadyExists } // Create tenant address := req.Street if req.Number != "" { address += ", " + req.Number } if req.Complement != "" { address += " - " + req.Complement } tenant := &domain.Tenant{ Name: req.AgencyName, Domain: fmt.Sprintf("%s.%s", req.Subdomain, s.cfg.App.BaseDomain), Subdomain: req.Subdomain, CNPJ: req.CNPJ, RazaoSocial: req.RazaoSocial, Email: req.AdminEmail, Phone: req.Phone, Website: req.Website, Address: address, Neighborhood: req.Neighborhood, Number: req.Number, Complement: req.Complement, City: req.City, State: req.State, Zip: req.CEP, Description: req.Description, Industry: req.Industry, TeamSize: req.TeamSize, PrimaryColor: req.PrimaryColor, SecondaryColor: req.SecondaryColor, LogoURL: req.LogoURL, LogoHorizontalURL: req.LogoHorizontalURL, } if err := s.tenantRepo.Create(tenant); err != nil { return nil, nil, err } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.AdminPassword), bcrypt.DefaultCost) if err != nil { return nil, nil, err } // Create admin user for the agency adminUser := &domain.User{ TenantID: &tenant.ID, Email: req.AdminEmail, Password: string(hashedPassword), Name: req.AdminName, Role: "ADMIN_AGENCIA", } if err := s.userRepo.Create(adminUser); err != nil { return nil, nil, err } return tenant, adminUser, nil } // RegisterClient creates a new client user for a specific agency // Only ADMIN_AGENCIA can call this func (s *AgencyService) RegisterClient(req domain.RegisterClientRequest, tenantID uuid.UUID) (*domain.User, error) { // Validate password if len(req.Password) < s.cfg.Security.PasswordMinLength { return nil, ErrWeakPassword } // Check if email already exists exists, err := s.userRepo.EmailExists(req.Email) if err != nil { return nil, err } if exists { return nil, ErrEmailAlreadyExists } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { return nil, err } // Create client user client := &domain.User{ TenantID: &tenantID, Email: req.Email, Password: string(hashedPassword), Name: req.Name, Role: "CLIENTE", } if err := s.userRepo.Create(client); err != nil { return nil, err } return client, nil } // GetAgencyDetails returns tenant and admin information for superadmin view func (s *AgencyService) GetAgencyDetails(id uuid.UUID) (*domain.AgencyDetails, error) { tenant, err := s.tenantRepo.FindByID(id) if err != nil { return nil, err } if tenant == nil { return nil, ErrTenantNotFound } admin, err := s.userRepo.FindAdminByTenantID(id) if err != nil { return nil, err } protocol := "http://" if s.cfg.App.Environment == "production" { protocol = "https://" } details := &domain.AgencyDetails{ Tenant: tenant, AccessURL: fmt.Sprintf("%s%s", protocol, tenant.Domain), } if admin != nil { details.Admin = admin } // Buscar subscription e soluções var subscription domain.AgencySubscriptionInfo query := ` SELECT s.plan_id, p.name as plan_name, s.status FROM agency_subscriptions s JOIN plans p ON s.plan_id = p.id WHERE s.agency_id = $1 LIMIT 1 ` err = s.db.QueryRow(query, id).Scan(&subscription.PlanID, &subscription.PlanName, &subscription.Status) if err == nil { // Buscar soluções do plano solutionsQuery := ` SELECT sol.id, sol.name, sol.slug, sol.icon FROM solutions sol JOIN plan_solutions ps ON sol.id = ps.solution_id WHERE ps.plan_id = $1 ORDER BY sol.name ` rows, err := s.db.Query(solutionsQuery, subscription.PlanID) if err == nil { defer rows.Close() var solutions []domain.Solution for rows.Next() { var solution domain.Solution if err := rows.Scan(&solution.ID, &solution.Name, &solution.Slug, &solution.Icon); err == nil { solutions = append(solutions, solution) } } subscription.Solutions = solutions details.Subscription = &subscription } } return details, nil } // DeleteAgency removes a tenant and its related resources func (s *AgencyService) DeleteAgency(id uuid.UUID) error { tenant, err := s.tenantRepo.FindByID(id) if err != nil { return err } if tenant == nil { return ErrTenantNotFound } return s.tenantRepo.Delete(id) } // UpdateAgencyStatus updates the is_active status of a tenant func (s *AgencyService) UpdateAgencyStatus(id uuid.UUID, isActive bool) error { tenant, err := s.tenantRepo.FindByID(id) if err != nil { return err } if tenant == nil { return ErrTenantNotFound } return s.tenantRepo.UpdateStatus(id, isActive) }