supports syncing of folders.
This commit is contained in:
@@ -12,20 +12,29 @@ class IMAPService:
|
|||||||
self.config = user.imap_config or {}
|
self.config = user.imap_config or {}
|
||||||
self.connection = None
|
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]:
|
def test_connection(self) -> Tuple[bool, str]:
|
||||||
"""Test IMAP connection with current configuration."""
|
"""Test IMAP connection with current configuration."""
|
||||||
try:
|
try:
|
||||||
if not self.config:
|
if not self.config:
|
||||||
return False, "No IMAP configuration found"
|
return False, "No IMAP configuration found"
|
||||||
|
|
||||||
# Create SSL context
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
|
|
||||||
# Connect to IMAP server
|
# Connect to IMAP server
|
||||||
self.connection = imaplib.IMAP4_SSL(
|
self._connect()
|
||||||
self.config.get('server', 'imap.gmail.com'),
|
|
||||||
self.config.get('port', 993)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Login
|
# Login
|
||||||
self.connection.login(
|
self.connection.login(
|
||||||
@@ -34,17 +43,27 @@ class IMAPService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Select inbox to verify connection
|
# 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()
|
self.connection.close()
|
||||||
|
|
||||||
|
# Logout
|
||||||
self.connection.logout()
|
self.connection.logout()
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
return True, "Connection successful"
|
return True, "Connection successful"
|
||||||
|
|
||||||
except imaplib.IMAP4.error as e:
|
except imaplib.IMAP4.error as e:
|
||||||
|
print(e)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return False, f"IMAP connection error: {str(e)}"
|
return False, f"IMAP connection error: {str(e)}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return False, f"Connection error: {str(e)}"
|
return False, f"Connection error: {str(e)}"
|
||||||
finally:
|
finally:
|
||||||
if self.connection:
|
if self.connection:
|
||||||
@@ -59,14 +78,8 @@ class IMAPService:
|
|||||||
if not self.config:
|
if not self.config:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Create SSL context
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
|
|
||||||
# Connect to IMAP server
|
# Connect to IMAP server
|
||||||
self.connection = imaplib.IMAP4_SSL(
|
self._connect()
|
||||||
self.config.get('server', 'imap.gmail.com'),
|
|
||||||
self.config.get('port', 993)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Login
|
# Login
|
||||||
self.connection.login(
|
self.connection.login(
|
||||||
@@ -79,6 +92,8 @@ class IMAPService:
|
|||||||
|
|
||||||
if status != 'OK':
|
if status != 'OK':
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
print(folder_data)
|
||||||
|
|
||||||
folders = []
|
folders = []
|
||||||
for folder_item in folder_data:
|
for folder_item in folder_data:
|
||||||
@@ -101,8 +116,6 @@ class IMAPService:
|
|||||||
'full_path': folder_name
|
'full_path': folder_name
|
||||||
})
|
})
|
||||||
|
|
||||||
# Close connection
|
|
||||||
self.connection.close()
|
|
||||||
self.connection.logout()
|
self.connection.logout()
|
||||||
|
|
||||||
return folders
|
return folders
|
||||||
@@ -163,5 +176,8 @@ class IMAPService:
|
|||||||
return True, f"Successfully synced {synced_count} folders"
|
return True, f"Successfully synced {synced_count} folders"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
print(e)
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
return False, f"Sync error: {str(e)}"
|
return False, f"Sync error: {str(e)}"
|
||||||
|
|||||||
@@ -205,7 +205,12 @@ def update_folder(folder_id):
|
|||||||
@login_required
|
@login_required
|
||||||
def imap_config_modal():
|
def imap_config_modal():
|
||||||
"""Return the IMAP configuration 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'
|
response.headers['HX-Trigger'] = 'open-modal'
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -213,6 +218,7 @@ def imap_config_modal():
|
|||||||
@login_required
|
@login_required
|
||||||
def test_imap_connection():
|
def test_imap_connection():
|
||||||
"""Test IMAP connection with provided configuration."""
|
"""Test IMAP connection with provided configuration."""
|
||||||
|
print("HELLO")
|
||||||
try:
|
try:
|
||||||
# Get form data
|
# Get form data
|
||||||
server = request.form.get('server')
|
server = request.form.get('server')
|
||||||
@@ -235,7 +241,7 @@ def test_imap_connection():
|
|||||||
errors['password'] = 'Password is required'
|
errors['password'] = 'Password is required'
|
||||||
|
|
||||||
if errors:
|
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-Retarget'] = '#imap-modal'
|
||||||
response.headers['HX-Reswap'] = 'outerHTML'
|
response.headers['HX-Reswap'] = 'outerHTML'
|
||||||
return response
|
return response
|
||||||
@@ -246,7 +252,7 @@ def test_imap_connection():
|
|||||||
'port': int(port),
|
'port': int(port),
|
||||||
'username': username,
|
'username': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
'use_ssl': use_ssl,
|
'use_ssl': False,
|
||||||
'use_tls': False,
|
'use_tls': False,
|
||||||
'connection_timeout': 30
|
'connection_timeout': 30
|
||||||
}
|
}
|
||||||
@@ -254,6 +260,7 @@ def test_imap_connection():
|
|||||||
# Test connection
|
# Test connection
|
||||||
temp_user = type('User', (), {'imap_config': test_config})()
|
temp_user = type('User', (), {'imap_config': test_config})()
|
||||||
imap_service = IMAPService(temp_user)
|
imap_service = IMAPService(temp_user)
|
||||||
|
print(temp_user, test_config)
|
||||||
success, message = imap_service.test_connection()
|
success, message = imap_service.test_connection()
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
@@ -266,8 +273,9 @@ def test_imap_connection():
|
|||||||
response.headers['HX-Retarget'] = '#imap-modal'
|
response.headers['HX-Retarget'] = '#imap-modal'
|
||||||
response.headers['HX-Reswap'] = 'outerHTML'
|
response.headers['HX-Reswap'] = 'outerHTML'
|
||||||
else:
|
else:
|
||||||
|
print(message)
|
||||||
response = make_response(render_template('partials/imap_config_modal.html',
|
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-Retarget'] = '#imap-modal'
|
||||||
response.headers['HX-Reswap'] = 'outerHTML'
|
response.headers['HX-Reswap'] = 'outerHTML'
|
||||||
|
|
||||||
@@ -275,8 +283,9 @@ def test_imap_connection():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("Error testing IMAP connection: %s", e)
|
logging.exception("Error testing IMAP connection: %s", e)
|
||||||
|
print(e)
|
||||||
errors = {'general': 'An unexpected error occurred. Please try again.'}
|
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-Retarget'] = '#imap-modal'
|
||||||
response.headers['HX-Reswap'] = 'outerHTML'
|
response.headers['HX-Reswap'] = 'outerHTML'
|
||||||
return response
|
return response
|
||||||
@@ -302,4 +311,5 @@ def sync_imap_folders():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception("Error syncing IMAP folders: %s", e)
|
logging.exception("Error syncing IMAP folders: %s", e)
|
||||||
return jsonify({'error': 'An unexpected error occurred. Please try again.'}), 500
|
print(e)
|
||||||
|
return jsonify({'error': 'An unexpected error occurred. Please try again.'}), 500
|
||||||
|
|||||||
@@ -28,6 +28,53 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<section id="folders-list" class="mb-12">
|
||||||
{% include 'partials/folders_list.html' %}
|
{% include 'partials/folders_list.html' %}
|
||||||
</section>
|
</section>
|
||||||
@@ -38,4 +85,4 @@
|
|||||||
|
|
||||||
{% block modal %}
|
{% block modal %}
|
||||||
{% include "partials/modal_holder.html" %}
|
{% include "partials/modal_holder.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{% for folder in folders %}
|
{% 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="card-body">
|
||||||
<div class="flex justify-between items-start mb-2">
|
<div class="flex justify-between items-start mb-2">
|
||||||
<h3 class="text-xl font-bold truncate">{{ folder.name }}</h3>
|
<h3 class="text-xl font-bold truncate">{{ folder.name }}</h3>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% 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">
|
<div class="text-5xl mb-4 text-primary">
|
||||||
<i class="fas fa-folder-open"></i>
|
<i class="fas fa-folder-open"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,6 +53,10 @@
|
|||||||
<i class="fas fa-plus mr-2"></i>
|
<i class="fas fa-plus mr-2"></i>
|
||||||
Create Folder
|
Create Folder
|
||||||
</button>
|
</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>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ services:
|
|||||||
tmpfs:
|
tmpfs:
|
||||||
- /tmp
|
- /tmp
|
||||||
- /run
|
- /run
|
||||||
|
user: "0:0"
|
||||||
|
|||||||
Reference in New Issue
Block a user