chore: modular monolith folder split (no behavior change)
This commit is contained in:
211
app/main.py
Normal file
211
app/main.py
Normal file
@@ -0,0 +1,211 @@
|
||||
"""
|
||||
Terra-View - Unified monitoring platform for device fleets
|
||||
Modular monolith architecture with strict feature boundaries
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Import configuration
|
||||
from app.core.config import APP_NAME, VERSION, ENVIRONMENT
|
||||
|
||||
# Import UI routes
|
||||
from app.ui import routes as ui_routes
|
||||
|
||||
# Import feature module routers (seismo)
|
||||
from app.seismo.routers import (
|
||||
roster as seismo_roster,
|
||||
units as seismo_units,
|
||||
photos as seismo_photos,
|
||||
roster_edit as seismo_roster_edit,
|
||||
dashboard as seismo_dashboard,
|
||||
dashboard_tabs as seismo_dashboard_tabs,
|
||||
activity as seismo_activity,
|
||||
seismo_dashboard as seismo_seismo_dashboard,
|
||||
settings as seismo_settings,
|
||||
)
|
||||
from app.seismo import routes as seismo_legacy_routes
|
||||
|
||||
# Import feature module routers (SLM)
|
||||
from app.slm.routers import (
|
||||
nl43_proxy as slm_nl43_proxy,
|
||||
dashboard as slm_dashboard,
|
||||
ui as slm_ui,
|
||||
)
|
||||
|
||||
# Import API aggregation layer (placeholder for now)
|
||||
from app.api import dashboard as api_dashboard
|
||||
from app.api import roster as api_roster
|
||||
|
||||
# Initialize database tables
|
||||
from app.seismo.database import engine as seismo_engine, Base as SeismoBase
|
||||
SeismoBase.metadata.create_all(bind=seismo_engine)
|
||||
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI(
|
||||
title=APP_NAME,
|
||||
description="Unified monitoring platform for seismograph, modem, and sound level meter fleets",
|
||||
version=VERSION
|
||||
)
|
||||
|
||||
# Add validation error handler to log details
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
logger.error(f"Validation error on {request.url}: {exc.errors()}")
|
||||
logger.error(f"Body: {await request.body()}")
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"detail": exc.errors()}
|
||||
)
|
||||
|
||||
# Configure CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Mount static files
|
||||
app.mount("/static", StaticFiles(directory="app/ui/static"), name="static")
|
||||
|
||||
# Middleware to add environment to request state
|
||||
@app.middleware("http")
|
||||
async def add_environment_to_context(request: Request, call_next):
|
||||
"""Middleware to add environment variable to request state"""
|
||||
request.state.environment = ENVIRONMENT
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
# ===== INCLUDE ROUTERS =====
|
||||
|
||||
# UI Layer (HTML pages)
|
||||
app.include_router(ui_routes.router)
|
||||
|
||||
# Seismograph Feature Module APIs
|
||||
app.include_router(seismo_roster.router)
|
||||
app.include_router(seismo_units.router)
|
||||
app.include_router(seismo_photos.router)
|
||||
app.include_router(seismo_roster_edit.router)
|
||||
app.include_router(seismo_dashboard.router)
|
||||
app.include_router(seismo_dashboard_tabs.router)
|
||||
app.include_router(seismo_activity.router)
|
||||
app.include_router(seismo_seismo_dashboard.router)
|
||||
app.include_router(seismo_settings.router)
|
||||
app.include_router(seismo_legacy_routes.router)
|
||||
|
||||
# SLM Feature Module APIs
|
||||
app.include_router(slm_nl43_proxy.router)
|
||||
app.include_router(slm_dashboard.router)
|
||||
app.include_router(slm_ui.router)
|
||||
|
||||
# API Aggregation Layer (future cross-feature endpoints)
|
||||
# app.include_router(api_dashboard.router) # TODO: Implement aggregation
|
||||
# app.include_router(api_roster.router) # TODO: Implement aggregation
|
||||
|
||||
# ===== ADDITIONAL ROUTES FROM OLD MAIN.PY =====
|
||||
# These will need to be migrated to appropriate modules
|
||||
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from typing import List, Dict
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import Depends
|
||||
|
||||
from app.seismo.database import get_db
|
||||
from app.seismo.services.snapshot import emit_status_snapshot
|
||||
from app.seismo.models import IgnoredUnit
|
||||
|
||||
# TODO: Move these to appropriate feature modules or UI layer
|
||||
|
||||
@app.post("/api/sync-edits")
|
||||
async def sync_edits(request: dict, db: Session = Depends(get_db)):
|
||||
"""Process offline edit queue and sync to database"""
|
||||
# TODO: Move to seismo module
|
||||
from app.seismo.models import RosterUnit
|
||||
|
||||
class EditItem(BaseModel):
|
||||
id: int
|
||||
unitId: str
|
||||
changes: Dict
|
||||
timestamp: int
|
||||
|
||||
class SyncEditsRequest(BaseModel):
|
||||
edits: List[EditItem]
|
||||
|
||||
sync_request = SyncEditsRequest(**request)
|
||||
results = []
|
||||
synced_ids = []
|
||||
|
||||
for edit in sync_request.edits:
|
||||
try:
|
||||
unit = db.query(RosterUnit).filter_by(id=edit.unitId).first()
|
||||
|
||||
if not unit:
|
||||
results.append({
|
||||
"id": edit.id,
|
||||
"status": "error",
|
||||
"reason": f"Unit {edit.unitId} not found"
|
||||
})
|
||||
continue
|
||||
|
||||
for key, value in edit.changes.items():
|
||||
if hasattr(unit, key):
|
||||
if key in ['deployed', 'retired']:
|
||||
setattr(unit, key, value in ['true', True, 'True', '1', 1])
|
||||
else:
|
||||
setattr(unit, key, value if value != '' else None)
|
||||
|
||||
db.commit()
|
||||
|
||||
results.append({
|
||||
"id": edit.id,
|
||||
"status": "success"
|
||||
})
|
||||
synced_ids.append(edit.id)
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
results.append({
|
||||
"id": edit.id,
|
||||
"status": "error",
|
||||
"reason": str(e)
|
||||
})
|
||||
|
||||
synced_count = len(synced_ids)
|
||||
|
||||
return JSONResponse({
|
||||
"synced": synced_count,
|
||||
"total": len(sync_request.edits),
|
||||
"synced_ids": synced_ids,
|
||||
"results": results
|
||||
})
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {
|
||||
"message": f"{APP_NAME} v{VERSION}",
|
||||
"status": "running",
|
||||
"version": VERSION,
|
||||
"modules": ["seismo", "slm"]
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
from app.core.config import PORT
|
||||
uvicorn.run(app, host="0.0.0.0", port=PORT)
|
||||
Reference in New Issue
Block a user