diff --git a/app/admin_model.py b/app/admin_model.py
index e9336bc8..a97d41e8 100644
--- a/app/admin_model.py
+++ b/app/admin_model.py
@@ -1,3 +1,4 @@
+from __future__ import annotations
from typing import Optional
import arrow
@@ -30,6 +31,8 @@ from app.models import (
Newsletter,
PADDLE_SUBSCRIPTION_GRACE_DAYS,
Mailbox,
+ DeletedAlias,
+ DomainDeletedAlias,
)
from app.newsletter_utils import send_newsletter_to_user, send_newsletter_to_address
@@ -729,6 +732,67 @@ class InvalidMailboxDomainAdmin(SLModelView):
can_delete = True
+class EmailSearchResult:
+ no_match: bool = True
+ alias: Optional[Alias] = None
+ mailbox: Optional[Mailbox] = None
+ deleted_alias: Optional[DeletedAlias] = None
+ deleted_custom_alias: Optional[DomainDeletedAlias] = None
+ user: Optional[User] = None
+
+ @staticmethod
+ def from_email(email: str) -> EmailSearchResult:
+ output = EmailSearchResult()
+ alias = Alias.get_by(email=email)
+ if alias:
+ output.alias = alias
+ output.no_match = False
+ return output
+ user = User.get_by(email=email)
+ if user:
+ output.user = user
+ output.no_match = False
+ return output
+ mailbox = Mailbox.get_by(email=email)
+ if mailbox:
+ output.mailbox = mailbox
+ output.no_match = False
+ return output
+ deleted_alias = DeletedAlias.get_by(email=email)
+ if deleted_alias:
+ output.deleted_alias = deleted_alias
+ output.no_match = False
+ return output
+ domain_deleted_alias = DomainDeletedAlias.get_by(email=email)
+ if domain_deleted_alias:
+ output.domain_deleted_alias = domain_deleted_alias
+ output.no_match = False
+ return output
+
+
+class EmailSearchHelpers:
+ @staticmethod
+ def mailbox_list(user: User) -> list[Mailbox]:
+ return (
+ Mailbox.filter_by(user_id=user.id)
+ .order_by(Mailbox.id.asc())
+ .limit(10)
+ .all()
+ )
+
+ @staticmethod
+ def mailbox_count(user: User) -> int:
+ return Mailbox.filter_by(user_id=user.id).order_by(Mailbox.id.asc()).count()
+
+ @staticmethod
+ def alias_list(user: User) -> list[Alias]:
+ return Alias.filter_by(user_id=user.id).order_by(Alias.id.asc()).limit(10).all()
+
+ @staticmethod
+ def alias_count(user: User) -> int:
+ return Alias.filter_by(user_id=user.id).count()
+
+
class EmailSearchAdmin(BaseView):
def is_accessible(self):
return current_user.is_authenticated and current_user.is_admin
@@ -740,50 +804,16 @@ class EmailSearchAdmin(BaseView):
@expose("/", methods=["GET", "POST"])
def index(self):
- alias = None
- user = None
- mailbox = None
- no_match = False
- email = None
+ search = EmailSearchResult()
+ email = ""
if request.form and request.form["email"]:
email = request.form["email"]
- alias = Alias.get_by(email=email)
- user = User.get_by(email=email)
- mailbox = Mailbox.get_by(email=email)
- if not alias and not user and not mailbox:
- no_match = True
-
- def user_mailboxes(user_id: int) -> list[Mailbox]:
- return (
- Mailbox.filter_by(user_id=user_id)
- .order_by(Mailbox.id.asc())
- .limit(10)
- .all()
- )
-
- def user_mailboxes_count(user_id: int) -> int:
- return Mailbox.filter_by(user_id=user_id).order_by(Mailbox.id.asc()).count()
-
- def user_aliases(user_id: int) -> list[Alias]:
- return (
- Alias.filter_by(user_id=user_id)
- .order_by(Alias.id.asc())
- .limit(10)
- .all()
- )
-
- def user_aliases_count(user_id: int) -> int:
- return Alias.filter_by(user_id=user_id).count()
+ email = email.strip()
+ search = EmailSearchResult.from_email(email)
return self.render(
"admin/email_search.html",
email=email,
- no_match=no_match,
- alias=alias,
- mailbox=mailbox,
- user=user,
- user_aliases=user_aliases,
- user_aliases_count=user_aliases_count,
- user_mailboxes=user_mailboxes,
- user_mailboxes_count=user_mailboxes_count,
+ data=search,
+ helper=EmailSearchHelpers,
)
diff --git a/app/alias_utils.py b/app/alias_utils.py
index e0aeb6ae..f4ff0d3b 100644
--- a/app/alias_utils.py
+++ b/app/alias_utils.py
@@ -63,12 +63,16 @@ def get_user_if_alias_would_auto_create(
# Prevent addresses with unicode characters (🤯) in them for now.
validate_email(address, check_deliverability=False, allow_smtputf8=False)
except EmailNotValidError:
+ LOG.i(f"Not creating alias for {address} because email is invalid")
return None
domain_and_rule = check_if_alias_can_be_auto_created_for_custom_domain(
address, notify_user=notify_user
)
if DomainDeletedAlias.get_by(email=address):
+ LOG.i(
+ f"Not creating alias for {address} because it was previously deleted for this domain"
+ )
return None
if domain_and_rule:
return domain_and_rule[0].user
@@ -93,6 +97,9 @@ def check_if_alias_can_be_auto_created_for_custom_domain(
custom_domain: CustomDomain = CustomDomain.get_by(domain=alias_domain)
if not custom_domain:
+ LOG.i(
+ f"Cannot auto-create custom domain alias for {address} because there's no custom domain for {alias_domain}"
+ )
return None
user: User = custom_domain.user
@@ -108,6 +115,9 @@ def check_if_alias_can_be_auto_created_for_custom_domain(
if not custom_domain.catch_all:
if len(custom_domain.auto_create_rules) == 0:
+ LOG.i(
+ f"Cannot create alias {address} for domain {custom_domain} because it has no catch-all and no rules"
+ )
return None
local = get_email_local_part(address)
@@ -121,7 +131,7 @@ def check_if_alias_can_be_auto_created_for_custom_domain(
)
return custom_domain, rule
else: # no rule passes
- LOG.d("no rule passed to create %s", local)
+ LOG.d(f"No rule matches auto-create {address} for domain {custom_domain}")
return None
LOG.d("Create alias via catchall")
@@ -148,6 +158,7 @@ def check_if_alias_can_be_auto_created_for_a_directory(
sep = "#"
else:
# if there's no directory separator in the alias, no way to auto-create it
+ LOG.info(f"Cannot auto-create {address} since it has no directory separator")
return None
directory_name = address[: address.find(sep)]
@@ -155,6 +166,9 @@ def check_if_alias_can_be_auto_created_for_a_directory(
directory = Directory.get_by(name=directory_name)
if not directory:
+ LOG.info(
+ f"Cannot auto-create {address} because there is no directory for {directory_name}"
+ )
return None
user: User = directory.user
@@ -163,12 +177,17 @@ def check_if_alias_can_be_auto_created_for_a_directory(
return None
if not user.can_create_new_alias():
- LOG.d(f"{user} can't create new directory alias {address}")
+ LOG.d(
+ f"{user} can't create new directory alias {address} because user cannot create aliases"
+ )
if notify_user:
send_cannot_create_directory_alias(user, address, directory_name)
return None
if directory.disabled:
+ LOG.d(
+ f"{user} can't create new directory alias {address} bcause directory is disabled"
+ )
if notify_user:
send_cannot_create_directory_alias_disabled(user, address, directory_name)
return None
diff --git a/app/email_utils.py b/app/email_utils.py
index 6048a99e..5ff34d06 100644
--- a/app/email_utils.py
+++ b/app/email_utils.py
@@ -548,7 +548,9 @@ def can_create_directory_for_address(email_address: str) -> bool:
for domain in config.ALIAS_DOMAINS:
if email_address.endswith("@" + domain):
return True
-
+ LOG.i(
+ f"Cannot create address in directory for {email_address} since it does not belong to a valid directory domain"
+ )
return False
diff --git a/templates/admin/email_search.html b/templates/admin/email_search.html
index 97a77955..537d03cb 100644
--- a/templates/admin/email_search.html
+++ b/templates/admin/email_search.html
@@ -50,7 +50,7 @@
{% endmacro %}
-{% macro list_alias(alias_count, aliases) %}
+{% macro list_alias(alias_count, aliases) %}
{{ alias_count }} Aliases found.
{% if alias_count>10 %}Showing only the first 10.{% endif %}
@@ -79,6 +79,100 @@
{% endmacro %}
+{% macro show_deleted_alias(deleted_alias) -%}
+
+ Deleted Alias {{ deleted_alias.email }} with ID {{ deleted_alias.id }}.
+
+
+
+
+
+ Deleted Alias ID
+ |
+
+ Email
+ |
+
+ Deleted At
+ |
+
+ Reason
+ |
+
+
+
+
+
+ {{ deleted_alias.id }}
+ |
+
+ {{ deleted_alias.email }}
+ |
+
+ {{ deleted_alias.created_at }}
+ |
+
+ {{ deleted_alias.reason }}
+ |
+
+
+
+{%- endmacro %}
+{% macro show_domain_deleted_alias(dom_deleted_alias) -%}
+
+ Domain Deleted Alias {{ dom_deleted_alias.email }} with ID {{ dom_deleted_alias.id }} for domain {{ dom_deleted_alias.domain.domain }}
+
+
+
+
+
+ Deleted Alias ID
+ |
+
+ Email
+ |
+
+ Domain
+ |
+
+ Domain ID
+ |
+
+ Domain owner user ID
+ |
+
+ Domain owner user email
+ |
+
+ Deleted At
+ |
+
+
+
+
+
+ {{ dom_deleted_alias.id }}
+ |
+
+ {{ dom_deleted_alias.email }}
+ |
+
+ {{ dom_deleted_alias.domain.domain }}
+ |
+
+ {{ dom_deleted_alias.domain.id }}
+ |
+
+ {{ dom_deleted_alias.domain.user_id }}
+ |
+
+ {{ dom_deleted_alias.created_at }}
+ |
+
+
+
+ {{ show_user(data.domain_deleted_alias.domain.user) }}
+{%- endmacro %}
{% block body %}
@@ -104,36 +198,54 @@
No user, alias or mailbox found for {{ email }}
{% endif %}
- {% if alias %}
+ {% if data.alias %}
- Found Alias {{ alias.email }}
+ Found Alias {{ data.alias.email }}
- {{ list_alias(1,[alias]) }}
- {{ show_user(alias.user) }}
- {{ list_mailboxes(user_mailboxes_count(alias.user.id), user_mailboxes(alias.user.id) ) }}
+ {{ list_alias(1,[data.alias]) }}
+ {{ show_user(data.alias.user) }}
+ {{ list_mailboxes(helper.mailbox_count(data.alias.user), helper.mailbox_list(data.alias.user) ) }}
{% endif %}
- {% if user %}
+ {% if data.user %}
- Found User {{ user.email }}
+ Found User {{ data.user.email }}
- {{ show_user(user) }}
- {{ list_mailboxes(user_mailboxes_count(user.id), user_mailboxes(user.id) ) }}
- {{ list_alias(user_aliases_count(user.id),user_aliases(user.id)) }}
+ {{ show_user(data.user) }}
+ {{ list_mailboxes(helper.mailbox_count(data.user), helper.mailbox_list(data.user) ) }}
+ {{ list_alias(helper.alias_count(data.user),helper.alias_list(data.user)) }}
{% endif %}
- {% if mailbox %}
+ {% if data.mailbox %}
- Found Mailbox {{ mailbox.email }}
+ Found Mailbox {{ data.mailbox.email }}
- {{ list_mailboxes(1, [mailbox] ) }}
- {{ show_user(mailbox.user) }}
+ {{ list_mailboxes(1, [data.mailbox] ) }}
+ {{ show_user(data.mailbox.user) }}
+
+ {% endif %}
+ {% if data.deleted_alias %}
+
+
+
+ Found DeletedAlias {{ data.deleted_alias.email }}
+
+ {{ show_deleted_alias(data.deleted_alias) }}
+
+ {% endif %}
+ {% if data.domain_deleted_alias %}
+
+
+
+ Found DomainDeletedAlias {{ data.domain_deleted_alias.email }}
+
+ {{ show_domain_deleted_alias(data.domain_deleted_alias) }}
{% endif %}
{% endblock %}