Files
panelhosting/internal/sitesvc/service.go
T

129 lines
3.4 KiB
Go

package sitesvc
import (
"context"
"errors"
"fmt"
"regexp"
"strings"
"github.com/panelhosting/panel/internal/auth"
"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")
ErrInvalidSSLEmail = errors.New("invalid ssl email")
ErrSSLEmailRequired = errors.New("ssl email is required when issuing certificate")
)
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 SSLIssuer interface {
IssueAsync(sslID, domainID int64, domain, webroot, email string)
IssueForSite(ctx context.Context, siteID int64, email string) error
}
type Service struct {
sites *repository.SiteRepository
servers *repository.ServerRepository
phpVers *repository.PHPVersionRepository
ssl SSLIssuer
}
func NewService(sites *repository.SiteRepository, servers *repository.ServerRepository, php *repository.PHPVersionRepository, ssl SSLIssuer) *Service {
return &Service{sites: sites, servers: servers, phpVers: php, ssl: ssl}
}
type CreateInput struct {
Name string
Domain string
PHPVersion string
SSLEmail string
ServerID int64
OwnerID int64
IsAdmin bool
IssueSSL 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)
sslEmail := strings.TrimSpace(in.SSLEmail)
if !siteNameRegex.MatchString(name) {
return nil, ErrInvalidSiteName
}
if !domainRegex.MatchString(domain) {
return nil, ErrInvalidDomain
}
if phpVersion == "" {
return nil, ErrInvalidPHPVersion
}
if in.IssueSSL {
if sslEmail == "" {
return nil, ErrSSLEmailRequired
}
if err := auth.ValidateEmail(sslEmail); err != nil {
return nil, ErrInvalidSSLEmail
}
}
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)
site, err := s.sites.Create(ctx, serverID, in.OwnerID, name, documentRoot, phpVersion, domain, sslEmail, in.IssueSSL)
if err != nil {
return nil, err
}
if in.IssueSSL && s.ssl != nil && site.SSLID > 0 {
s.ssl.IssueAsync(site.SSLID, site.DomainID, domain, documentRoot, sslEmail)
}
return site, nil
}
func (s *Service) ReissueSSLAsync(siteID int64, email string) {
if s.ssl == nil {
return
}
go func() {
_ = s.ssl.IssueForSite(context.Background(), siteID, email)
}()
}