Initial commit — multi-tenant filtering, port constraints, chart bbox
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
"""
|
||||
Organization CRUD: Ports, Companies, BuoyOwnership.
|
||||
|
||||
GET /org/ports → list ports
|
||||
POST /org/ports → create port (admin)
|
||||
PUT /org/ports/{id} → update port (admin)
|
||||
|
||||
GET /org/companies → list companies
|
||||
POST /org/companies → create company (admin)
|
||||
PUT /org/companies/{id} → update company (admin)
|
||||
|
||||
GET /org/companies/{company_id}/buoys → list owned buoys
|
||||
POST /org/companies/{company_id}/buoys → assign buoy to company (admin)
|
||||
DELETE /org/companies/{company_id}/buoys/{id} → remove ownership (admin)
|
||||
|
||||
GET /org/me/company → current user's company + port (for homepage)
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
import uuid
|
||||
import os
|
||||
import json
|
||||
|
||||
from database import get_db
|
||||
from models.org import Port, Company, BuoyOwnership
|
||||
from models.user import User
|
||||
from models.aid import Aid
|
||||
from routers.auth import get_current_user, require_admin
|
||||
|
||||
# Charts directory — one level above the backend package
|
||||
_CHARTS_DIR = os.path.normpath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'charts')
|
||||
)
|
||||
|
||||
def _read_chart_bbox(chart_name: str) -> list | None:
|
||||
"""Return [west, south, east, north] from the chart's meta.json, or None."""
|
||||
if not chart_name:
|
||||
return None
|
||||
meta = os.path.join(_CHARTS_DIR, chart_name, 'meta.json')
|
||||
try:
|
||||
with open(meta, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
bbox = data.get('bbox')
|
||||
if bbox and len(bbox) == 4:
|
||||
return bbox # [minLon, minLat, maxLon, maxLat]
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
router = APIRouter(prefix="/org", tags=["org"])
|
||||
|
||||
|
||||
# ── Ports ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/ports")
|
||||
def list_ports(db: Session = Depends(get_db)):
|
||||
return [_port_dict(p) for p in db.query(Port).filter(Port.activo == True).all()]
|
||||
|
||||
|
||||
@router.post("/ports", dependencies=[Depends(require_admin)])
|
||||
def create_port(data: dict, db: Session = Depends(get_db)):
|
||||
if not data.get("name"):
|
||||
raise HTTPException(400, "name is required")
|
||||
port = Port(
|
||||
id=str(uuid.uuid4()),
|
||||
name=data["name"],
|
||||
country=data.get("country", "Colombia"),
|
||||
center_lat=data.get("center_lat"),
|
||||
center_lon=data.get("center_lon"),
|
||||
default_zoom=data.get("default_zoom", 12.0),
|
||||
chart_name=data.get("chart_name"),
|
||||
)
|
||||
db.add(port); db.commit()
|
||||
return _port_dict(port)
|
||||
|
||||
|
||||
@router.put("/ports/{port_id}", dependencies=[Depends(require_admin)])
|
||||
def update_port(port_id: str, data: dict, db: Session = Depends(get_db)):
|
||||
port = db.query(Port).filter(Port.id == port_id).first()
|
||||
if not port:
|
||||
raise HTTPException(404, "Port not found")
|
||||
for field in ("name", "country", "center_lat", "center_lon",
|
||||
"default_zoom", "chart_name", "activo"):
|
||||
if field in data:
|
||||
setattr(port, field, data[field])
|
||||
db.commit()
|
||||
return _port_dict(port)
|
||||
|
||||
|
||||
def _port_dict(p: Port) -> dict:
|
||||
return {
|
||||
"id": p.id, "name": p.name, "country": p.country,
|
||||
"center_lat": p.center_lat, "center_lon": p.center_lon,
|
||||
"default_zoom": p.default_zoom, "chart_name": p.chart_name,
|
||||
"chart_bbox": _read_chart_bbox(p.chart_name), # [W,S,E,N] or null
|
||||
"activo": p.activo,
|
||||
}
|
||||
|
||||
|
||||
# ── Companies ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/companies")
|
||||
def list_companies(db: Session = Depends(get_db)):
|
||||
return [_company_dict(c) for c in db.query(Company).all()]
|
||||
|
||||
|
||||
@router.post("/companies", dependencies=[Depends(require_admin)])
|
||||
def create_company(data: dict, db: Session = Depends(get_db)):
|
||||
if not data.get("name"):
|
||||
raise HTTPException(400, "name is required")
|
||||
company = Company(
|
||||
id=str(uuid.uuid4()),
|
||||
name=data["name"],
|
||||
port_id=data.get("port_id"),
|
||||
contact_email=data.get("contact_email"),
|
||||
contact_phone=data.get("contact_phone"),
|
||||
notas=data.get("notas"),
|
||||
)
|
||||
db.add(company); db.commit()
|
||||
return _company_dict(company)
|
||||
|
||||
|
||||
@router.put("/companies/{company_id}", dependencies=[Depends(require_admin)])
|
||||
def update_company(company_id: str, data: dict, db: Session = Depends(get_db)):
|
||||
company = db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
raise HTTPException(404, "Company not found")
|
||||
for field in ("name", "port_id", "contact_email", "contact_phone", "notas", "activa"):
|
||||
if field in data:
|
||||
setattr(company, field, data[field])
|
||||
db.commit()
|
||||
return _company_dict(company)
|
||||
|
||||
|
||||
def _company_dict(c: Company) -> dict:
|
||||
return {
|
||||
"id": c.id, "name": c.name, "port_id": c.port_id,
|
||||
"contact_email": c.contact_email, "contact_phone": c.contact_phone,
|
||||
"activa": c.activa, "notas": c.notas,
|
||||
}
|
||||
|
||||
|
||||
# ── Buoy Ownership ────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/companies/{company_id}/buoys")
|
||||
def list_company_buoys(company_id: str, db: Session = Depends(get_db)):
|
||||
rows = db.query(BuoyOwnership).filter(BuoyOwnership.company_id == company_id).all()
|
||||
result = []
|
||||
for r in rows:
|
||||
entry = {"id": r.id, "company_id": r.company_id,
|
||||
"aid_id": r.aid_id, "mmsi": r.mmsi, "notas": r.notas}
|
||||
# Enrich with aid name if available
|
||||
if r.aid_id:
|
||||
aid = db.query(Aid).filter(Aid.id == r.aid_id).first()
|
||||
if aid:
|
||||
entry["aid_nombre"] = aid.nombre
|
||||
entry["mmsi"] = entry["mmsi"] or aid.mmsi
|
||||
result.append(entry)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/companies/{company_id}/buoys", dependencies=[Depends(require_admin)])
|
||||
def assign_buoy(company_id: str, data: dict, db: Session = Depends(get_db)):
|
||||
"""
|
||||
Assign a buoy to a company. Provide either aid_id or mmsi (or both).
|
||||
If aid_id is given, mmsi is auto-filled from the Aid row.
|
||||
"""
|
||||
company = db.query(Company).filter(Company.id == company_id).first()
|
||||
if not company:
|
||||
raise HTTPException(404, "Company not found")
|
||||
|
||||
aid_id = data.get("aid_id")
|
||||
mmsi = data.get("mmsi")
|
||||
|
||||
if not aid_id and not mmsi:
|
||||
raise HTTPException(400, "Provide aid_id or mmsi")
|
||||
|
||||
if aid_id:
|
||||
aid = db.query(Aid).filter(Aid.id == aid_id).first()
|
||||
if not aid:
|
||||
raise HTTPException(404, "Aid not found")
|
||||
mmsi = mmsi or aid.mmsi # fill from aid if not explicitly given
|
||||
|
||||
# Prevent duplicate
|
||||
existing = db.query(BuoyOwnership).filter(
|
||||
BuoyOwnership.company_id == company_id,
|
||||
BuoyOwnership.aid_id == aid_id,
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(409, "Already assigned")
|
||||
|
||||
row = BuoyOwnership(
|
||||
id=str(uuid.uuid4()),
|
||||
company_id=company_id,
|
||||
aid_id=aid_id,
|
||||
mmsi=mmsi,
|
||||
notas=data.get("notas"),
|
||||
)
|
||||
db.add(row); db.commit()
|
||||
return {"ok": True, "id": row.id, "mmsi": mmsi}
|
||||
|
||||
|
||||
@router.delete("/companies/{company_id}/buoys/{ownership_id}",
|
||||
dependencies=[Depends(require_admin)])
|
||||
def remove_ownership(company_id: str, ownership_id: str, db: Session = Depends(get_db)):
|
||||
row = db.query(BuoyOwnership).filter(
|
||||
BuoyOwnership.id == ownership_id,
|
||||
BuoyOwnership.company_id == company_id,
|
||||
).first()
|
||||
if not row:
|
||||
raise HTTPException(404, "Ownership record not found")
|
||||
db.delete(row); db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ── Current user's company / home port ───────────────────────────────────────
|
||||
|
||||
@router.get("/me/company")
|
||||
def my_company(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Returns the company and port associated with the logged-in user.
|
||||
Used by the frontend to set the default map view on login.
|
||||
"""
|
||||
company_id = getattr(current_user, "company_id", None)
|
||||
if not company_id:
|
||||
return {"company": None, "port": None}
|
||||
|
||||
company = db.query(Company).filter(Company.id == company_id).first()
|
||||
port = db.query(Port).filter(Port.id == company.port_id).first() \
|
||||
if company and company.port_id else None
|
||||
|
||||
return {
|
||||
"company": _company_dict(company) if company else None,
|
||||
"port": _port_dict(port) if port else None,
|
||||
}
|
||||
Reference in New Issue
Block a user