4237c4bdb8
## Phase 1: Code Cleanup (~1,859 lines removed) **Deleted Redundant Matchers:** - ❌ strategies_orm.py (356 lines) - Old ORM-based strategies - ❌ simple_matcher.py (351 lines) - Replaced by strategies.py - ❌ rtl433_matcher.py (352 lines) - Replaced by strategies.py **Archived Old Scripts:** - Moved 11 one-time analysis/import scripts to scripts/archive/ - Scripts: analyze_flipper_signatures, analyze_tembed_files, identify_tembed_devices, import_flipper_sqlite, import_tembed_signatures, match_tembed_with_db, match_with_flipper_db, rematch_captures, test_gps_extraction, test_tembed_matching, test_wardriving_import **Consolidated API:** - Renamed main.py → main_orm_legacy.py (archived old ORM-based API) - main_simple.py is now the primary production API ## Phase 2: Pattern Decoder Integration ✅ **CRITICAL FIX: Pattern decoder now integrated into production API!** **Changes:** 1. Updated main_simple.py to use unified SignatureMatcher 2. Added 6 strategies to matcher pipeline: - ExactMatcher (protocol + frequency) - FrequencyMatcher (frequency-based) - BitPatternMatcher (data patterns) - TimingMatcher (timing-based) - RTL433DecoderStrategy (RTL_433 decoder) - PatternBasedStrategy (NEW! Pattern decoder for short captures) 3. Created MockDB class for simplified mode (no real database) 4. Replaced old get_matcher() with get_matcher_engine() 5. Updated upload handler to use MatchResult format 6. All matches now include confidence scores and match methods **Result:** - Pattern decoder is NOW ACTIVE in production 🎉 - Unified matching pipeline with 6 strategies - Cleaner codebase (-1,859 lines) - Single source of truth for matching logic 🎉 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
281 lines
8.7 KiB
Python
281 lines
8.7 KiB
Python
"""
|
|
GigLez FastAPI Application
|
|
|
|
Environment-aware API server supporting both development (Termux) and production (Server) modes
|
|
"""
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.middleware.gzip import GZipMiddleware
|
|
from fastapi.responses import JSONResponse, HTMLResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
from contextlib import asynccontextmanager
|
|
from loguru import logger
|
|
from pathlib import Path
|
|
import time
|
|
import sys
|
|
|
|
# Add project root to Python path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
|
|
from config.settings import settings
|
|
from config.database import get_db_config
|
|
|
|
|
|
# =============================================================================
|
|
# LIFESPAN MANAGEMENT
|
|
# =============================================================================
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""
|
|
Application lifespan manager
|
|
Handles startup and shutdown events
|
|
"""
|
|
# Startup
|
|
logger.info("=" * 80)
|
|
logger.info("GigLez API Starting")
|
|
logger.info("=" * 80)
|
|
|
|
# Display configuration
|
|
settings.display()
|
|
|
|
# Validate configuration
|
|
errors = settings.validate()
|
|
if errors:
|
|
logger.error("Configuration validation failed:")
|
|
for error in errors:
|
|
logger.error(f" ❌ {error}")
|
|
raise RuntimeError("Invalid configuration")
|
|
|
|
# Test database connection
|
|
db_config = get_db_config()
|
|
if not db_config.test_connection():
|
|
raise RuntimeError("Database connection failed")
|
|
|
|
if not db_config.test_postgis():
|
|
raise RuntimeError("PostGIS extension not available")
|
|
|
|
logger.success("✅ Database connection established")
|
|
logger.success("✅ PostGIS extension available")
|
|
|
|
# Initialize storage backend
|
|
from src.core.storage import get_storage_backend
|
|
storage = get_storage_backend()
|
|
logger.success(f"✅ Storage backend initialized: {type(storage).__name__}")
|
|
|
|
# Initialize background task system
|
|
if settings.use_celery:
|
|
logger.info("Using Celery for background tasks")
|
|
# Celery app will be imported when needed
|
|
else:
|
|
logger.info("Using synchronous background tasks")
|
|
|
|
logger.success("=" * 80)
|
|
logger.success(f"🚀 GigLez API ready on {settings.api_host}:{settings.api_port}")
|
|
logger.success(f"📡 Mode: {settings.mode.value}")
|
|
logger.success("=" * 80)
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
logger.info("=" * 80)
|
|
logger.info("GigLez API Shutting Down")
|
|
logger.info("=" * 80)
|
|
|
|
# Close database connections
|
|
db_config.close()
|
|
logger.success("✅ Database connections closed")
|
|
|
|
logger.info("Shutdown complete")
|
|
|
|
|
|
# =============================================================================
|
|
# APPLICATION INSTANCE
|
|
# =============================================================================
|
|
|
|
app = FastAPI(
|
|
title="GigLez API",
|
|
description="IoT RF Device Mapping Platform - Wigle for Sub-GHz Signals",
|
|
version="1.0.0",
|
|
lifespan=lifespan,
|
|
docs_url="/docs" if settings.debug_endpoints else None,
|
|
redoc_url="/redoc" if settings.debug_endpoints else None,
|
|
openapi_url="/openapi.json" if settings.debug_endpoints else None,
|
|
)
|
|
|
|
# Mount static files and templates
|
|
BASE_DIR = Path(__file__).parent.parent.parent
|
|
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
|
|
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
|
|
|
|
|
|
# =============================================================================
|
|
# MIDDLEWARE
|
|
# =============================================================================
|
|
|
|
# CORS Middleware
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
expose_headers=["X-Total-Count", "X-Page-Size"],
|
|
)
|
|
|
|
# GZip compression
|
|
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
|
|
|
|
|
# Request timing middleware
|
|
@app.middleware("http")
|
|
async def add_process_time_header(request: Request, call_next):
|
|
"""Add request processing time header"""
|
|
start_time = time.time()
|
|
response = await call_next(request)
|
|
process_time = time.time() - start_time
|
|
response.headers["X-Process-Time"] = f"{process_time:.4f}"
|
|
return response
|
|
|
|
|
|
# Request logging middleware
|
|
@app.middleware("http")
|
|
async def log_requests(request: Request, call_next):
|
|
"""Log all requests"""
|
|
logger.info(f"{request.method} {request.url.path}")
|
|
response = await call_next(request)
|
|
logger.info(f"{request.method} {request.url.path} - {response.status_code}")
|
|
return response
|
|
|
|
|
|
# =============================================================================
|
|
# EXCEPTION HANDLERS
|
|
# =============================================================================
|
|
|
|
@app.exception_handler(Exception)
|
|
async def global_exception_handler(request: Request, exc: Exception):
|
|
"""Global exception handler"""
|
|
logger.error(f"Unhandled exception: {exc}", exc_info=True)
|
|
|
|
if settings.debug_errors:
|
|
# Detailed error in development
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"error": "Internal Server Error",
|
|
"detail": str(exc),
|
|
"type": type(exc).__name__
|
|
}
|
|
)
|
|
else:
|
|
# Generic error in production
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"error": "Internal Server Error"}
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# ROOT ENDPOINTS
|
|
# =============================================================================
|
|
|
|
@app.get("/", response_class=HTMLResponse)
|
|
async def root(request: Request):
|
|
"""Root endpoint - Serve web interface"""
|
|
return templates.TemplateResponse("index.html", {"request": request})
|
|
|
|
|
|
@app.get("/api")
|
|
async def api_root():
|
|
"""API information endpoint"""
|
|
return {
|
|
"name": "GigLez API",
|
|
"version": "1.0.0",
|
|
"description": "IoT RF Device Mapping Platform",
|
|
"mode": settings.mode.value,
|
|
"docs": "/docs" if settings.debug_endpoints else "disabled",
|
|
"status": "operational"
|
|
}
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
"""Health check endpoint"""
|
|
# Test database connection
|
|
db_config = get_db_config()
|
|
db_healthy = db_config.test_connection()
|
|
|
|
return {
|
|
"status": "healthy" if db_healthy else "unhealthy",
|
|
"database": "connected" if db_healthy else "disconnected",
|
|
"mode": settings.mode.value,
|
|
"hardware_enabled": settings.enable_hardware
|
|
}
|
|
|
|
|
|
@app.get("/version")
|
|
async def version():
|
|
"""Version information"""
|
|
return {
|
|
"version": "1.0.0",
|
|
"mode": settings.mode.value,
|
|
"api": "v1"
|
|
}
|
|
|
|
|
|
# =============================================================================
|
|
# IMPORT ROUTERS
|
|
# =============================================================================
|
|
|
|
# Import and register route modules
|
|
from src.api.routes import captures, devices, query, stats
|
|
|
|
# Register routers
|
|
app.include_router(captures.router, prefix="/api/v1", tags=["captures"])
|
|
app.include_router(devices.router, prefix="/api/v1", tags=["devices"])
|
|
app.include_router(query.router, prefix="/api/v1", tags=["query"])
|
|
app.include_router(stats.router, prefix="/api/v1", tags=["statistics"])
|
|
|
|
# Hardware endpoints (development mode only)
|
|
if settings.enable_hardware:
|
|
from src.api.routes import hardware
|
|
app.include_router(hardware.router, prefix="/api/v1", tags=["hardware"])
|
|
logger.info("✅ Hardware endpoints enabled")
|
|
|
|
|
|
# Debug endpoints (development mode only)
|
|
if settings.debug_endpoints:
|
|
from src.api.routes import debug
|
|
app.include_router(debug.router, prefix="/api/debug", tags=["debug"])
|
|
logger.info("✅ Debug endpoints enabled")
|
|
|
|
|
|
# =============================================================================
|
|
# STARTUP MESSAGE
|
|
# =============================================================================
|
|
|
|
logger.info("FastAPI application initialized")
|
|
logger.info(f"Mode: {settings.mode.value}")
|
|
logger.info(f"Authentication: {'Required' if settings.require_auth else 'Optional'}")
|
|
logger.info(f"CORS Origins: {settings.cors_origins}")
|
|
logger.info(f"Storage: {settings.storage_type.value}")
|
|
|
|
|
|
# =============================================================================
|
|
# RUN WITH UVICORN (for development)
|
|
# =============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(
|
|
"src.api.main:app",
|
|
host=settings.api_host,
|
|
port=settings.api_port,
|
|
reload=settings.auto_reload,
|
|
log_level=settings.log_level.lower(),
|
|
access_log=True,
|
|
)
|