support toggle.
This commit is contained in:
@@ -39,5 +39,6 @@ class Folder(Base):
|
||||
name = db.Column(db.String(255), nullable=False)
|
||||
rule_text = db.Column(db.Text)
|
||||
priority = db.Column(db.Integer)
|
||||
organize_enabled = db.Column(db.Boolean, default=True)
|
||||
|
||||
user = db.relationship('User', backref=db.backref('folders', lazy=True))
|
||||
@@ -116,6 +116,30 @@ def delete_folder(folder_id):
|
||||
folders = Folder.query.filter_by(user_id=current_user.id).all()
|
||||
return render_template('partials/folders_list.html', folders=folders)
|
||||
|
||||
@main.route('/api/folders/<folder_id>/toggle', methods=['PUT'])
|
||||
@login_required
|
||||
def toggle_folder_organize(folder_id):
|
||||
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
|
||||
|
||||
# Toggle the organize_enabled flag
|
||||
folder.organize_enabled = not folder.organize_enabled
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Return just the updated folder card HTML for this specific folder
|
||||
return render_template('partials/folder_card.html', folder=folder)
|
||||
|
||||
except Exception as e:
|
||||
# Print unhandled exceptions to the console as required
|
||||
logging.exception("Error toggling folder organize flag: %s", e)
|
||||
db.session.rollback()
|
||||
return jsonify({'error': 'An unexpected error occurred'}), 500
|
||||
|
||||
@main.route('/api/folders/<folder_id>/edit', methods=['GET'])
|
||||
@login_required
|
||||
def edit_folder_modal(folder_id):
|
||||
|
||||
50
app/templates/partials/folder_card.html
Normal file
50
app/templates/partials/folder_card.html
Normal file
@@ -0,0 +1,50 @@
|
||||
<div id="folder-{{ folder.id }}" 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>
|
||||
<div class="flex space-x-2">
|
||||
<button class="btn btn-sm btn-outline"
|
||||
hx-get="/api/folders/{{ folder.id }}/edit"
|
||||
hx-target="#modal-holder"
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline btn-error"
|
||||
hx-delete="/api/folders/{{ folder.id }}"
|
||||
hx-target="#folders-list"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure you want to delete this folder?">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-base-200 rounded-box p-4 mb-4">
|
||||
<p class="text-base-content/80">{{ folder.rule_text }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-2">
|
||||
{% if folder.priority == 1 %}
|
||||
<span class="badge badge-error">High Priority</span>
|
||||
{% elif folder.priority == -1 %}
|
||||
<span class="badge badge-info">Low Priority</span>
|
||||
{% else %}
|
||||
<span class="badge badge-primary">Normal Priority</span>
|
||||
{% endif %}
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-xs">Organize:</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle toggle-sm toggle-success"
|
||||
{% if folder.organize_enabled %}checked="checked"{% endif %}
|
||||
hx-put="/api/folders/{{ folder.id }}/toggle"
|
||||
hx-target="#folder-{{ folder.id }}"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="click"
|
||||
aria-label="Toggle organize enabled">
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,43 +1,6 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<div id="folders-list" 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 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>
|
||||
<div class="flex space-x-2">
|
||||
<button class="btn btn-sm btn-outline"
|
||||
hx-get="/api/folders/{{ folder.id }}/edit"
|
||||
hx-target="#modal-holder"
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="click">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline btn-error"
|
||||
hx-delete="/api/folders/{{ folder.id }}"
|
||||
hx-target="#folders-list"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure you want to delete this folder?">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-base-200 rounded-box p-4 mb-4">
|
||||
<p class="text-base-content/80">{{ folder.rule_text }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-2">
|
||||
{% if folder.priority == 1 %}
|
||||
<span class="badge badge-error">High Priority</span>
|
||||
{% elif folder.priority == -1 %}
|
||||
<span class="badge badge-info">Low Priority</span>
|
||||
{% else %}
|
||||
<span class="badge badge-primary">Normal Priority</span>
|
||||
{% endif %}
|
||||
<span class="text-xs badge badge-secondary">0 emails</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'partials/folder_card.html' %}
|
||||
{% else %}
|
||||
<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">
|
||||
@@ -45,7 +8,7 @@
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold mb-2">No folders yet</h3>
|
||||
<p class="mb-6 text-base-content/70">Add your first folder to get started organizing your emails.</p>
|
||||
<button class="btn btn-primary btn-lg"
|
||||
<button class="btn btn-primary btn-lg"
|
||||
hx-get="/api/folders/new"
|
||||
hx-target="#modal-holder"
|
||||
hx-swap="innerHTML"
|
||||
|
||||
32
migrations/versions/f8ba65458ba2_adding_toggle.py
Normal file
32
migrations/versions/f8ba65458ba2_adding_toggle.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""adding toggle
|
||||
|
||||
Revision ID: f8ba65458ba2
|
||||
Revises: 0bcf54a7f2a7
|
||||
Create Date: 2025-08-05 06:58:59.265593
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f8ba65458ba2'
|
||||
down_revision = '0bcf54a7f2a7'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('folders', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('organize_enabled', sa.Boolean(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('folders', schema=None) as batch_op:
|
||||
batch_op.drop_column('organize_enabled')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from app.models import User, Folder
|
||||
from app import db
|
||||
import uuid
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
@@ -308,4 +309,64 @@ def test_edit_folder_content_whitespace_handling(authenticated_client, mock_fold
|
||||
assert updated_folder is not None
|
||||
assert updated_folder.name == 'Updated Folder With Whitespace' # Should be trimmed
|
||||
assert updated_folder.rule_text == 'Updated rule with whitespace around it' # Should be trimmed
|
||||
assert updated_folder.priority == int(test_priority)
|
||||
assert updated_folder.priority == int(test_priority)
|
||||
|
||||
def test_toggle_folder_organize_enabled(authenticated_client, mock_folder):
|
||||
"""Test toggling the organize_enabled flag for a folder."""
|
||||
# Verify initial state is True (default)
|
||||
assert mock_folder.organize_enabled is True
|
||||
|
||||
# Toggle the flag
|
||||
response = authenticated_client.put(f'/api/folders/{mock_folder.id}/toggle')
|
||||
|
||||
# Should return 200 OK
|
||||
assert response.status_code == 200
|
||||
|
||||
# Verify the folder was updated in the database
|
||||
updated_folder = Folder.query.filter_by(id=mock_folder.id).first()
|
||||
assert updated_folder is not None
|
||||
assert updated_folder.organize_enabled is False
|
||||
|
||||
# Toggle again to make sure it works both ways
|
||||
response2 = authenticated_client.put(f'/api/folders/{mock_folder.id}/toggle')
|
||||
|
||||
# Should return 200 OK
|
||||
assert response2.status_code == 200
|
||||
|
||||
# Verify the folder was updated in the database again
|
||||
updated_folder2 = Folder.query.filter_by(id=mock_folder.id).first()
|
||||
assert updated_folder2 is not None
|
||||
assert updated_folder2.organize_enabled is True
|
||||
|
||||
def test_toggle_folder_organize_enabled_not_found(authenticated_client, mock_user):
|
||||
"""Test toggling organize_enabled flag for a non-existent folder."""
|
||||
# Try to toggle a folder that doesn't exist
|
||||
response = authenticated_client.put('/api/folders/999/toggle')
|
||||
|
||||
# Should return 404 Not Found
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_toggle_folder_organize_enabled_unauthorized(authenticated_client, mock_user, app):
|
||||
"""Test toggling organize_enabled flag for a folder that doesn't belong to the user."""
|
||||
# Create a folder that belongs to a different user
|
||||
other_user = User(email='other@example.com', first_name='Other', last_name='User')
|
||||
other_user.set_password('password')
|
||||
|
||||
with app.app_context():
|
||||
db.session.add(other_user)
|
||||
db.session.commit()
|
||||
|
||||
# Create a folder for the other user
|
||||
other_folder = Folder(
|
||||
user_id=other_user.id,
|
||||
name='Other User Folder',
|
||||
rule_text='Test rule text'
|
||||
)
|
||||
db.session.add(other_folder)
|
||||
db.session.commit()
|
||||
|
||||
# Try to toggle the flag for the folder that doesn't belong to authenticated user
|
||||
response = authenticated_client.put(f'/api/folders/{other_folder.id}/toggle')
|
||||
|
||||
# Should return 404 Not Found (folder not found due to authorization check)
|
||||
assert response.status_code == 404
|
||||
1
tmp/imap-data/example.com/user1/dovecot-uidlist
Normal file
1
tmp/imap-data/example.com/user1/dovecot-uidlist
Normal file
@@ -0,0 +1 @@
|
||||
3 V1754397400 N1 Gdd33f323d8fa916813000000739f6a6d
|
||||
1
tmp/imap-data/example.com/user1/dovecot-uidvalidity
Normal file
1
tmp/imap-data/example.com/user1/dovecot-uidvalidity
Normal file
@@ -0,0 +1 @@
|
||||
6891fad8
|
||||
BIN
tmp/imap-data/example.com/user1/dovecot.index.log
Normal file
BIN
tmp/imap-data/example.com/user1/dovecot.index.log
Normal file
Binary file not shown.
BIN
tmp/imap-data/example.com/user1/dovecot.list.index.log
Normal file
BIN
tmp/imap-data/example.com/user1/dovecot.list.index.log
Normal file
Binary file not shown.
BIN
tmp/imap-data/example.com/user1/dovecot.mailbox.log
Normal file
BIN
tmp/imap-data/example.com/user1/dovecot.mailbox.log
Normal file
Binary file not shown.
0
tmp/imap-data/example.com/user1/maildirfolder
Normal file
0
tmp/imap-data/example.com/user1/maildirfolder
Normal file
3
tmp/imap-data/example.com/user1/subscriptions
Normal file
3
tmp/imap-data/example.com/user1/subscriptions
Normal file
@@ -0,0 +1,3 @@
|
||||
V 2
|
||||
|
||||
INBOX
|
||||
Reference in New Issue
Block a user