From 9bf0041aa3b8ee3bde2f937848090191dcbebbfe Mon Sep 17 00:00:00 2001 From: Lina Date: Thu, 6 Nov 2025 15:19:44 +0100 Subject: [PATCH 1/4] Inbox mail table --- enums.py | 1 + models.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/enums.py b/enums.py index 128b8e9..5c01ec7 100644 --- a/enums.py +++ b/enums.py @@ -178,6 +178,7 @@ class Tablenames(Enum): ADMIN_QUERY_MESSAGE_SUMMARY = "admin_query_message_summary" RELEASE_NOTIFICATION = "release_notification" TIMED_EXECUTIONS = "timed_executions" + INBOX_MAIL = "inbox_mail" def snake_case_to_pascal_case(self): # the type name (written in PascalCase) of a table is needed to create backrefs diff --git a/models.py b/models.py index 00e624e..ef72581 100644 --- a/models.py +++ b/models.py @@ -2512,3 +2512,35 @@ class TimedExecutions(Base): __table_args__ = {"schema": "global"} time_key = Column(String, unique=True, primary_key=True) # enums.TimedExecutionKey last_executed_at = Column(DateTime) + + +class InboxMail(Base): + __tablename__ = Tablenames.INBOX_MAIL.value + __table_args__ = {"schema": "global"} + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + organization_id = Column( + UUID(as_uuid=True), + ForeignKey(f"{Tablenames.ORGANIZATION.value}.id", ondelete="CASCADE"), + index=True, + ) + created_at = Column(DateTime, default=sql.func.now()) + send_from = Column(String) + send_to = Column(JSON) + subject = Column(String) + content = Column(String) + mark_as_important = Column(Boolean, default=False) + meta_data = Column(JSON) + is_seen = Column(Boolean, default=False) + being_worked_on = Column(Boolean, default=False) + parent_id = Column( + UUID(as_uuid=True), + ForeignKey(f"global.{Tablenames.INBOX_MAIL.value}.id", ondelete="SET NULL"), + index=True, + nullable=True, + ) + child_id = Column( + UUID(as_uuid=True), + ForeignKey(f"global.{Tablenames.INBOX_MAIL.value}.id", ondelete="SET NULL"), + index=True, + nullable=True, + ) From b3f8d919b4a1008817759fff3440971261ffd38f Mon Sep 17 00:00:00 2001 From: Lina Date: Thu, 6 Nov 2025 17:01:15 +0100 Subject: [PATCH 2/4] Create and get requests for inbox mail --- global_objects/inbox_mail.py | 47 ++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 global_objects/inbox_mail.py diff --git a/global_objects/inbox_mail.py b/global_objects/inbox_mail.py new file mode 100644 index 0000000..6b707fb --- /dev/null +++ b/global_objects/inbox_mail.py @@ -0,0 +1,47 @@ +from typing import Dict, List + +from ..session import session +from sqlalchemy import cast, String + +from submodules.model.business_objects import general +from submodules.model.models import InboxMail + + +def get_inbox_mail( + org_id: str, + user_email: str, +) -> List[InboxMail]: + return ( + session.query(InboxMail) + .filter(InboxMail.organization_id == org_id) + .filter(cast(InboxMail.send_to, String).like(f"%{user_email}%")) + .all() + ) + + +def create( + org_id: str, + send_from: str, + send_to: Dict, + subject: str, + content: str, + mark_as_important: bool, + meta_data: Dict, + parent_id: str = None, + child_id: str = None, + with_commit: bool = True, +) -> InboxMail: + obj = InboxMail( + organization_id=org_id, + send_from=send_from, + send_to=send_to, + subject=subject, + content=content, + mark_as_important=mark_as_important, + meta_data=meta_data, + parent_id=parent_id, + child_id=child_id, + ) + general.add(obj, with_commit) + + return obj From 662835912546e75a73be751ab23c62e95935409d Mon Sep 17 00:00:00 2001 From: LennartSchmidtKern Date: Wed, 12 Nov 2025 11:00:56 +0100 Subject: [PATCH 3/4] new model --- global_objects/inbox_mail.py | 118 +++++++++++++++++++++++++++-------- models.py | 31 +++++---- 2 files changed, 109 insertions(+), 40 deletions(-) diff --git a/global_objects/inbox_mail.py b/global_objects/inbox_mail.py index 6b707fb..c9faf55 100644 --- a/global_objects/inbox_mail.py +++ b/global_objects/inbox_mail.py @@ -1,47 +1,111 @@ -from typing import Dict, List +from typing import Dict, List, Optional, Any from ..session import session -from sqlalchemy import cast, String +from sqlalchemy import cast, String, func, desc from submodules.model.business_objects import general from submodules.model.models import InboxMail +from sqlalchemy import or_ -def get_inbox_mail( +def get_by_thread( org_id: str, - user_email: str, + user_id: str, + thread_id: str, ) -> List[InboxMail]: + return ( session.query(InboxMail) - .filter(InboxMail.organization_id == org_id) - .filter(cast(InboxMail.send_to, String).like(f"%{user_email}%")) + .filter( + InboxMail.organization_id == org_id, + InboxMail.thread_id == thread_id, + or_( + InboxMail.recipient_id == user_id, + InboxMail.sender_id == user_id, + ), + ) + .order_by(desc(InboxMail.created_at)) .all() ) -def create( +def get_overview_by_threads( + org_id: str, + user_id: str, + page: int = 1, + limit: int = 10, +) -> Dict[str, Any]: + subquery = ( + session.query( + InboxMail.thread_id, + func.max(InboxMail.created_at).label("latest_mail_time"), + ) + .filter( + InboxMail.organization_id == org_id, + or_( + InboxMail.recipient_id == user_id, + InboxMail.sender_id == user_id, + ), + ) + .group_by(InboxMail.thread_id) + .subquery() + ) + + query = ( + session.query(InboxMail) + .join( + subquery, + (InboxMail.thread_id == subquery.c.thread_id) + & (InboxMail.created_at == subquery.c.latest_mail_time), + ) + .order_by(desc(InboxMail.created_at)) + ) + + total_threads = query.count() + mails = query.offset((page - 1) * limit).limit(limit).all() + + return { + "totalThreads": total_threads, + "page": page, + "limit": limit, + "mails": mails, + } + + +def create_by_thread( org_id: str, - send_from: str, - send_to: Dict, + sender_id: str, + recipient_ids: List[str], subject: str, content: str, - mark_as_important: bool, - meta_data: Dict, - parent_id: str = None, - child_id: str = None, + meta_data: Optional[Dict] = None, + parent_id: Optional[str] = None, + thread_id: Optional[str] = None, + is_important: bool = False, with_commit: bool = True, -) -> InboxMail: - obj = InboxMail( - organization_id=org_id, - send_from=send_from, - send_to=send_to, - subject=subject, - content=content, - mark_as_important=mark_as_important, - meta_data=meta_data, - parent_id=parent_id, - child_id=child_id, - ) - general.add(obj, with_commit) +) -> List[InboxMail]: + mail_entities: List[InboxMail] = [] + + for rid in recipient_ids: + other_recipient_ids = [r for r in recipient_ids if r != rid] + + mail_entity = InboxMail( + organization_id=org_id, + sender_id=sender_id, + recipient_id=rid, + other_recipient_ids=other_recipient_ids, + subject=subject, + content=content, + meta_data=meta_data or {}, + thread_id=thread_id, + parent_id=parent_id, + is_important=is_important, + ) + + mail_entities.append(mail_entity) + + general.add_all(mail_entities) + if with_commit: + general.commit() - return obj + return mail_entities diff --git a/models.py b/models.py index 14d6dbd..faec1f4 100644 --- a/models.py +++ b/models.py @@ -2573,23 +2573,28 @@ class InboxMail(Base): index=True, ) created_at = Column(DateTime, default=sql.func.now()) - send_from = Column(String) - send_to = Column(JSON) - subject = Column(String) - content = Column(String) - mark_as_important = Column(Boolean, default=False) - meta_data = Column(JSON) - is_seen = Column(Boolean, default=False) - being_worked_on = Column(Boolean, default=False) - parent_id = Column( + sender_id = Column( UUID(as_uuid=True), - ForeignKey(f"global.{Tablenames.INBOX_MAIL.value}.id", ondelete="SET NULL"), + ForeignKey(f"{Tablenames.USER.value}.id", ondelete="SET NULL"), index=True, - nullable=True, ) - child_id = Column( + recipient_id = Column( UUID(as_uuid=True), - ForeignKey(f"global.{Tablenames.INBOX_MAIL.value}.id", ondelete="SET NULL"), + ForeignKey(f"{Tablenames.USER.value}.id", ondelete="CASCADE"), index=True, + ) + other_recipient_ids = Column(JSON) + thread_id = Column(UUID(as_uuid=True), index=True, default=uuid.uuid4()) + parent_id = Column( + UUID(as_uuid=True), + ForeignKey("global.inbox_mail.id", ondelete="SET NULL"), nullable=True, + index=True, ) + subject = Column(String) + content = Column(String) + meta_data = Column(JSON) + + is_seen = Column(Boolean, default=False) + is_important = Column(Boolean, default=False) + being_working_on = Column(Boolean, default=False) From 7809311ddef8fe9af2fbd04c9bb09d4bba40149d Mon Sep 17 00:00:00 2001 From: LennartSchmidtKern Date: Wed, 12 Nov 2025 17:53:17 +0100 Subject: [PATCH 4/4] mail collection by threads --- global_objects/inbox_mail.py | 29 ++++++++++++++++++++++++----- models.py | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/global_objects/inbox_mail.py b/global_objects/inbox_mail.py index c9faf55..db42ec7 100644 --- a/global_objects/inbox_mail.py +++ b/global_objects/inbox_mail.py @@ -1,4 +1,5 @@ from typing import Dict, List, Optional, Any +from submodules.model.util import sql_alchemy_to_dict from ..session import session from sqlalchemy import cast, String, func, desc @@ -51,7 +52,7 @@ def get_overview_by_threads( .subquery() ) - query = ( + latest_mails_query = ( session.query(InboxMail) .join( subquery, @@ -61,14 +62,33 @@ def get_overview_by_threads( .order_by(desc(InboxMail.created_at)) ) - total_threads = query.count() - mails = query.offset((page - 1) * limit).limit(limit).all() + total_threads = latest_mails_query.count() + latest_mails = latest_mails_query.offset((page - 1) * limit).limit(limit).all() + + threads = [] + for mail in latest_mails: + total_mails = ( + session.query(func.count(InboxMail.id)) + .filter( + InboxMail.thread_id == mail.thread_id, + InboxMail.organization_id == org_id, + ) + .scalar() + ) + + threads.append( + { + "threadId": mail.thread_id, + "latestMail": sql_alchemy_to_dict(mail), + "totalMails": total_mails, + } + ) return { "totalThreads": total_threads, "page": page, "limit": limit, - "mails": mails, + "threads": threads, } @@ -85,7 +105,6 @@ def create_by_thread( with_commit: bool = True, ) -> List[InboxMail]: mail_entities: List[InboxMail] = [] - for rid in recipient_ids: other_recipient_ids = [r for r in recipient_ids if r != rid] diff --git a/models.py b/models.py index faec1f4..b572b63 100644 --- a/models.py +++ b/models.py @@ -2584,7 +2584,7 @@ class InboxMail(Base): index=True, ) other_recipient_ids = Column(JSON) - thread_id = Column(UUID(as_uuid=True), index=True, default=uuid.uuid4()) + thread_id = Column(UUID(as_uuid=True), index=True, unique=True, default=uuid.uuid4) parent_id = Column( UUID(as_uuid=True), ForeignKey("global.inbox_mail.id", ondelete="SET NULL"),