""" 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, }