diff options
Diffstat (limited to 'generalresearch/managers/thl/userhealth.py')
| -rw-r--r-- | generalresearch/managers/thl/userhealth.py | 147 |
1 files changed, 64 insertions, 83 deletions
diff --git a/generalresearch/managers/thl/userhealth.py b/generalresearch/managers/thl/userhealth.py index b986256..1aeab44 100644 --- a/generalresearch/managers/thl/userhealth.py +++ b/generalresearch/managers/thl/userhealth.py @@ -6,7 +6,6 @@ from datetime import UTC, datetime, timedelta from itertools import zip_longest from typing import TYPE_CHECKING, Any -import faker from pydantic import NonNegativeInt, PositiveInt from generalresearch.decorators import LOG @@ -14,16 +13,17 @@ from generalresearch.managers.base import ( PostgresManager, PostgresManagerWithRedis, ) -from generalresearch.managers.thl.ipinfo import GeoIpInfoManager from generalresearch.models.thl.user_iphistory import ( IPRecord, UserIPHistory, UserIPRecord, ) +from generalresearch.models.thl.user_ref import UserRef from generalresearch.models.thl.userhealth import AuditLog if TYPE_CHECKING: from generalresearch.managers.base import Permission + from generalresearch.managers.thl.ipinfo import GeoIpInfoManager from generalresearch.models.custom_types import IPvAnyAddressStr from generalresearch.models.thl.product import Product from generalresearch.models.thl.user import User @@ -31,46 +31,22 @@ if TYPE_CHECKING: from generalresearch.pg_helper import PostgresConfig from generalresearch.redis_helper import RedisConfig -fake = faker.Faker() - class UserIpHistoryManager(PostgresManagerWithRedis): - def __init__( - self, - pg_config: PostgresConfig, - redis_config: RedisConfig, - permissions: Collection[Permission] | None = None, - cache_prefix: str | None = None, - ): - super().__init__( - pg_config=pg_config, - redis_config=redis_config, - permissions=permissions, - cache_prefix=cache_prefix, - ) - self.geoipinfo_manager = GeoIpInfoManager( - pg_config=pg_config, - redis_config=redis_config, - cache_prefix=cache_prefix, - ) + def __init__(self, geoip_info_manager: GeoIpInfoManager | None, **kwargs) -> None: + super().__init__(**kwargs) + self.geoip_info_manager = geoip_info_manager def get_redis_key(self, user_id: int) -> str: - return f"generalreserach:user-ip-history:{user_id}" + return f"generalreserach:user-ip-history-v2:{user_id}" def get_user_ip_records_sql(self, user_id: int) -> list[UserIPRecord]: # The IP metadata is ONLY for the 'ip', NOT for any forwarded ips. # This might get called immediately after a write, so use the non-rr res = self.pg_config.execute_sql_query( query=""" - SELECT iph.ip, iph.created, iph.user_id, - geo.subdivision_1_iso, - ipinfo.country_iso, - ipinfo.is_anonymous + SELECT iph.ip, iph.created FROM userhealth_iphistory iph - LEFT JOIN thl_ipinformation AS ipinfo - ON iph.ip = ipinfo.ip - LEFT JOIN thl_geoname AS geo - ON ipinfo.geoname_id = geo.geoname_id WHERE iph.user_id = %s AND created > NOW() - INTERVAL '28 days' ORDER BY iph.created DESC @@ -95,56 +71,63 @@ class UserIpHistoryManager(PostgresManagerWithRedis): value = iph.model_dump_json() self.redis_client.set(self.get_redis_key(user_id), value, ex=3 * 24 * 3600) - def recreate_user_ip_history_cache(self, user_id: int) -> None: - self.delete_user_ip_history_cache(user_id=user_id) - records = self.get_user_ip_records_sql(user_id=user_id) + def recreate_user_ip_history_cache(self, user: UserRef) -> None: + self.delete_user_ip_history_cache(user_id=user.user_id) + records = self.get_user_ip_records_sql(user_id=user.user_id) # todo: we may get dns records from somewhere else here ... - iph = UserIPHistory(user_id=user_id, ips=records) - self.set_user_ip_history_cache(user_id=user_id, iph=iph) + iph = UserIPHistory(user=user, ips=records) + self.set_user_ip_history_cache(user_id=user.user_id, iph=iph) - def get_user_ip_history(self, user_id: int) -> UserIPHistory: - assert isinstance(user_id, int) - iph = self.get_user_ip_history_cache(user_id=user_id) + def get_user_ip_history(self, user: UserRef | User) -> UserIPHistory: + assert self.geoip_info_manager is not None, "GeoIpInfoManager is required" + user = user if isinstance(user, UserRef) else user.to_user_ref() + + iph = self.get_user_ip_history_cache(user_id=user.user_id) if iph: LOG.debug(f"get_user_ip_history got in cache: {iph.model_dump_json()}") - else: LOG.debug("get_user_ip_history cache not found, using mysql") - records = self.get_user_ip_records_sql(user_id=user_id) + records = self.get_user_ip_records_sql(user_id=user.user_id) # todo: we may get dns records from somewhere else here ... - iph = UserIPHistory(user_id=user_id, ips=records) - self.set_user_ip_history_cache(user_id=user_id, iph=iph) - - iph.enrich_ips(pg_config=self.pg_config, redis_config=self.redis_config) + iph = UserIPHistory(user=user, ips=records) + self.set_user_ip_history_cache(user_id=user.user_id, iph=iph) + iph.enrich_ips(geoip_info_manager=self.geoip_info_manager) return iph - def get_user_latest_ip(self, user: User, exclude_anon: bool = False) -> str | None: - record = self.get_user_latest_ip_record(user=user, exclude_anon=exclude_anon) - if record: - return record.ip - return None - def get_user_latest_ip_record( - self, user: User, exclude_anon: bool = False + self, + user: UserRef | User, + exclude_anon: bool = False, ) -> UserIPRecord | None: - iphistory = self.get_user_ip_history(user_id=user.user_id) - - if iphistory.ips: - if exclude_anon: - return next( - filter( - lambda x: not x.information.is_anonymous, - iphistory.ips[::-1], - ), - None, - ) - else: - return iphistory.ips[-1] + iphistory = self.get_user_ip_history(user=user) + if not iphistory or not iphistory.ips: + return None + + # Note: UserIPHistory.ips is sorted by 'created DESC' !!! + # This logic was changed at some point? Or the py-utils + # get_user_latest_ip_record was changed. Please be careful here... + if exclude_anon: + for ipr in iphistory.ips: + if not ipr.information.is_anonymous: + return ipr + else: + ipr = iphistory.ips[0] + return ipr + def get_user_latest_ip( + self, + user: UserRef | User, + exclude_anon: bool = False, + ) -> str | None: + record = self.get_user_latest_ip_record(user=user, exclude_anon=exclude_anon) + if record: + return record.ip return None def get_user_latest_country( - self, user: User, exclude_anon: bool = False + self, + user: UserRef | User, + exclude_anon: bool = False, ) -> str | None: """Get the country the user is in, based off their latest ip.""" ipr = self.get_user_latest_ip_record(user, exclude_anon=exclude_anon) @@ -152,10 +135,8 @@ class UserIpHistoryManager(PostgresManagerWithRedis): # no IP history at all, so the record is None return ipr.country_iso if ipr is not None else None - def is_user_anonymous(self, user: User) -> bool | None: + def is_user_anonymous(self, user: UserRef | User) -> bool | None: # Get the user's latest ip. is it marked as anonymous? - # Note: it is possible we only did a "basic" lookup of this IP so - # we don't know if they are anonymous. Default to False # Return None if the user has no IP history at all ipr = self.get_user_latest_ip_record(user) if ipr: @@ -164,7 +145,6 @@ class UserIpHistoryManager(PostgresManagerWithRedis): class IPRecordManager(PostgresManagerWithRedis): - def __init__( self, pg_config: PostgresConfig, @@ -183,11 +163,12 @@ class IPRecordManager(PostgresManagerWithRedis): redis_config=self.redis_config, cache_prefix=self.cache_prefix, permissions=self.permissions, + geoip_info_manager=None, ) def create_unpack( self, - user_id: PositiveInt, + user_id: int, ip: IPvAnyAddressStr, forwarded_ips: list[str], ) -> IPRecord: @@ -200,14 +181,14 @@ class IPRecordManager(PostgresManagerWithRedis): def create( self, - user_id: PositiveInt, + user_id: int, ip: IPvAnyAddressStr, - forwarded_ip1: IPvAnyAddressStr, - forwarded_ip2: IPvAnyAddressStr, - forwarded_ip3: IPvAnyAddressStr, - forwarded_ip4: IPvAnyAddressStr, - forwarded_ip5: IPvAnyAddressStr, - forwarded_ip6: IPvAnyAddressStr, + forwarded_ip1: IPvAnyAddressStr | None, + forwarded_ip2: IPvAnyAddressStr | None, + forwarded_ip3: IPvAnyAddressStr | None, + forwarded_ip4: IPvAnyAddressStr | None, + forwarded_ip5: IPvAnyAddressStr | None, + forwarded_ip6: IPvAnyAddressStr | None, ) -> IPRecord: data = { @@ -253,11 +234,12 @@ class IPRecordManager(PostgresManagerWithRedis): """, params=data, ) - self.recreate_user_ip_history_cache(user_id=user_id) + self.delete_user_ip_history_cache(user_id=user_id) return IPRecord.from_mysql(data) - def get_user_latest_ip_record(self, user: User) -> IPRecord | None: + def get_user_latest_ip_record(self, user: UserRef | User) -> IPRecord | None: + user = user if isinstance(user, UserRef) else user.to_user_ref() res = self.filter_ip_records(user_ids=[user.user_id], limit=1) if res: return res[0] @@ -317,14 +299,13 @@ class IPRecordManager(PostgresManagerWithRedis): return [IPRecord.from_mysql(i) for i in res] - def recreate_user_ip_history_cache(self, user_id: int): - return self.user_ip_history_manager.recreate_user_ip_history_cache( + def delete_user_ip_history_cache(self, user_id: int): + return self.user_ip_history_manager.delete_user_ip_history_cache( user_id=user_id ) class AuditLogManager(PostgresManager): - def create( self, user_id: PositiveInt, |
