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/', 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//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//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/', 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)