fix(backend): profile history gets a row only when a field really changed, partial snapshots keep the known phone and username

This commit is contained in:
hh committed 2026-10-19 02:57:49 +00:00
1 parent aa41bbbc74
commit 0c9c848fb1
2 files changed
+46 -15

No files matched your search

@@ -1,4 +1,4 @@
from dataclasses import dataclass from dataclasses import dataclass, field
from pyrogram import Client, raw from pyrogram import Client, raw
from pyrogram.types import Chat, User from pyrogram.types import Chat, User
@@ -12,6 +12,11 @@ class ProfileFields:
phone: str | None = None phone: str | None = None
photo_unique_id: str | None = None photo_unique_id: str | None = None
is_deleted_account: bool = False is_deleted_account: bool = False
unknown: frozenset[str] = field(default=frozenset(), compare=False)
MIN_UNKNOWN = frozenset({"username", "phone"})
CHAT_UNKNOWN = frozenset({"phone", "is_deleted_account"})
def active_username(usernames: list[raw.types.Username]) -> str | None: def active_username(usernames: list[raw.types.Username]) -> str | None:
@@ -35,6 +40,7 @@ async def snapshot_from_user(
phone=user.phone_number if user else None, phone=user.phone_number if user else None,
photo_unique_id=photo_unique_id, photo_unique_id=photo_unique_id,
is_deleted_account=bool(getattr(raw_user, "deleted", False)), is_deleted_account=bool(getattr(raw_user, "deleted", False)),
unknown=MIN_UNKNOWN if getattr(raw_user, "min", False) else frozenset(),
) )
return fields, photo_file_id, photo_unique_id return fields, photo_file_id, photo_unique_id
@@ -52,6 +58,7 @@ def snapshot_from_high_level(
phone=user.phone_number, phone=user.phone_number,
photo_unique_id=photo_unique_id, photo_unique_id=photo_unique_id,
is_deleted_account=bool(user.is_deleted), is_deleted_account=bool(user.is_deleted),
unknown=MIN_UNKNOWN if user.is_min else frozenset(),
) )
return fields, photo_file_id, photo_unique_id return fields, photo_file_id, photo_unique_id
@@ -64,8 +71,7 @@ def snapshot_from_chat(chat: Chat) -> tuple[ProfileFields, str | None, str | Non
first_name=chat.first_name, first_name=chat.first_name,
last_name=chat.last_name, last_name=chat.last_name,
username=chat.username, username=chat.username,
phone=None,
photo_unique_id=photo_unique_id, photo_unique_id=photo_unique_id,
is_deleted_account=False, unknown=CHAT_UNKNOWN,
) )
return fields, photo_file_id, photo_unique_id return fields, photo_file_id, photo_unique_id
@@ -9,26 +9,46 @@ WHERE account_id = $1 AND peer_id = $2
""" """
_UPSERT_PEER = """ _UPSERT_PEER = """
INSERT INTO peers INSERT INTO peers AS p
(account_id, peer_id, first_name, last_name, username, phone, (account_id, peer_id, first_name, last_name, username, phone,
photo_unique_id, is_deleted_account, raw, updated_at) photo_unique_id, is_deleted_account, raw, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, now()) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, now())
ON CONFLICT (account_id, peer_id) DO UPDATE SET ON CONFLICT (account_id, peer_id) DO UPDATE SET
first_name = EXCLUDED.first_name, first_name = EXCLUDED.first_name,
last_name = EXCLUDED.last_name, last_name = EXCLUDED.last_name,
username = EXCLUDED.username, username = CASE WHEN 'username' = ANY($10::text[])
phone = EXCLUDED.phone, THEN COALESCE(EXCLUDED.username, p.username) ELSE EXCLUDED.username END,
phone = CASE WHEN 'phone' = ANY($10::text[])
THEN COALESCE(EXCLUDED.phone, p.phone) ELSE EXCLUDED.phone END,
photo_unique_id = EXCLUDED.photo_unique_id, photo_unique_id = EXCLUDED.photo_unique_id,
is_deleted_account = EXCLUDED.is_deleted_account, is_deleted_account = CASE WHEN 'is_deleted_account' = ANY($10::text[])
THEN p.is_deleted_account ELSE EXCLUDED.is_deleted_account END,
raw = EXCLUDED.raw, raw = EXCLUDED.raw,
updated_at = now() updated_at = now()
""" """
_INSERT_HISTORY = """ _INSERT_CHANGE = """
INSERT INTO peer_history INSERT INTO peer_history
(account_id, peer_id, observed_at, first_name, last_name, username, phone, (account_id, peer_id, observed_at, first_name, last_name, username, phone,
photo_unique_id, is_deleted_account, raw) photo_unique_id, is_deleted_account, raw)
VALUES ($1, $2, now(), $3, $4, $5, $6, $7, $8, $9::jsonb) SELECT account_id, peer_id, clock_timestamp(), first_name, last_name, username,
phone, photo_unique_id, is_deleted_account, raw
FROM peers p
WHERE account_id = $1 AND peer_id = $2
AND NOT EXISTS (
SELECT 1 FROM (
SELECT first_name, last_name, username, phone, photo_unique_id,
is_deleted_account
FROM peer_history
WHERE account_id = $1 AND peer_id = $2
ORDER BY observed_at DESC LIMIT 1
) latest
WHERE (latest.first_name, latest.last_name, latest.username, latest.phone,
latest.photo_unique_id, latest.is_deleted_account)
IS NOT DISTINCT FROM
(p.first_name, p.last_name, p.username, p.phone,
p.photo_unique_id, p.is_deleted_account)
)
ON CONFLICT DO NOTHING ON CONFLICT DO NOTHING
""" """
@@ -57,8 +77,12 @@ async def write_profile( # noqa: PLR0913
raw: str, raw: str,
*, *,
history: bool, history: bool,
) -> None: ) -> bool:
args = ( async with pool.acquire() as conn, conn.transaction():
await conn.execute(
_UPSERT_PEER,
account_id,
peer_id,
fields.first_name, fields.first_name,
fields.last_name, fields.last_name,
fields.username, fields.username,
@@ -66,8 +90,9 @@ async def write_profile( # noqa: PLR0913
fields.photo_unique_id, fields.photo_unique_id,
fields.is_deleted_account, fields.is_deleted_account,
raw, raw,
sorted(fields.unknown),
) )
async with pool.acquire() as conn, conn.transaction(): if not history:
await conn.execute(_UPSERT_PEER, account_id, peer_id, *args) return False
if history: status = await conn.execute(_INSERT_CHANGE, account_id, peer_id)
await conn.execute(_INSERT_HISTORY, account_id, peer_id, *args) return status == "INSERT 0 1"