@@ -0,0 +1,66 @@
|
||||
package provision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Slug rules mirrored from the control plane (server/internal/services: Slugify in
|
||||
stepscan.go, reservedSlugs and CreateOrg in orgs.go).
|
||||
|
||||
They live here rather than being imported because sitesvc is a separate module
|
||||
with no dependency on the server. That is a deliberate trade: sitesvc stays
|
||||
small and independent, at the cost of this one duplicated rule set.
|
||||
|
||||
Keep the two in step. If the control plane's slug handling, reserved names or
|
||||
bcrypt cost change, change them here in the same commit — nothing enforces the
|
||||
match automatically, and a divergence would create tenants under rules the app
|
||||
does not agree with.
|
||||
*/
|
||||
|
||||
const (
|
||||
MinSlugLength = 3
|
||||
MaxSlugLength = 40
|
||||
BcryptCost = 12 // matches services.CreateUser
|
||||
)
|
||||
|
||||
var slugStrip = regexp.MustCompile(`[^a-z0-9]+`)
|
||||
|
||||
// ReservedSlugs are names that would collide with a route or a host label.
|
||||
// Mirrored from services.reservedSlugs.
|
||||
var ReservedSlugs = map[string]bool{
|
||||
"www": true, "api": true, "app": true, "admin": true, "auth": true,
|
||||
"install": true, "static": true, "_next": true, "default": true,
|
||||
}
|
||||
|
||||
func Slugify(name string) string {
|
||||
s := strings.ToLower(name)
|
||||
s = slugStrip.ReplaceAllString(s, "-")
|
||||
return strings.Trim(s, "-")
|
||||
}
|
||||
|
||||
// BaseSlug derives and validates the slug for an organisation name, returning
|
||||
// the same errors the control plane's CreateOrg would.
|
||||
func BaseSlug(name string) (string, error) {
|
||||
base := Slugify(name)
|
||||
if len(base) < MinSlugLength {
|
||||
return "", fmt.Errorf("organisation name too short (slug must be at least %d characters)", MinSlugLength)
|
||||
}
|
||||
if len(base) > MaxSlugLength {
|
||||
base = base[:MaxSlugLength]
|
||||
}
|
||||
if ReservedSlugs[base] {
|
||||
return "", fmt.Errorf("that organisation name is reserved")
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// NextSlug is the collision suffix scheme: base, base-2, base-3, ...
|
||||
func NextSlug(base string, attempt int) string {
|
||||
if attempt < 2 {
|
||||
return base
|
||||
}
|
||||
return fmt.Sprintf("%s-%d", base, attempt)
|
||||
}
|
||||
Reference in New Issue
Block a user