feat: Marketing site
Server Deploy / deploy (push) Successful in 5m51s

This commit is contained in:
2026-07-22 16:50:13 +01:00
parent 7a3b8cb700
commit 693d59a3e2
51 changed files with 10656 additions and 12 deletions
+66
View File
@@ -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)
}