"""Shared domain primitives. Pure Python — no framework, no I/O. The domain layer must stay importable without FastAPI, SQLAlchemy or a database connection. """ from __future__ import annotations import re import uuid from datetime import UTC, datetime _SLUG_RE = re.compile(r"[^a-z0-9]+") def new_id() -> str: """Opaque, URL-safe entity id (portable across SQLite and PostgreSQL).""" return uuid.uuid4().hex def utcnow() -> datetime: return datetime.now(UTC) def slugify(value: str, *, max_length: int = 48) -> str: """ASCII slug. Non-ASCII input (e.g. Persian) collapses to a stable fallback. Persian organization names do not transliterate, so we keep a short random suffix appended by the caller when the slug would be empty. """ slug = _SLUG_RE.sub("-", value.strip().lower()).strip("-") slug = slug[:max_length].strip("-") if not slug: slug = f"x{uuid.uuid4().hex[:8]}" return slug def ensure_unique_slug(base: str, taken: set[str], *, max_length: int = 48) -> str: """Append ``-2``, ``-3`` ... until the slug is free.""" candidate = base[:max_length] if candidate not in taken: return candidate n = 2 while True: suffix = f"-{n}" candidate = f"{base[: max_length - len(suffix)]}{suffix}" if candidate not in taken: return candidate n += 1