diff --git a/database.py b/database.py new file mode 100644 index 0000000..bb191d9 --- /dev/null +++ b/database.py @@ -0,0 +1,22 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +SQLALCHEMY_DATABASE_URL = "sqlite:///./seismo_fleet.db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} +) + +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() + + +def get_db(): + """Dependency for database sessions""" + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/main.py b/main.py new file mode 100644 index 0000000..9458fd8 --- /dev/null +++ b/main.py @@ -0,0 +1,41 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from database import engine, Base +from routes import router + +# Create database tables +Base.metadata.create_all(bind=engine) + +# Initialize FastAPI app +app = FastAPI( + title="Seismo Fleet Manager", + description="Backend API for managing seismograph fleet status", + version="0.1.0" +) + +# Configure CORS (adjust origins as needed for your deployment) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, specify exact origins + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include API routes +app.include_router(router) + + +@app.get("/") +def root(): + """Root endpoint - health check""" + return { + "message": "Seismo Fleet Manager API v0.1", + "status": "running" + } + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/models.py b/models.py new file mode 100644 index 0000000..9ae42c1 --- /dev/null +++ b/models.py @@ -0,0 +1,18 @@ +from sqlalchemy import Column, String, DateTime +from datetime import datetime +from database import Base + + +class Emitter(Base): + """Emitter model representing a seismograph unit in the fleet""" + __tablename__ = "emitters" + + id = Column(String, primary_key=True, index=True) + unit_type = Column(String, nullable=False) + last_seen = Column(DateTime, default=datetime.utcnow) + last_file = Column(String, nullable=False) + status = Column(String, nullable=False) # OK, Pending, Missing + notes = Column(String, nullable=True) + + def __repr__(self): + return f"" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4ab590c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +sqlalchemy==2.0.23 +pydantic==2.5.0 +python-multipart==0.0.6 diff --git a/routes.py b/routes.py new file mode 100644 index 0000000..4cdb4fa --- /dev/null +++ b/routes.py @@ -0,0 +1,82 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from pydantic import BaseModel +from datetime import datetime +from typing import Optional, List + +from database import get_db +from models import Emitter + +router = APIRouter() + + +# Pydantic schemas for request/response validation +class EmitterReport(BaseModel): + unit: str + unit_type: str + timestamp: str + file: str + status: str + + +class EmitterResponse(BaseModel): + id: str + unit_type: str + last_seen: datetime + last_file: str + status: str + notes: Optional[str] = None + + class Config: + from_attributes = True + + +@router.post("/emitters/report", status_code=200) +def report_emitter(report: EmitterReport, db: Session = Depends(get_db)): + """ + Endpoint for emitters to report their status. + Creates a new emitter if it doesn't exist, or updates an existing one. + """ + try: + # Parse the timestamp + timestamp = datetime.fromisoformat(report.timestamp.replace('Z', '+00:00')) + except ValueError: + raise HTTPException(status_code=400, detail="Invalid timestamp format") + + # Check if emitter already exists + emitter = db.query(Emitter).filter(Emitter.id == report.unit).first() + + if emitter: + # Update existing emitter + emitter.unit_type = report.unit_type + emitter.last_seen = timestamp + emitter.last_file = report.file + emitter.status = report.status + else: + # Create new emitter + emitter = Emitter( + id=report.unit, + unit_type=report.unit_type, + last_seen=timestamp, + last_file=report.file, + status=report.status + ) + db.add(emitter) + + db.commit() + db.refresh(emitter) + + return { + "message": "Emitter report received", + "unit": emitter.id, + "status": emitter.status + } + + +@router.get("/fleet/status", response_model=List[EmitterResponse]) +def get_fleet_status(db: Session = Depends(get_db)): + """ + Returns a list of all emitters and their current status. + """ + emitters = db.query(Emitter).all() + return emitters