diff --git a/app/main.py b/app/main.py index 4c6a8ad..b397cfd 100644 --- a/app/main.py +++ b/app/main.py @@ -1007,6 +1007,20 @@ def _loomio_safe_username(raw) -> str | None: return u if len(u) >= 2 else None +def _loomio_is_autogenerated(current: str, email: str) -> bool: + """Is this handle one Loomio generated for us? + + Loomio derives a handle from the email local part with non-alphanumerics + stripped, plus a random suffix ("joshuaztan" + "xjan5rzb8j27" for + joshua.z.tan@...). Anything that is not that shape — "amarino", say — was + chosen by a person, and renaming it would be an unrequested member-visible + change. Only auto-generated handles are in scope for this fix. + """ + local = re.sub(r"[^a-z0-9]", "", (email or "").split("@")[0].lower()) + cur = (current or "").lower() + return bool(local) and cur.startswith(local) and len(cur) > len(local) + + async def loomio_fix_usernames(client: httpx.AsyncClient) -> dict: """Rename Loomio users with auto-generated handles to their Cloudron username. @@ -1023,6 +1037,11 @@ async def loomio_fix_usernames(client: httpx.AsyncClient) -> dict: fixed, skipped = 0, 0 for u in users: + # Only Loomio's own auto-generated handles are in scope: a name a human + # chose stays, even when it differs from the Cloudron username. + if not _loomio_is_autogenerated(u.get("username") or "", u.get("email") or ""): + skipped += 1 + continue target = None for ident in u.get("identities") or []: if ident.get("identity_type") == "oauth" and ident.get("uid"):