feat: per newsletter folder move setting

This commit is contained in:
Leon
2025-07-17 14:36:53 +02:00
parent e915330a78
commit 19426e3108
13 changed files with 355 additions and 13 deletions

View File

@@ -51,7 +51,9 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
"""Create a new newsletter."""
logger.info(f"Creating new newsletter with name '{newsletter.name}'")
db_newsletter = Newsletter(
name=newsletter.name, extract_content=newsletter.extract_content
name=newsletter.name,
extract_content=newsletter.extract_content,
move_to_folder=newsletter.move_to_folder,
)
db.add(db_newsletter)
db.commit()
@@ -79,6 +81,8 @@ def update_newsletter(
return None
db_newsletter.name = newsletter_update.name
db_newsletter.move_to_folder = newsletter_update.move_to_folder
db_newsletter.extract_content = newsletter_update.extract_content
# Simple approach: delete existing senders and add new ones
for sender in db_newsletter.senders:

View File

@@ -11,6 +11,7 @@ class Newsletter(Base):
id = Column(Integer, primary_key=True, index=True)
name = Column(String)
move_to_folder = Column(String, nullable=True)
is_active = Column(Boolean, default=True)
extract_content = Column(Boolean, default=False)

View File

@@ -28,6 +28,7 @@ class NewsletterBase(BaseModel):
"""Base schema for a newsletter."""
name: str
move_to_folder: str | None = None
extract_content: bool = False

View File

@@ -146,9 +146,10 @@ def _process_single_email(
logger.debug(f"Marking email with id={num} as read")
mail.store(num, "+FLAGS", "\\Seen")
if settings.move_to_folder:
logger.debug(f"Moving email with id={num} to {settings.move_to_folder}")
mail.copy(num, settings.move_to_folder)
move_folder = newsletter.move_to_folder or settings.move_to_folder
if move_folder:
logger.debug(f"Moving email with id={num} to {move_folder}")
mail.copy(num, move_folder)
mail.store(num, "+FLAGS", "\\Deleted")

View File

@@ -110,6 +110,23 @@ def test_create_newsletter(db_session: Session):
assert newsletter.senders[0].email == unique_email
def test_create_newsletter_with_move_to_folder(db_session: Session):
"""Test creating a newsletter with the move_to_folder attribute."""
unique_email = f"sender_{uuid.uuid4()}@test.com"
newsletter_data = NewsletterCreate(
name="Test Newsletter with Folder",
sender_emails=[unique_email],
move_to_folder="Archive/Test",
extract_content=True,
)
newsletter = create_newsletter(db_session, newsletter_data)
retrieved_newsletter = get_newsletter(db_session, newsletter.id)
assert retrieved_newsletter.name == "Test Newsletter with Folder"
assert retrieved_newsletter.move_to_folder == "Archive/Test"
assert retrieved_newsletter.extract_content is True
def test_get_newsletter(db_session: Session):
"""Test getting a single newsletter."""
unique_email = f"sender_{uuid.uuid4()}@test.com"

View File

@@ -0,0 +1,97 @@
import imaplib
from email.message import Message
from unittest.mock import MagicMock
from sqlalchemy.orm import Session
from app.crud.newsletters import create_newsletter
from app.crud.settings import create_or_update_settings
from app.schemas.newsletters import NewsletterCreate
from app.schemas.settings import SettingsCreate
from app.services.email_processor import _process_single_email
def test_process_single_email_with_newsletter_move_folder(db_session: Session):
"""Test that the per-newsletter move_to_folder is used when set, overriding the global setting."""
# 1. ARRANGE
# Global settings with a move folder
settings_data = SettingsCreate(
imap_server="test.com",
imap_username="test",
imap_password="password",
move_to_folder="GlobalArchive",
)
settings = create_or_update_settings(db_session, settings_data)
# Newsletter with a specific move folder
newsletter_data = NewsletterCreate(
name="Test Newsletter",
sender_emails=["test@example.com"],
)
newsletter = create_newsletter(db_session, newsletter_data)
newsletter.move_to_folder = "NewsletterArchive"
db_session.commit()
# Mock IMAP mail object
mock_mail = MagicMock(spec=imaplib.IMAP4_SSL)
# Mock email message
msg = Message()
msg["From"] = "test@example.com"
msg["Subject"] = "Test Email"
msg["Message-ID"] = "<test-message-id>"
# Mock mail.fetch to return the message
mock_mail.fetch.return_value = ("OK", [(b"1 (RFC822)", msg.as_bytes())])
sender_map = {"test@example.com": newsletter}
# 2. ACT
_process_single_email("1", mock_mail, db_session, sender_map, settings)
# 3. ASSERT
# Check that the email was moved to the newsletter-specific folder
mock_mail.copy.assert_called_once_with("1", "NewsletterArchive")
mock_mail.store.assert_any_call("1", "+FLAGS", "\\Deleted")
def test_process_single_email_with_global_move_folder(db_session: Session):
"""Test that the global move_to_folder is used when the per-newsletter setting is not set."""
# 1. ARRANGE
# Global settings with a move folder
settings_data = SettingsCreate(
imap_server="test.com",
imap_username="test",
imap_password="password",
move_to_folder="GlobalArchive",
)
settings = create_or_update_settings(db_session, settings_data)
# Newsletter without a specific move folder
newsletter_data = NewsletterCreate(
name="Test Newsletter",
sender_emails=["test@example.com"],
)
newsletter = create_newsletter(db_session, newsletter_data)
# Mock IMAP mail object
mock_mail = MagicMock(spec=imaplib.IMAP4_SSL)
# Mock email message
msg = Message()
msg["From"] = "test@example.com"
msg["Subject"] = "Test Email"
msg["Message-ID"] = "<test-message-id-2>"
# Mock mail.fetch to return the message
mock_mail.fetch.return_value = ("OK", [(b"1 (RFC822)", msg.as_bytes())])
sender_map = {"test@example.com": newsletter}
# 2. ACT
_process_single_email("1", mock_mail, db_session, sender_map, settings)
# 3. ASSERT
# Check that the email was moved to the global folder
mock_mail.copy.assert_called_once_with("1", "GlobalArchive")
mock_mail.store.assert_any_call("1", "+FLAGS", "\\Deleted")