Add flight comparator web app with full scan pipeline
Full-stack flight price scanner built on fast-flights v3 (SOCS cookie bypass): Backend (FastAPI + SQLite): - REST API with rate limiting, Pydantic v2 validation, paginated responses - Scan pipeline: resolves airports, queries every day in the window, saves individual flights + aggregate route stats to SQLite - Background async scan processor with real-time progress tracking - Airport search endpoint backed by OpenFlights dataset - Daily scan window (all dates, not monthly samples) Frontend (React 19 + TypeScript + Tailwind CSS v4): - Dashboard with live scan status and recent scans - Create scan form: country mode or specific airports (searchable dropdown) - Scan detail page with expandable route rows showing individual flights (date, airline, departure, arrival, price) loaded on demand - AirportSearch component with debounced live search and multi-select Database: - scans → routes → flights schema with FK cascade and auto-update triggers - Migrations for schema evolution (relaxed country constraint) Tests: - 74 tests: unit + integration, isolated per-test SQLite DB - Confirmed flight fixtures in tests/confirmed_flights.json (50 real flights, BDS→FMM Ryanair + BDS→DUS Eurowings, scraped Feb 2026) - Integration tests parametrized from confirmed routes Docker: - Multi-stage builds, Compose orchestration, Nginx reverse proxy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
104
flight-comparator/cache_admin.py
Executable file
104
flight-comparator/cache_admin.py
Executable file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cache administration utility for flight search results.
|
||||
|
||||
Provides commands to view cache statistics and clean up old entries.
|
||||
"""
|
||||
|
||||
import click
|
||||
from cache import get_cache_stats, clear_old_cache, init_database, CACHE_DB_PATH
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
"""Flight search cache administration."""
|
||||
pass
|
||||
|
||||
|
||||
@cli.command()
|
||||
def stats():
|
||||
"""Display cache statistics."""
|
||||
init_database()
|
||||
|
||||
stats = get_cache_stats()
|
||||
|
||||
click.echo()
|
||||
click.echo("Flight Search Cache Statistics")
|
||||
click.echo("=" * 50)
|
||||
click.echo(f"Database location: {CACHE_DB_PATH}")
|
||||
click.echo(f"Total searches cached: {stats['total_searches']}")
|
||||
click.echo(f"Total flight results: {stats['total_results']}")
|
||||
click.echo(f"Database size: {stats['db_size_mb']:.2f} MB")
|
||||
|
||||
if stats['oldest_entry']:
|
||||
click.echo(f"Oldest entry: {stats['oldest_entry']}")
|
||||
if stats['newest_entry']:
|
||||
click.echo(f"Newest entry: {stats['newest_entry']}")
|
||||
|
||||
click.echo("=" * 50)
|
||||
click.echo()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--days', default=30, type=int, help='Delete entries older than N days')
|
||||
@click.option('--confirm', is_flag=True, help='Skip confirmation prompt')
|
||||
def clean(days: int, confirm: bool):
|
||||
"""Clean up old cache entries."""
|
||||
init_database()
|
||||
|
||||
stats = get_cache_stats()
|
||||
|
||||
click.echo()
|
||||
click.echo(f"Current cache: {stats['total_searches']} searches, {stats['db_size_mb']:.2f} MB")
|
||||
click.echo(f"Will delete entries older than {days} days.")
|
||||
click.echo()
|
||||
|
||||
if not confirm:
|
||||
if not click.confirm('Proceed with cleanup?'):
|
||||
click.echo("Cancelled.")
|
||||
return
|
||||
|
||||
deleted = clear_old_cache(days)
|
||||
|
||||
# Get new stats
|
||||
new_stats = get_cache_stats()
|
||||
|
||||
click.echo(f"✓ Deleted {deleted} old search(es)")
|
||||
click.echo(f"New cache size: {new_stats['total_searches']} searches, {new_stats['db_size_mb']:.2f} MB")
|
||||
click.echo()
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option('--confirm', is_flag=True, help='Skip confirmation prompt')
|
||||
def clear_all(confirm: bool):
|
||||
"""Delete all cached data."""
|
||||
init_database()
|
||||
|
||||
stats = get_cache_stats()
|
||||
|
||||
click.echo()
|
||||
click.echo(f"⚠️ WARNING: This will delete ALL {stats['total_searches']} cached searches!")
|
||||
click.echo()
|
||||
|
||||
if not confirm:
|
||||
if not click.confirm('Are you sure?'):
|
||||
click.echo("Cancelled.")
|
||||
return
|
||||
|
||||
deleted = clear_old_cache(days=0) # Delete everything
|
||||
|
||||
click.echo(f"✓ Deleted all {deleted} cached search(es)")
|
||||
click.echo()
|
||||
|
||||
|
||||
@cli.command()
|
||||
def init():
|
||||
"""Initialize cache database (create tables if not exist)."""
|
||||
click.echo("Initializing cache database...")
|
||||
init_database()
|
||||
click.echo(f"✓ Database initialized at: {CACHE_DB_PATH}")
|
||||
click.echo()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
cli()
|
||||
Reference in New Issue
Block a user