fix(hosting): smart slug generation with fallback chain

Slugify now handles both domains and business names gracefully:
- Domain: strip protocol/www/TLD → batirenovation-strasbourg
- Business name: take first 3 meaningful words, skip filler
  (le, la, du, des, the, and) → boulangerie-coin
- Cap at 30 chars

Clients without a domain get clean slugs from their business name
instead of the full title truncated mid-word.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 22:56:28 +02:00
parent d380437594
commit b51f9e8e30

View File

@@ -34,20 +34,30 @@ ALLOWED_TRANSITIONS: dict[HostedSiteStatus, list[HostedSiteStatus]] = {
}
def _slugify(name: str) -> str:
"""Generate a short URL-safe slug from a business name or domain."""
def _slugify(name: str, max_length: int = 30) -> str:
"""Generate a short URL-safe slug from a domain or business name.
Priority: domain name (clean) > first 3 words of business name > full slug truncated.
"""
slug = name.lower().strip()
# Remove protocol/www if domain was passed
# If it looks like a domain, extract the hostname part
for prefix in ["https://", "http://", "www."]:
if slug.startswith(prefix):
slug = slug[len(prefix):]
slug = slug.rstrip("/")
# Remove TLD if it looks like a domain
if "." in slug and " " not in slug:
# Domain: remove TLD → batirenovation-strasbourg.fr → batirenovation-strasbourg
slug = slug.rsplit(".", 1)[0]
else:
# Business name: take first 3 meaningful words for brevity
words = re.sub(r"[^a-z0-9\s]", "", slug).split()
# Skip filler words
filler = {"the", "le", "la", "les", "de", "du", "des", "et", "and", "und", "die", "der", "das"}
words = [w for w in words if w not in filler][:3]
slug = " ".join(words)
slug = re.sub(r"[^a-z0-9\s-]", "", slug)
slug = re.sub(r"[\s-]+", "-", slug)
return slug.strip("-")[:30]
return slug.strip("-")[:max_length]
class HostedSiteService: