package service import ( "database/sql" "errors" "aggios-app/backend/internal/domain" "aggios-app/backend/internal/repository" "github.com/google/uuid" ) var ( ErrTenantNotFound = errors.New("tenant not found") ) // TenantService handles tenant business logic type TenantService struct { tenantRepo *repository.TenantRepository db *sql.DB } // NewTenantService creates a new tenant service func NewTenantService(tenantRepo *repository.TenantRepository, db *sql.DB) *TenantService { return &TenantService{ tenantRepo: tenantRepo, db: db, } } // Create creates a new tenant func (s *TenantService) Create(req domain.CreateTenantRequest) (*domain.Tenant, error) { // Check if subdomain already exists exists, err := s.tenantRepo.SubdomainExists(req.Subdomain) if err != nil { return nil, err } if exists { return nil, ErrSubdomainTaken } tenant := &domain.Tenant{ Name: req.Name, Domain: req.Domain, Subdomain: req.Subdomain, } if err := s.tenantRepo.Create(tenant); err != nil { return nil, err } return tenant, nil } // GetByID retrieves a tenant by ID func (s *TenantService) GetByID(id uuid.UUID) (*domain.Tenant, error) { tenant, err := s.tenantRepo.FindByID(id) if err != nil { return nil, err } if tenant == nil { return nil, ErrTenantNotFound } return tenant, nil } // GetBySubdomain retrieves a tenant by subdomain func (s *TenantService) GetBySubdomain(subdomain string) (*domain.Tenant, error) { tenant, err := s.tenantRepo.FindBySubdomain(subdomain) if err != nil { return nil, err } if tenant == nil { return nil, ErrTenantNotFound } return tenant, nil } // ListAll retrieves all tenants func (s *TenantService) ListAll() ([]*domain.Tenant, error) { return s.tenantRepo.FindAll() } // ListAllWithDetails retrieves all tenants with their plan and solutions information func (s *TenantService) ListAllWithDetails() ([]map[string]interface{}, error) { tenants, err := s.tenantRepo.FindAll() if err != nil { return nil, err } var result []map[string]interface{} for _, tenant := range tenants { tenantData := map[string]interface{}{ "id": tenant.ID, "name": tenant.Name, "subdomain": tenant.Subdomain, "domain": tenant.Domain, "email": tenant.Email, "phone": tenant.Phone, "cnpj": tenant.CNPJ, "is_active": tenant.IsActive, "created_at": tenant.CreatedAt, "logo_url": tenant.LogoURL, "logo_horizontal_url": tenant.LogoHorizontalURL, "primary_color": tenant.PrimaryColor, "secondary_color": tenant.SecondaryColor, } // Buscar subscription e soluções var planName sql.NullString var planID string query := ` SELECT s.plan_id, p.name as plan_name FROM agency_subscriptions s JOIN plans p ON s.plan_id = p.id WHERE s.agency_id = $1 AND s.status = 'active' LIMIT 1 ` err = s.db.QueryRow(query, tenant.ID).Scan(&planID, &planName) if err == nil && planName.Valid { tenantData["plan_name"] = planName.String // 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, planID) if err == nil { defer rows.Close() var solutions []map[string]interface{} for rows.Next() { var id, name, slug string var icon sql.NullString if err := rows.Scan(&id, &name, &slug, &icon); err == nil { solution := map[string]interface{}{ "id": id, "name": name, "slug": slug, } if icon.Valid { solution["icon"] = icon.String } solutions = append(solutions, solution) } } tenantData["solutions"] = solutions } } result = append(result, tenantData) } return result, nil } // Delete removes a tenant by ID func (s *TenantService) Delete(id uuid.UUID) error { if err := s.tenantRepo.Delete(id); err != nil { if errors.Is(err, sql.ErrNoRows) { return ErrTenantNotFound } return err } return nil }