Merge branch 'master' of ssh://raspberrypi/~/git/email-organizer
This commit is contained in:
10
.env
10
.env
@@ -1,5 +1,9 @@
|
||||
SECRET_KEY=your-secret-key-here
|
||||
DATABASE_URL=postgresql://postgres:password@localhost:5432/email_organizer_dev
|
||||
OPENAI_API_KEY=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
|
||||
OPENAI_BASE_URL=https://openrouter.ai/api/v1
|
||||
OPENAI_MODEL=qwen/qwen3-coder
|
||||
# OPENAI_API_KEY=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
|
||||
# OPENAI_BASE_URL=https://openrouter.ai/api/v1
|
||||
# OPENAI_MODEL=qwen/qwen3-coder
|
||||
#
|
||||
OPENAI_API_KEY=aaoeu
|
||||
OPENAI_BASE_URL=http://localhost:8082/v1
|
||||
OPENAI_MODEL=
|
||||
|
||||
@@ -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(
|
||||
@@ -79,6 +92,8 @@ class IMAPService:
|
||||
|
||||
if status != 'OK':
|
||||
return []
|
||||
|
||||
print(folder_data)
|
||||
|
||||
folders = []
|
||||
for folder_item in folder_data:
|
||||
@@ -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)}"
|
||||
return False, f"Sync error: {str(e)}"
|
||||
|
||||
@@ -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)
|
||||
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,65 @@
|
||||
</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>
|
||||
{% if current_user.imap_config %}
|
||||
<button class="btn btn-outline" hx-post="/api/imap/sync" hx-target="#folders-list"
|
||||
hx-swap="innerHTML">
|
||||
<i class="fas fa-sync mr-2"></i>
|
||||
Sync Folders
|
||||
</button>
|
||||
<button class="btn btn-outline" hx-get="/api/imap/config" hx-target="#modal-holder"
|
||||
hx-swap="innerHTML">
|
||||
<i class="fas fa-cog"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</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>
|
||||
@@ -38,4 +97,4 @@
|
||||
|
||||
{% block modal %}
|
||||
{% 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">
|
||||
{% 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>
|
||||
|
||||
@@ -30,3 +30,4 @@ services:
|
||||
tmpfs:
|
||||
- /tmp
|
||||
- /run
|
||||
user: "0:0"
|
||||
|
||||
@@ -34,10 +34,10 @@ class TestIMAPRoutes:
|
||||
|
||||
response = client.post('/api/imap/test', data={
|
||||
'server': 'test.com',
|
||||
'port': '993',
|
||||
'username': 'test@test.com',
|
||||
'password': 'testpass',
|
||||
'use_ssl': 'on'
|
||||
'port': '5153',
|
||||
'username': 'user1@example.com',
|
||||
'password': 'password1',
|
||||
'use_ssl': 'off'
|
||||
})
|
||||
|
||||
assert response.status_code == 200
|
||||
@@ -60,4 +60,4 @@ class TestIMAPRoutes:
|
||||
|
||||
response = client.post('/api/imap/sync')
|
||||
# Should fail without real IMAP server but return proper response
|
||||
assert response.status_code in [200, 400]
|
||||
assert response.status_code in [200, 400]
|
||||
|
||||
Reference in New Issue
Block a user