@mytec: 1.1iter ready for testing

This commit is contained in:
2026-01-30 22:36:32 +02:00
parent 43b1267c56
commit 4326a6c4f7
28 changed files with 245 additions and 8 deletions

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

5
backend/app/api/deps.py Normal file
View File

@@ -0,0 +1,5 @@
from app.core.database import get_database
async def get_db():
return await get_database()

View File

View File

@@ -0,0 +1,18 @@
from fastapi import APIRouter, Depends
from app.api.deps import get_db
router = APIRouter()
@router.get("/")
async def health_check():
return {"status": "ok", "service": "rfcp-backend", "version": "1.1.0"}
@router.get("/db")
async def db_check(db=Depends(get_db)):
try:
await db.command("ping")
return {"status": "ok", "database": "connected"}
except Exception as e:
return {"status": "error", "database": str(e)}

View File

@@ -0,0 +1,83 @@
from fastapi import APIRouter, Depends, HTTPException
from datetime import datetime
from app.api.deps import get_db
from app.models.project import Project, CoverageSettings
from app.models.site import Site
router = APIRouter()
@router.get("/current")
async def get_current_project(db=Depends(get_db)):
"""Get the global project."""
project = await db.projects.find_one({"name": "global"})
if not project:
default = Project(name="global")
result = await db.projects.insert_one(default.model_dump())
return default.model_dump()
project.pop("_id", None)
return project
@router.put("/current")
async def update_current_project(project: Project, db=Depends(get_db)):
"""Update the global project."""
project.updated_at = datetime.utcnow()
data = project.model_dump()
await db.projects.update_one(
{"name": "global"},
{"$set": data},
upsert=True,
)
return data
@router.get("/current/sites")
async def get_sites(db=Depends(get_db)):
"""Get all sites from the global project."""
project = await db.projects.find_one({"name": "global"})
if not project:
return []
return project.get("sites", [])
@router.put("/current/sites")
async def update_sites(sites: list[Site], db=Depends(get_db)):
"""Update all sites in the global project."""
await db.projects.update_one(
{"name": "global"},
{
"$set": {
"sites": [s.model_dump() for s in sites],
"updated_at": datetime.utcnow(),
}
},
upsert=True,
)
return {"updated": len(sites)}
@router.get("/current/settings")
async def get_settings(db=Depends(get_db)):
"""Get coverage settings."""
project = await db.projects.find_one({"name": "global"})
if not project:
return CoverageSettings().model_dump()
return project.get("settings", CoverageSettings().model_dump())
@router.put("/current/settings")
async def update_settings(settings: CoverageSettings, db=Depends(get_db)):
"""Update coverage settings."""
await db.projects.update_one(
{"name": "global"},
{
"$set": {
"settings": settings.model_dump(),
"updated_at": datetime.utcnow(),
}
},
upsert=True,
)
return settings.model_dump()

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
MONGODB_URL: str = "mongodb://localhost:27017"
DATABASE_NAME: str = "rfcp"
TERRAIN_DATA_DIR: str = "/opt/rfcp/data/terrain"
class Config:
env_file = ".env"
settings = Settings()

View File

@@ -0,0 +1,22 @@
from motor.motor_asyncio import AsyncIOMotorClient
from app.core.config import settings
class Database:
client: AsyncIOMotorClient = None
db = Database()
async def get_database():
return db.client[settings.DATABASE_NAME]
async def connect_to_mongo():
db.client = AsyncIOMotorClient(settings.MONGODB_URL)
async def close_mongo_connection():
if db.client:
db.client.close()

View File

@@ -1,29 +1,58 @@
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
import os
app = FastAPI(title="RFCP API")
from app.core.config import settings
from app.core.database import connect_to_mongo, close_mongo_connection
from app.api.routes import health, projects
# CORS
@asynccontextmanager
async def lifespan(app: FastAPI):
await connect_to_mongo()
yield
await close_mongo_connection()
app = FastAPI(
title="RFCP Backend API",
description="RF Coverage Planning Backend",
version="1.1.0",
lifespan=lifespan,
)
# CORS for frontend
app.add_middleware(
CORSMiddleware,
allow_origins=["https://rfcp.eliah.one", "http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health")
async def health():
return {"status": "healthy", "version": "1.0.0"}
# Routes
app.include_router(health.router, prefix="/api/health", tags=["health"])
app.include_router(projects.router, prefix="/api/projects", tags=["projects"])
@app.get("/")
async def root():
return {"message": "RFCP Backend API", "version": "1.1.0"}
@app.get("/api/terrain/{region}")
async def get_terrain(region: str):
terrain_path = f"/opt/rfcp/data/terrain/{region}.hgt"
"""Serve SRTM terrain .hgt files."""
terrain_path = os.path.join(settings.TERRAIN_DATA_DIR, f"{region}.hgt")
if os.path.exists(terrain_path):
return FileResponse(terrain_path)
return {"error": "Region not found"}, 404
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8090)

View File

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,21 @@
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
from app.models.site import Site
class CoverageSettings(BaseModel):
radius: float = 10000.0
resolution: float = 200.0
min_signal: float = -105.0
max_signal: float = -65.0
class Project(BaseModel):
id: Optional[str] = None
name: str = "global"
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
sites: list[Site] = []
settings: CoverageSettings = Field(default_factory=CoverageSettings)

View File

@@ -0,0 +1,35 @@
from pydantic import BaseModel
from typing import Optional
class Sector(BaseModel):
id: str
name: str = "Alpha"
power: float = 43.0
gain: float = 8.0
height: float = 30.0
frequency: float = 1800.0
azimuth: float = 0.0
beamwidth: float = 65.0
tilt: float = 0.0
antenna_type: str = "directional"
class Site(BaseModel):
id: str
name: str
lat: float
lon: float
height: float = 30.0
power: float = 43.0
gain: float = 8.0
frequency: float = 1800.0
antenna_type: str = "omni"
azimuth: Optional[float] = None
beamwidth: Optional[float] = None
tilt: Optional[float] = None
color: str = "#ef4444"
visible: bool = True
notes: Optional[str] = None
equipment: Optional[str] = None
sectors: list[Sector] = []

View File

@@ -2,3 +2,9 @@ fastapi==0.110.0
uvicorn[standard]==0.29.0
python-multipart==0.0.9
aiofiles==23.2.1
motor==3.3.2
pymongo==4.6.1
pydantic-settings==2.1.0
numpy==1.26.4
scipy==1.12.0
requests==2.31.0