Compare commits

..

2 Commits

Author SHA1 Message Date
c7bd634ad6 Add search functionality (Issue #3)
Backend changes:
- Added search query parameter (q) to /api/posts endpoint
- Search filters posts by title, content, author, and source
- Case-insensitive search with substring matching

Frontend changes:
- Made search bar functional with Enter key and click support
- Added performSearch() function to trigger searches
- Added Clear Search button that appears during active search
- Search results update feed title to show query
- Integrated search with existing pagination and filtering
- Preserves anonymous/authenticated feed title when clearing search

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 19:47:04 -05:00
5d6da930df Investigate comments loading issue (Issue #4)
- Added debug logging to post_detail route to track comment loading
- Created migration script for poll source fields (max_posts, fetch_comments, priority)
- Migration adds default values to ensure comments are fetched

The issue may be that existing poll sources in database dont have fetch_comments field.
Migration needs to be run on server with database access.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 19:43:55 -05:00
3 changed files with 143 additions and 5 deletions

20
app.py
View File

@@ -348,6 +348,7 @@ def api_posts():
per_page = int(request.args.get('per_page', DEFAULT_PAGE_SIZE))
community = request.args.get('community', '')
platform = request.args.get('platform', '')
search_query = request.args.get('q', '').lower().strip()
# Use cached data for better performance
cached_posts, cached_comments = _load_posts_cache()
@@ -363,7 +364,21 @@ def api_posts():
# Apply platform filter
if platform and post_data.get('platform', '').lower() != platform.lower():
continue
# Apply search filter
if search_query:
# Search in title, content, author, and source
title = post_data.get('title', '').lower()
content = post_data.get('content', '').lower()
author = post_data.get('author', '').lower()
source = post_data.get('source', '').lower()
if not (search_query in title or
search_query in content or
search_query in author or
search_query in source):
continue
# Get comment count from cache
comment_count = len(cached_comments.get(post_uuid, []))
@@ -552,7 +567,8 @@ def post_detail(post_id):
# Get comments from cache
comments = cached_comments.get(post_id, [])
logger.info(f"Loading post {post_id}: found {len(comments)} comments")
# Sort comments by timestamp
comments.sort(key=lambda x: x.get('timestamp', 0))

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
"""
Database migration to add new polling configuration fields to poll_sources table.
Run this once to add the new columns: max_posts, fetch_comments, priority
"""
import sys
from app import app, db
def migrate():
"""Add polling configuration columns to poll_sources table"""
with app.app_context():
try:
# Check if columns already exist
from sqlalchemy import inspect
inspector = inspect(db.engine)
columns = [col['name'] for col in inspector.get_columns('poll_sources')]
if 'max_posts' in columns and 'fetch_comments' in columns and 'priority' in columns:
print("✓ Polling configuration columns already exist")
return True
# Add the new columns using raw SQL
with db.engine.connect() as conn:
if 'max_posts' not in columns:
print("Adding max_posts column...")
conn.execute(db.text(
"ALTER TABLE poll_sources ADD COLUMN max_posts INTEGER NOT NULL DEFAULT 100"
))
conn.commit()
if 'fetch_comments' not in columns:
print("Adding fetch_comments column...")
conn.execute(db.text(
"ALTER TABLE poll_sources ADD COLUMN fetch_comments BOOLEAN NOT NULL DEFAULT TRUE"
))
conn.commit()
if 'priority' not in columns:
print("Adding priority column...")
conn.execute(db.text(
"ALTER TABLE poll_sources ADD COLUMN priority VARCHAR(20) NOT NULL DEFAULT 'medium'"
))
conn.commit()
print("✓ Polling configuration columns added successfully")
print("\nUpdating existing poll sources with default values...")
# Update existing rows to have default values
with db.engine.connect() as conn:
result = conn.execute(db.text("UPDATE poll_sources SET fetch_comments = TRUE WHERE fetch_comments IS NULL"))
conn.commit()
print(f"✓ Updated {result.rowcount} rows with default fetch_comments=TRUE")
return True
except Exception as e:
print(f"✗ Migration failed: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == '__main__':
print("Running poll source fields migration...")
success = migrate()
sys.exit(0 if success else 1)

View File

@@ -447,6 +447,24 @@
transform: translateY(-1px);
}
.clear-search-btn {
background: #f1f5f9;
color: #64748b;
border: 1px solid #e2e8f0;
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.clear-search-btn:hover {
background: #e2e8f0;
color: #2c3e50;
transform: translateY(-1px);
}
.feed-container {
padding: 0;
}
@@ -803,6 +821,7 @@ async function loadPosts(page = 1, community = '', platform = '', append = false
params.append('per_page', 20);
if (community) params.append('community', community);
if (platform) params.append('platform', platform);
if (currentSearchQuery) params.append('q', currentSearchQuery);
const response = await fetch(`/api/posts?${params}`);
const data = await response.json();
@@ -1037,15 +1056,52 @@ function refreshFeed() {
}
// Search functionality
let currentSearchQuery = '';
document.querySelector('.search-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const query = this.value.trim();
if (query) {
alert(`Search functionality coming soon! You searched for: "${query}"`);
}
performSearch(query);
}
});
document.querySelector('.search-btn').addEventListener('click', function() {
const query = document.querySelector('.search-input').value.trim();
performSearch(query);
});
function performSearch(query) {
currentSearchQuery = query;
currentPage = 1;
if (query) {
document.querySelector('.content-header h1').textContent = `Search results for "${query}"`;
// Show clear search button
if (!document.querySelector('.clear-search-btn')) {
const clearBtn = document.createElement('button');
clearBtn.className = 'clear-search-btn';
clearBtn.textContent = '✕ Clear search';
clearBtn.onclick = clearSearch;
document.querySelector('.content-actions').prepend(clearBtn);
}
}
loadPosts();
}
function clearSearch() {
currentSearchQuery = '';
document.querySelector('.search-input').value = '';
// Restore original feed title based on user state
const isAnonymous = {{ 'true' if anonymous else 'false' }};
document.querySelector('.content-header h1').textContent = isAnonymous ? 'Public Feed' : 'Your Feed';
const clearBtn = document.querySelector('.clear-search-btn');
if (clearBtn) {
clearBtn.remove();
}
loadPosts();
}
// Setup infinite scroll functionality
function setupInfiniteScroll() {
if (!userSettings?.experience?.infinite_scroll) {