package sitesvc import ( "context" "errors" "fmt" "regexp" "strings" "github.com/panelhosting/panel/internal/models" "github.com/panelhosting/panel/internal/repository" ) var ( ErrInvalidSiteName = errors.New("site name must be 3-64 chars: lowercase letters, digits, hyphen") ErrInvalidDomain = errors.New("invalid domain") ErrInvalidPHPVersion = errors.New("unsupported php version") ) var ( siteNameRegex = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$`) domainRegex = regexp.MustCompile(`^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$`) ) type Service struct { sites *repository.SiteRepository servers *repository.ServerRepository phpVers *repository.PHPVersionRepository } func NewService(sites *repository.SiteRepository, servers *repository.ServerRepository, php *repository.PHPVersionRepository) *Service { return &Service{sites: sites, servers: servers, phpVers: php} } type CreateInput struct { Name string Domain string PHPVersion string ServerID int64 OwnerID int64 IsAdmin bool } func (s *Service) List(ctx context.Context, user *models.User, isAdmin bool) ([]repository.SiteWithDomain, error) { return s.sites.ListForUser(ctx, user.ID, isAdmin) } func (s *Service) ListPHPVersions(ctx context.Context) ([]string, error) { return s.phpVers.ListActive(ctx) } func (s *Service) Create(ctx context.Context, in CreateInput) (*repository.SiteWithDomain, error) { name := strings.ToLower(strings.TrimSpace(in.Name)) domain := strings.ToLower(strings.TrimSpace(in.Domain)) phpVersion := strings.TrimSpace(in.PHPVersion) if !siteNameRegex.MatchString(name) { return nil, ErrInvalidSiteName } if !domainRegex.MatchString(domain) { return nil, ErrInvalidDomain } if phpVersion == "" { return nil, ErrInvalidPHPVersion } active, err := s.phpVers.IsActive(ctx, phpVersion) if err != nil { return nil, err } if !active { return nil, ErrInvalidPHPVersion } serverID := in.ServerID if serverID == 0 { srv, err := s.servers.GetFirst(ctx) if err != nil { return nil, fmt.Errorf("no server available: %w", err) } serverID = srv.ID } else { if _, err := s.servers.GetByID(ctx, serverID); err != nil { return nil, err } } documentRoot := fmt.Sprintf("/var/www/%s/public", name) return s.sites.Create(ctx, serverID, in.OwnerID, name, documentRoot, phpVersion, domain) }