Files
email-organizer/app/routes/folders.py
2025-08-06 21:51:57 -07:00

314 lines
13 KiB
Python

from flask import Blueprint, render_template, request, jsonify, make_response
from flask_login import login_required, current_user
from app import db
from app.models import Folder
import logging
folders_bp = Blueprint('folders', __name__)
@folders_bp.route('/')
@login_required
def index():
"""Main page showing all folders."""
# Get folders for the current authenticated user
folders = Folder.query.filter_by(user_id=current_user.id).all()
return render_template('index.html',
folders=folders,
show_hidden=False)
@folders_bp.route('/api/folders/new', methods=['GET'])
@login_required
def new_folder_modal():
"""Return the add folder modal."""
response = make_response(render_template('partials/folder_modal.html'))
response.headers['HX-Trigger'] = 'open-modal'
return response
@folders_bp.route('/api/folders', methods=['POST'])
@login_required
def add_folder():
"""Add a new folder."""
try:
# Get form data instead of JSON
name = request.form.get('name')
rule_text = request.form.get('rule_text')
priority = request.form.get('priority')
# Server-side validation
errors = {}
if not name or not name.strip():
errors['name'] = 'Folder name is required'
elif len(name.strip()) < 3:
errors['name'] = 'Folder name must be at least 3 characters'
elif len(name.strip()) > 50:
errors['name'] = 'Folder name must be less than 50 characters'
if not rule_text or not rule_text.strip():
errors['rule_text'] = 'Rule text is required'
elif len(rule_text.strip()) < 10:
errors['rule_text'] = 'Rule text must be at least 10 characters'
elif len(rule_text.strip()) > 200:
errors['rule_text'] = 'Rule text must be less than 200 characters'
# If there are validation errors, return the modal with errors
if errors:
response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority))
response.headers['HX-Retarget'] = '#folder-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
# Create new folder for the current user
# Default to 'destination' type for manually created folders
folder_type = 'tidy' if name.strip().lower() == 'inbox' else 'destination'
# If folder_type is 'ignore', reset emails_count to 0
if folder_type == 'ignore':
emails_count = 0
folder = Folder(
user_id=current_user.id,
name=name.strip(),
rule_text=rule_text.strip(),
priority=int(priority) if priority else 0,
folder_type=folder_type,
emails_count=0 if folder_type == 'ignore' else None
)
db.session.add(folder)
db.session.commit()
# Get updated list of folders for the current user
folders = Folder.query.filter_by(user_id=current_user.id).all()
response = make_response(render_template('partials/folders_list.html', folders=folders))
response.headers['HX-Trigger'] = 'close-modal'
response.headers["HX-Target"] = '#folders-list'
response.status_code = 201
return response
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error adding folder: %s", e)
db.session.rollback()
# Return error in modal
errors = {'general': 'An unexpected error occurred. Please try again.'}
# Get updated lists of folders by type for error fallback
response = make_response(render_template('partials/folder_modal.html', errors=errors, name=name, rule_text=rule_text, priority=priority))
response.headers['HX-Retarget'] = '#folder-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
@folders_bp.route('/api/folders/<folder_id>', methods=['DELETE'])
@login_required
def delete_folder(folder_id):
"""Delete a folder."""
try:
# Find the folder by ID and ensure it belongs to the current user
folder = Folder.query.filter_by(id=folder_id, user_id=current_user.id).first()
if not folder:
# Folder not found
folders = Folder.query.filter_by(user_id=current_user.id).all()
return render_template('partials/folders_list.html', folders=folders)
# Delete all associated processed emails first
from app.models import ProcessedEmail
ProcessedEmail.query.filter_by(folder_id=folder.id).delete()
# Delete the folder
db.session.delete(folder)
db.session.commit()
folders = Folder.query.filter_by(user_id=current_user.id).all()
return render_template('partials/folders_list.html', folders=folders)
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error deleting folder: %s", e)
db.session.rollback()
# Return the folders list unchanged
folders = Folder.query.filter_by(user_id=current_user.id).all()
# Return both sections
return render_template('partials/folders_list.html', folders=folders)
@folders_bp.route('/api/folders/<folder_id>/type', methods=['PUT'])
@login_required
def update_folder_type(folder_id):
"""Update the type of a folder."""
try:
# Find the folder by ID and ensure it belongs to the current user
folder = Folder.query.filter_by(id=folder_id, user_id=current_user.id).first()
if not folder:
return jsonify({'error': 'Folder not found'}), 404
# Get the new folder type from form data
new_folder_type = request.form.get('folder_type')
# Validate the folder type
if new_folder_type not in ['tidy', 'destination', 'ignore']:
return jsonify({'error': 'Invalid folder type'}), 400
# If changing to 'ignore', reset the emails_count to 0
if new_folder_type == 'ignore' and folder.folder_type != 'ignore':
folder.emails_count = 0
# Update the folder type
folder.folder_type = new_folder_type
db.session.commit()
# Get updated list of folders for the current user
folders = Folder.query.filter_by(user_id=current_user.id).all()
return render_template('partials/folders_list.html', folders=folders)
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error updating folder type: %s", e)
db.session.rollback()
return jsonify({'error': 'An unexpected error occurred'}), 500
@folders_bp.route('/api/folders/<folder_id>/edit', methods=['GET'])
@login_required
def edit_folder_modal(folder_id):
"""Return the edit folder modal with folder data."""
try:
# Find the folder by ID and ensure it belongs to the current user
folder = Folder.query.filter_by(id=folder_id, user_id=current_user.id).first()
if not folder:
return jsonify({'error': 'Folder not found'}), 404
# Return the edit folder modal with folder data
response = make_response(render_template('partials/folder_modal.html', folder=folder))
response.headers['HX-Trigger'] = 'open-modal'
return response
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error getting folder for edit: %s", e)
return jsonify({'error': 'Error retrieving folder'}), 500
@folders_bp.route('/api/folders/<folder_id>', methods=['PUT'])
@login_required
def update_folder(folder_id):
"""Update an existing folder."""
try:
# Find the folder by ID and ensure it belongs to the current user
folder = Folder.query.filter_by(id=folder_id, user_id=current_user.id).first()
if not folder:
# Folder not found
folders = Folder.query.filter_by(user_id=current_user.id).all()
return render_template('partials/folders_list.html', folders=folders)
# Get form data
name = request.form.get('name')
rule_text = request.form.get('rule_text')
priority = request.form.get('priority')
# Server-side validation
errors = {}
if not name or not name.strip():
errors['name'] = 'Folder name is required'
elif len(name.strip()) < 3:
errors['name'] = 'Folder name must be at least 3 characters'
elif len(name.strip()) > 50:
errors['name'] = 'Folder name must be less than 50 characters'
if not rule_text or not rule_text.strip():
errors['rule_text'] = 'Rule text is required'
elif len(rule_text.strip()) < 10:
errors['rule_text'] = 'Rule text must be at least 10 characters'
elif len(rule_text.strip()) > 200:
errors['rule_text'] = 'Rule text must be less than 200 characters'
# If there are validation errors, return the modal with errors
if errors:
response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority))
response.headers['HX-Retarget'] = '#folder-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
# Update folder
folder.name = name.strip()
folder.rule_text = rule_text.strip()
folder.priority = int(priority) if priority else 0
# Check if folder type is being changed to 'ignore'
old_folder_type = folder.folder_type
folder.folder_type = 'tidy' if name.strip().lower() == 'inbox' else 'destination'
# If changing to 'ignore', reset emails_count to 0
if folder.folder_type == 'ignore' and old_folder_type != 'ignore':
folder.emails_count = 0
db.session.commit()
# Get updated list of folders for the current user
folders = Folder.query.filter_by(user_id=current_user.id).all()
# Return both sections
section = render_template('partials/folders_list.html', folders=folders)
response = make_response(section)
response.headers['HX-Trigger'] = 'close-modal'
return response
except Exception as e:
# Print unhandled exceptions to the console as required
logging.exception("Error updating folder: %s", e)
db.session.rollback()
# Return error in modal
errors = {'general': 'An unexpected error occurred. Please try again.'}
response = make_response(render_template('partials/folder_modal.html', folder=folder, errors=errors, name=name, rule_text=rule_text, priority=priority))
response.headers['HX-Retarget'] = '#folder-modal'
response.headers['HX-Reswap'] = 'outerHTML'
return response
@folders_bp.route('/api/folders', methods=['GET'])
@login_required
def get_folders():
"""Get folders with optional filtering."""
# Get filter parameter from query string
filter_type = request.args.get('filter', 'all')
folder_type = request.args.get('type', None)
show_hidden = request.args.get('show_hidden', 'off').lower() == 'on'
# Get folders for the current authenticated user
if folder_type:
# Filter by folder type
folders = Folder.query.filter_by(user_id=current_user.id, folder_type=folder_type).all()
elif filter_type == 'high':
# Get high priority folders (priority = 1)
folders = Folder.query.filter_by(user_id=current_user.id, priority=1).all()
elif filter_type == 'normal':
# Get normal priority folders (priority = 0 or not set)
folders = Folder.query.filter_by(user_id=current_user.id).filter(Folder.priority != 1).all()
else:
# Get all folders
folders = Folder.query.filter_by(user_id=current_user.id).all()
if folder_type == 'tidy':
response = make_response(render_template('partials/folders_to_tidy_section.html', folders=folders, show_hidden=show_hidden))
response.headers["HX-Retarget"] = "#folders-to-tidy"
return response
elif folder_type == 'destination':
response = make_response(render_template('partials/destination_folders_section.html', folders=folders, show_hidden=show_hidden))
response.headers["HX-Retarget"] = "#destination-folders"
return response
elif folder_type == 'ignore':
response = make_response(render_template('partials/hidden_folders_section.html', folders=folders, show_hidden=show_hidden))
response.headers["HX-Retarget"] = "#hidden-folders"
# Show or hide the hidden folders section based on the show_hidden parameter
if show_hidden:
response.headers['HX-Trigger'] = 'show-hidden'
else:
response.headers['HX-Trigger'] = 'hide-hidden'
return response
else:
return render_template('partials/folders_list.html', folders=folders, show_hidden=show_hidden)