supports syncing of folders.

This commit is contained in:
2025-08-04 16:35:13 -07:00
parent 3fa43432d5
commit 8bf00e9a3b
5 changed files with 106 additions and 28 deletions

View File

@@ -12,20 +12,29 @@ class IMAPService:
self.config = user.imap_config or {}
self.connection = None
def _connect(self):
"""Create an IMAP connection based on configuration."""
server = self.config.get('server', 'imap.gmail.com')
port = self.config.get('port', 993)
use_ssl = self.config.get('use_ssl', True)
if use_ssl:
# Create SSL context
context = ssl.create_default_context()
# Connect using SSL
self.connection = imaplib.IMAP4_SSL(server, port)
else:
# Connect without SSL
self.connection = imaplib.IMAP4(server, port)
def test_connection(self) -> Tuple[bool, str]:
"""Test IMAP connection with current configuration."""
try:
if not self.config:
return False, "No IMAP configuration found"
# Create SSL context
context = ssl.create_default_context()
# Connect to IMAP server
self.connection = imaplib.IMAP4_SSL(
self.config.get('server', 'imap.gmail.com'),
self.config.get('port', 993)
)
self._connect()
# Login
self.connection.login(
@@ -34,17 +43,27 @@ class IMAPService:
)
# Select inbox to verify connection
self.connection.select('INBOX')
resp_code, content = self.connection.select('INBOX')
print(resp_code, content)
# Close connection
# Close the folder, not the connection
self.connection.close()
# Logout
self.connection.logout()
self.connection = None
return True, "Connection successful"
except imaplib.IMAP4.error as e:
print(e)
import traceback
traceback.print_exc()
return False, f"IMAP connection error: {str(e)}"
except Exception as e:
print(e)
import traceback
traceback.print_exc()
return False, f"Connection error: {str(e)}"
finally:
if self.connection:
@@ -59,14 +78,8 @@ class IMAPService:
if not self.config:
return []
# Create SSL context
context = ssl.create_default_context()
# Connect to IMAP server
self.connection = imaplib.IMAP4_SSL(
self.config.get('server', 'imap.gmail.com'),
self.config.get('port', 993)
)
self._connect()
# Login
self.connection.login(
@@ -80,6 +93,8 @@ class IMAPService:
if status != 'OK':
return []
print(folder_data)
folders = []
for folder_item in folder_data:
if isinstance(folder_item, bytes):
@@ -101,8 +116,6 @@ class IMAPService:
'full_path': folder_name
})
# Close connection
self.connection.close()
self.connection.logout()
return folders
@@ -163,5 +176,8 @@ class IMAPService:
return True, f"Successfully synced {synced_count} folders"
except Exception as e:
import traceback
traceback.print_exc()
print(e)
db.session.rollback()
return False, f"Sync error: {str(e)}"

View File

@@ -205,7 +205,12 @@ def update_folder(folder_id):
@login_required
def imap_config_modal():
"""Return the IMAP configuration modal."""
response = make_response(render_template('partials/imap_config_modal.html'))
# Pass existing IMAP config to the template if it exists
response = make_response(render_template('partials/imap_config_modal.html',
server=current_user.imap_config.get('server') if current_user.imap_config else None,
port=current_user.imap_config.get('port') if current_user.imap_config else None,
username=current_user.imap_config.get('username') if current_user.imap_config else None,
use_ssl=current_user.imap_config.get('use_ssl', True) if current_user.imap_config else True))
response.headers['HX-Trigger'] = 'open-modal'
return response
@@ -213,6 +218,7 @@ def imap_config_modal():
@login_required
def test_imap_connection():
"""Test IMAP connection with provided configuration."""
print("HELLO")
try:
# Get form data
server = request.form.get('server')
@@ -235,7 +241,7 @@ def test_imap_connection():
errors['password'] = 'Password is required'
if errors:
response = make_response(render_template('partials/imap_config_modal.html', errors=errors))
response = make_response(render_template('partials/imap_config_modal.html', errors=errors, server=server, port=port, username=username, use_ssl=use_ssl))
response.headers['HX-Retarget'] = '#imap-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
@@ -246,7 +252,7 @@ def test_imap_connection():
'port': int(port),
'username': username,
'password': password,
'use_ssl': use_ssl,
'use_ssl': False,
'use_tls': False,
'connection_timeout': 30
}
@@ -254,6 +260,7 @@ def test_imap_connection():
# Test connection
temp_user = type('User', (), {'imap_config': test_config})()
imap_service = IMAPService(temp_user)
print(temp_user, test_config)
success, message = imap_service.test_connection()
if success:
@@ -266,8 +273,9 @@ def test_imap_connection():
response.headers['HX-Retarget'] = '#imap-modal'
response.headers['HX-Reswap'] = 'outerHTML'
else:
print(message)
response = make_response(render_template('partials/imap_config_modal.html',
errors={'general': message}))
errors={'general': message}, server=server, port=port, username=username, use_ssl=use_ssl))
response.headers['HX-Retarget'] = '#imap-modal'
response.headers['HX-Reswap'] = 'outerHTML'
@@ -275,8 +283,9 @@ def test_imap_connection():
except Exception as e:
logging.exception("Error testing IMAP connection: %s", e)
print(e)
errors = {'general': 'An unexpected error occurred. Please try again.'}
response = make_response(render_template('partials/imap_config_modal.html', errors=errors))
response = make_response(render_template('partials/imap_config_modal.html', errors=errors, server=server, port=port, username=username, use_ssl=use_ssl))
response.headers['HX-Retarget'] = '#imap-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
@@ -302,4 +311,5 @@ def sync_imap_folders():
except Exception as e:
logging.exception("Error syncing IMAP folders: %s", e)
print(e)
return jsonify({'error': 'An unexpected error occurred. Please try again.'}), 500

View File

@@ -28,6 +28,53 @@
</button>
</div>
<!-- Welcome Section -->
<div class="mb-8 p-6 bg-base-100 rounded-box shadow-lg border border-base-300">
<h3 class="text-xl font-bold mb-2">Welcome to Email Organizer!</h3>
<p class="text-base-content/80 mb-4">Organize your emails automatically with AI-powered rules. Create folders and set up rules to categorize incoming emails.</p>
<div class="flex space-x-4">
<button class="btn btn-primary" hx-get="/api/folders/new" hx-target="#modal-holder"
hx-swap="innerHTML">
<i class="fas fa-plus mr-2"></i>
Create Your First Folder
</button>
<button class="btn btn-outline" hx-get="/api/imap/config" hx-target="#modal-holder"
hx-swap="innerHTML">
<i class="fas fa-cog mr-2"></i>
Configure IMAP
</button>
</div>
</div>
<!-- Stats Section -->
<div class="mb-8 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="card bg-base-100 shadow-md border border-base-300 p-4">
<div class="text-2xl font-bold text-primary">{{ folders|length }}</div>
<div class="text-sm text-base-content/70">Total Folders</div>
</div>
<div class="card bg-base-100 shadow-md border border-base-300 p-4">
<div class="text-2xl font-bold text-secondary">0</div>
<div class="text-sm text-base-content/70">Emails Processed</div>
</div>
<div class="card bg-base-100 shadow-md border border-base-300 p-4">
<div class="text-2xl font-bold text-info">0</div>
<div class="text-sm text-base-content/70">Active Rules</div>
</div>
</div>
<!-- Search and Filter -->
<div class="mb-6 flex justify-between items-center">
<div class="relative w-64">
<input type="text" placeholder="Search folders..." class="input input-bordered w-full pr-10">
<i class="fas fa-search absolute right-3 top-3 text-base-content/50"></i>
</div>
<div class="flex space-x-2">
<button class="btn btn-sm btn-outline">All</button>
<button class="btn btn-sm btn-outline">High Priority</button>
<button class="btn btn-sm btn-outline">Normal</button>
</div>
</div>
<section id="folders-list" class="mb-12">
{% include 'partials/folders_list.html' %}
</section>

View File

@@ -1,6 +1,6 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for folder in folders %}
<div class="card bg-base-100 shadow-xl border border-base-300">
<div class="card bg-base-100 shadow-xl border border-base-300 hover:shadow-lg transition-shadow duration-200">
<div class="card-body">
<div class="flex justify-between items-start mb-2">
<h3 class="text-xl font-bold truncate">{{ folder.name }}</h3>
@@ -39,7 +39,7 @@
</div>
</div>
{% else %}
<div class="col-span-full text-center py-12 bg-base-100 rounded-box shadow-lg">
<div class="col-span-full text-center py-12 bg-base-100 rounded-box shadow-lg border border-dashed border-base-300">
<div class="text-5xl mb-4 text-primary">
<i class="fas fa-folder-open"></i>
</div>
@@ -53,6 +53,10 @@
<i class="fas fa-plus mr-2"></i>
Create Folder
</button>
<div class="mt-4 text-sm text-base-content/70">
<p>Need help setting up your first folder?</p>
<a href="#" class="link link-primary">View tutorial</a>
</div>
</div>
{% endfor %}
</div>

View File

@@ -30,3 +30,4 @@ services:
tmpfs:
- /tmp
- /run
user: "0:0"