@mytec: 1.1iter ready for testing
This commit is contained in:
@@ -5,7 +5,11 @@
|
|||||||
"Bash(npm install:*)",
|
"Bash(npm install:*)",
|
||||||
"Bash(npx tsc:*)",
|
"Bash(npx tsc:*)",
|
||||||
"Bash(npm run build:*)",
|
"Bash(npm run build:*)",
|
||||||
"Bash(npx eslint:*)"
|
"Bash(npx eslint:*)",
|
||||||
|
"Bash(tree:*)",
|
||||||
|
"Bash(python:*)",
|
||||||
|
"Bash(pip --version:*)",
|
||||||
|
"Bash(pip install:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
data/terrain/
|
data/terrain/
|
||||||
data/terrain/
|
data/terrain/
|
||||||
|
venv/
|
||||||
|
|||||||
BIN
backend/app/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
backend/app/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/__pycache__/main.cpython-311.pyc
Normal file
BIN
backend/app/__pycache__/main.cpython-311.pyc
Normal file
Binary file not shown.
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
BIN
backend/app/api/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
backend/app/api/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/api/__pycache__/deps.cpython-311.pyc
Normal file
BIN
backend/app/api/__pycache__/deps.cpython-311.pyc
Normal file
Binary file not shown.
5
backend/app/api/deps.py
Normal file
5
backend/app/api/deps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from app.core.database import get_database
|
||||||
|
|
||||||
|
|
||||||
|
async def get_db():
|
||||||
|
return await get_database()
|
||||||
0
backend/app/api/routes/__init__.py
Normal file
0
backend/app/api/routes/__init__.py
Normal file
BIN
backend/app/api/routes/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
backend/app/api/routes/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/api/routes/__pycache__/health.cpython-311.pyc
Normal file
BIN
backend/app/api/routes/__pycache__/health.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/api/routes/__pycache__/projects.cpython-311.pyc
Normal file
BIN
backend/app/api/routes/__pycache__/projects.cpython-311.pyc
Normal file
Binary file not shown.
18
backend/app/api/routes/health.py
Normal file
18
backend/app/api/routes/health.py
Normal 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)}
|
||||||
83
backend/app/api/routes/projects.py
Normal file
83
backend/app/api/routes/projects.py
Normal 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()
|
||||||
0
backend/app/core/__init__.py
Normal file
0
backend/app/core/__init__.py
Normal file
BIN
backend/app/core/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
backend/app/core/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/core/__pycache__/config.cpython-311.pyc
Normal file
BIN
backend/app/core/__pycache__/config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/core/__pycache__/database.cpython-311.pyc
Normal file
BIN
backend/app/core/__pycache__/database.cpython-311.pyc
Normal file
Binary file not shown.
13
backend/app/core/config.py
Normal file
13
backend/app/core/config.py
Normal 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()
|
||||||
22
backend/app/core/database.py
Normal file
22
backend/app/core/database.py
Normal 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()
|
||||||
@@ -1,29 +1,58 @@
|
|||||||
|
import os
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import FileResponse
|
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(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["https://rfcp.eliah.one", "http://localhost:5173"],
|
allow_origins=["https://rfcp.eliah.one", "http://localhost:5173"],
|
||||||
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.get("/health")
|
# Routes
|
||||||
async def health():
|
app.include_router(health.router, prefix="/api/health", tags=["health"])
|
||||||
return {"status": "healthy", "version": "1.0.0"}
|
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}")
|
@app.get("/api/terrain/{region}")
|
||||||
async def get_terrain(region: str):
|
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):
|
if os.path.exists(terrain_path):
|
||||||
return FileResponse(terrain_path)
|
return FileResponse(terrain_path)
|
||||||
return {"error": "Region not found"}, 404
|
return {"error": "Region not found"}, 404
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8090)
|
uvicorn.run(app, host="0.0.0.0", port=8090)
|
||||||
|
|||||||
0
backend/app/models/__init__.py
Normal file
0
backend/app/models/__init__.py
Normal file
BIN
backend/app/models/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
backend/app/models/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/models/__pycache__/project.cpython-311.pyc
Normal file
BIN
backend/app/models/__pycache__/project.cpython-311.pyc
Normal file
Binary file not shown.
BIN
backend/app/models/__pycache__/site.cpython-311.pyc
Normal file
BIN
backend/app/models/__pycache__/site.cpython-311.pyc
Normal file
Binary file not shown.
21
backend/app/models/project.py
Normal file
21
backend/app/models/project.py
Normal 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)
|
||||||
35
backend/app/models/site.py
Normal file
35
backend/app/models/site.py
Normal 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] = []
|
||||||
@@ -2,3 +2,9 @@ fastapi==0.110.0
|
|||||||
uvicorn[standard]==0.29.0
|
uvicorn[standard]==0.29.0
|
||||||
python-multipart==0.0.9
|
python-multipart==0.0.9
|
||||||
aiofiles==23.2.1
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user