From c0aee026ca5d6fefed962fd3c3db9997c8016259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Fri, 26 Jun 2026 12:01:28 +0200 Subject: [PATCH 1/5] Log errors only if messages cannot be processed feat(admin): add domain ID and uid: search to custom domain search Plain integers now search by domain ID; user lookup requires either an email address (contains @) or the uid: prefix. Adds full unit test coverage for all search paths. Co-Authored-By: Claude Sonnet 4.6 (1M context) User the const for the alias count to display a warning --- app/admin/custom_domain_search.py | 70 ++++++---- app/admin/email_search.py | 3 +- app/message_utils.py | 8 +- templates/admin/custom_domain_search.html | 4 +- templates/admin/email_search.html | 7 +- tests/admin/test_custom_domain_search.py | 158 +++++++++++++++++++++- 6 files changed, 217 insertions(+), 33 deletions(-) diff --git a/app/admin/custom_domain_search.py b/app/admin/custom_domain_search.py index 9744c00c9..3aa07d6e3 100644 --- a/app/admin/custom_domain_search.py +++ b/app/admin/custom_domain_search.py @@ -48,33 +48,31 @@ def __init__(self): @staticmethod def search(query: str) -> CustomDomainSearchResult: - """Search for custom domains by exact match or POSIX regex.""" + """Search for custom domains by exact match or POSIX regex. + + - Numeric query: search by domain ID + - Query with '@': search by user email + - Query with 'uid:': search by user ID + - Otherwise: exact domain match, then regex on domain names + """ output = CustomDomainSearchResult() output.query = query - # Try exact domain match first - domain = CustomDomain.get_by(domain=query) - if domain: - output.domains = [CustomDomainSearchHelpers.get_validation_data(domain)] - output.found_by_regex = False - output.no_match = False - return output - - # Try searching by user email - user = User.get_by(email=query) - if user: - output.domains = [ - CustomDomainSearchHelpers.get_validation_data(d) - for d in user.custom_domains - ] - output.found_by_regex = False - output.no_match = len(output.domains) == 0 + # Search by domain ID if query is a plain integer + try: + domain_id = int(query) + domain = CustomDomain.get(domain_id) + if domain: + output.domains = [CustomDomainSearchHelpers.get_validation_data(domain)] + output.found_by_regex = False + output.no_match = False return output + except ValueError: + pass - # Try searching by user ID - try: - user_id = int(query) - user = User.get(user_id) + # Search by user email if query contains '@' + if "@" in query: + user = User.get_by(email=query) if user: output.domains = [ CustomDomainSearchHelpers.get_validation_data(d) @@ -82,9 +80,31 @@ def search(query: str) -> CustomDomainSearchResult: ] output.found_by_regex = False output.no_match = len(output.domains) == 0 - return output - except ValueError: - pass + return output + + # Search by user ID if query has the form 'uid:' + if query.startswith("uid:"): + try: + user_id = int(query[4:]) + user = User.get(user_id) + if user: + output.domains = [ + CustomDomainSearchHelpers.get_validation_data(d) + for d in user.custom_domains + ] + output.found_by_regex = False + output.no_match = len(output.domains) == 0 + except ValueError: + pass + return output + + # Try exact domain match first + domain = CustomDomain.get_by(domain=query) + if domain: + output.domains = [CustomDomainSearchHelpers.get_validation_data(domain)] + output.found_by_regex = False + output.no_match = False + return output # Try regex search on domain names domains = ( diff --git a/app/admin/email_search.py b/app/admin/email_search.py index 95cda96c7..f9e61816b 100644 --- a/app/admin/email_search.py +++ b/app/admin/email_search.py @@ -315,6 +315,7 @@ def from_request(query: str, search_type: str) -> EmailSearchResult: class EmailSearchHelpers: PAGE_SIZE = 25 + ALIAS_DISPLAY_LIMIT = 5000 @staticmethod def mailbox_list( @@ -379,7 +380,7 @@ def alias_mailbox_count(alias: Alias) -> int: @staticmethod def alias_list(user: User, page: int = 1) -> list[Alias]: - """Get aliases for user with pagination (50 per page). + """Get aliases for user with pagination. Args: user: The user to get aliases for diff --git a/app/message_utils.py b/app/message_utils.py index 34f57d4fd..38faf60c2 100644 --- a/app/message_utils.py +++ b/app/message_utils.py @@ -11,11 +11,15 @@ def message_to_bytes(msg: Message) -> bytes: """replace Message.as_bytes() method by trying different policies""" + errors = [] for generator_policy in [None, policy.SMTP, policy.SMTPUTF8]: try: return msg.as_bytes(policy=generator_policy) - except Exception: - LOG.w("as_bytes() fails with %s policy", policy, exc_info=True) + except Exception as e: + errors.append((generator_policy, e)) + + for generator_policy, e in errors: + LOG.w("as_bytes() fails with %s policy: %s", generator_policy, e) msg_string = msg.as_string() try: diff --git a/templates/admin/custom_domain_search.html b/templates/admin/custom_domain_search.html index 5d8cd59a5..119c87932 100644 --- a/templates/admin/custom_domain_search.html +++ b/templates/admin/custom_domain_search.html @@ -971,9 +971,9 @@
name="query" id="query" value="{{ query or '' }}" - placeholder="domain.com, user@email.com, user ID, or POSIX regex"> + placeholder="domain.com, domain ID, user@email.com, uid:123, or POSIX regex"> - Search by domain name, user email, user ID, or use POSIX regex patterns (limited to 10 results). + Search by domain name or domain ID (integer), user email (must contain @), user ID (uid:<int>), or POSIX regex (limited to 10 results).