fix: avoid repeated panel identity syncs
This commit is contained in:
@@ -42,8 +42,55 @@ def _normalize_description(value: Optional[str]) -> str:
|
|||||||
return "\n".join((value or "").split()).strip()
|
return "\n".join((value or "").split()).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _repair_cp1251_mojibake(value: str) -> str:
|
||||||
|
try:
|
||||||
|
return value.encode("latin1").decode("cp1251")
|
||||||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def _description_variants(value: Optional[str]) -> set[str]:
|
||||||
|
normalized = _normalize_description(value)
|
||||||
|
variants = {normalized}
|
||||||
|
repaired = _normalize_description(_repair_cp1251_mojibake(normalized))
|
||||||
|
if repaired:
|
||||||
|
variants.add(repaired)
|
||||||
|
return variants
|
||||||
|
|
||||||
|
|
||||||
def _description_matches(current: Optional[str], desired: str) -> bool:
|
def _description_matches(current: Optional[str], desired: str) -> bool:
|
||||||
return _normalize_description(current) == _normalize_description(desired)
|
return bool(_description_variants(current) & _description_variants(desired))
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_identity_matches_user(
|
||||||
|
panel_user: dict[str, Any],
|
||||||
|
user: User,
|
||||||
|
desired_description: str,
|
||||||
|
) -> bool:
|
||||||
|
if desired_description and not _description_matches(
|
||||||
|
panel_user.get("description"),
|
||||||
|
desired_description,
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if user.email and _normalize_panel_email(panel_user.get("email")) != user.email.strip().lower():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if user.telegram_id and _coerce_panel_telegram_id(panel_user.get("telegramId")) != int(
|
||||||
|
user.telegram_id
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _panel_identity_update_payload(user: User, description_text: str) -> dict[str, Any]:
|
||||||
|
payload: dict[str, Any] = {"description": description_text}
|
||||||
|
if user.email:
|
||||||
|
payload["email"] = user.email
|
||||||
|
if user.telegram_id:
|
||||||
|
payload["telegramId"] = user.telegram_id
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
def _datetime_matches(current: Optional[datetime], desired: datetime) -> bool:
|
def _datetime_matches(current: Optional[datetime], desired: datetime) -> bool:
|
||||||
@@ -589,28 +636,15 @@ async def _perform_sync_impl(
|
|||||||
if line
|
if line
|
||||||
)
|
)
|
||||||
# Update description only when it differs from the current one on panel
|
# Update description only when it differs from the current one on panel
|
||||||
current_panel_description = (
|
|
||||||
panel_user_dict.get("description") or ""
|
|
||||||
).strip()
|
|
||||||
desired_description = description_text.strip()
|
desired_description = description_text.strip()
|
||||||
if desired_description and not _description_matches(
|
if desired_description and not _panel_identity_matches_user(
|
||||||
current_panel_description, desired_description
|
panel_user_dict,
|
||||||
|
existing_user,
|
||||||
|
desired_description,
|
||||||
):
|
):
|
||||||
await panel_service.update_user_details_on_panel(
|
await panel_service.update_user_details_on_panel(
|
||||||
panel_uuid,
|
panel_uuid,
|
||||||
{
|
_panel_identity_update_payload(existing_user, description_text),
|
||||||
"description": description_text,
|
|
||||||
**(
|
|
||||||
{"email": existing_user.email}
|
|
||||||
if existing_user.email
|
|
||||||
else {}
|
|
||||||
),
|
|
||||||
**(
|
|
||||||
{"telegramId": existing_user.telegram_id}
|
|
||||||
if existing_user.telegram_id
|
|
||||||
else {}
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except Exception as e_desc:
|
except Exception as e_desc:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
|
|||||||
@@ -14,6 +14,20 @@ def test_description_match_ignores_whitespace_shape():
|
|||||||
assert _description_matches("email@example.com username", "email@example.com\nusername")
|
assert _description_matches("email@example.com username", "email@example.com\nusername")
|
||||||
|
|
||||||
|
|
||||||
|
def test_description_match_accepts_cp1251_mojibake_from_panel():
|
||||||
|
desired = "user@example.com\nalice\nАлексей\nЧерников"
|
||||||
|
panel_value = "user@example.com\nalice\nÀëåêñåé\n×åðíèêîâ"
|
||||||
|
|
||||||
|
assert _description_matches(panel_value, desired)
|
||||||
|
|
||||||
|
|
||||||
|
def test_description_match_rejects_different_identity_after_mojibake_repair():
|
||||||
|
desired = "user@example.com\nalice\nАлексей"
|
||||||
|
panel_value = "other@example.com\nalice\nÀëåêñåé"
|
||||||
|
|
||||||
|
assert not _description_matches(panel_value, desired)
|
||||||
|
|
||||||
|
|
||||||
def test_panel_telegram_id_is_coerced_to_int():
|
def test_panel_telegram_id_is_coerced_to_int():
|
||||||
assert _coerce_panel_telegram_id("12345") == 12345
|
assert _coerce_panel_telegram_id("12345") == 12345
|
||||||
assert _coerce_panel_telegram_id("") is None
|
assert _coerce_panel_telegram_id("") is None
|
||||||
|
|||||||
Reference in New Issue
Block a user