a working product with ugly ui
This commit is contained in:
227
app/services/item_service.py
Normal file
227
app/services/item_service.py
Normal file
@@ -0,0 +1,227 @@
|
||||
"""Map item service for business logic."""
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import HTTPException, status
|
||||
from geoalchemy2.shape import from_shape, to_shape
|
||||
from shapely.geometry import shape, Point, LineString
|
||||
import json
|
||||
|
||||
from app.models.map_item import MapItem
|
||||
from app.models.user import User
|
||||
from app.schemas.map_item import MapItemCreate, MapItemUpdate
|
||||
from app.services.map_service import get_map_by_id
|
||||
|
||||
|
||||
def get_map_items(db: Session, map_id: UUID, user: Optional[User] = None) -> List[MapItem]:
|
||||
"""Get all items for a map."""
|
||||
# Verify user has access to the map
|
||||
get_map_by_id(db, map_id, user)
|
||||
|
||||
items = db.query(MapItem).filter(MapItem.map_id == map_id).all()
|
||||
return items
|
||||
|
||||
|
||||
def get_map_item_by_id(db: Session, item_id: UUID, user: Optional[User] = None) -> MapItem:
|
||||
"""Get a map item by ID."""
|
||||
item = db.query(MapItem).filter(MapItem.id == item_id).first()
|
||||
|
||||
if not item:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Map item not found"
|
||||
)
|
||||
|
||||
# Verify user has access to the map
|
||||
get_map_by_id(db, item.map_id, user)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def geojson_to_geography(geojson: dict) -> str:
|
||||
"""Convert GeoJSON geometry to PostGIS geography WKT."""
|
||||
geom = shape(geojson)
|
||||
|
||||
# Ensure coordinates are in the correct format for PostGIS (lon, lat)
|
||||
if isinstance(geom, Point):
|
||||
return f'SRID=4326;POINT({geom.x} {geom.y})'
|
||||
elif isinstance(geom, LineString):
|
||||
coords = ', '.join([f'{x} {y}' for x, y in geom.coords])
|
||||
return f'SRID=4326;LINESTRING({coords})'
|
||||
else:
|
||||
raise ValueError(f"Unsupported geometry type: {type(geom)}")
|
||||
|
||||
|
||||
def geography_to_geojson(geography) -> dict:
|
||||
"""Convert PostGIS geography to GeoJSON."""
|
||||
geom = to_shape(geography)
|
||||
return json.loads(json.dumps(geom.__geo_interface__))
|
||||
|
||||
|
||||
def create_map_item(db: Session, map_id: UUID, item_data: MapItemCreate, user: User) -> MapItem:
|
||||
"""Create a new map item."""
|
||||
# Verify user has access to the map
|
||||
get_map_by_id(db, map_id, user)
|
||||
|
||||
# Convert GeoJSON to PostGIS geography
|
||||
geometry_wkt = geojson_to_geography(item_data.geometry)
|
||||
|
||||
item = MapItem(
|
||||
map_id=map_id,
|
||||
type=item_data.type,
|
||||
geometry=geometry_wkt,
|
||||
properties=item_data.properties,
|
||||
created_by=user.id,
|
||||
updated_by=user.id
|
||||
)
|
||||
|
||||
db.add(item)
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
|
||||
# If this is a cable with device connections, update device port tracking
|
||||
if item.type == 'cable':
|
||||
start_device_id = item.properties.get('start_device_id')
|
||||
end_device_id = item.properties.get('end_device_id')
|
||||
|
||||
print(f"Cable created: start_device_id={start_device_id}, end_device_id={end_device_id}")
|
||||
|
||||
if start_device_id:
|
||||
print(f"Updating port connections for start device: {start_device_id}")
|
||||
update_device_connections(db, UUID(start_device_id), item.id)
|
||||
if end_device_id:
|
||||
print(f"Updating port connections for end device: {end_device_id}")
|
||||
update_device_connections(db, UUID(end_device_id), item.id)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def update_device_connections(db: Session, device_id: UUID, cable_id: UUID) -> None:
|
||||
"""Add cable connection to device's connections array."""
|
||||
print(f"update_device_connections called: device_id={device_id}, cable_id={cable_id}")
|
||||
device = db.query(MapItem).filter(MapItem.id == device_id).first()
|
||||
if not device:
|
||||
print(f"Device not found: {device_id}")
|
||||
return
|
||||
|
||||
# Create a mutable copy of properties
|
||||
properties = dict(device.properties) if device.properties else {}
|
||||
connections = properties.get('connections', [])
|
||||
port_count = properties.get('port_count', 0)
|
||||
|
||||
print(f"Device {device_id}: port_count={port_count}, current_connections={len(connections)}")
|
||||
|
||||
# Find next available port
|
||||
used_ports = {conn.get('port_number') for conn in connections if isinstance(conn, dict)}
|
||||
next_port = 1
|
||||
while next_port in used_ports and next_port <= port_count:
|
||||
next_port += 1
|
||||
|
||||
# Only add if there's an available port
|
||||
if next_port <= port_count:
|
||||
connections.append({
|
||||
'cable_id': str(cable_id),
|
||||
'port_number': next_port
|
||||
})
|
||||
properties['connections'] = connections
|
||||
|
||||
# Mark the column as modified so SQLAlchemy detects the change
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
device.properties = properties
|
||||
flag_modified(device, 'properties')
|
||||
|
||||
db.commit()
|
||||
db.refresh(device)
|
||||
print(f"Added connection to port {next_port}. Total connections now: {len(connections)}")
|
||||
print(f"Device properties after update: {device.properties}")
|
||||
else:
|
||||
print(f"No available ports! Port count: {port_count}, used: {len(connections)}")
|
||||
|
||||
|
||||
def update_map_item(db: Session, item_id: UUID, item_data: MapItemUpdate, user: User) -> MapItem:
|
||||
"""Update a map item."""
|
||||
item = get_map_item_by_id(db, item_id, user)
|
||||
|
||||
# Update fields if provided
|
||||
if item_data.type is not None:
|
||||
item.type = item_data.type
|
||||
if item_data.geometry is not None:
|
||||
item.geometry = geojson_to_geography(item_data.geometry)
|
||||
if item_data.properties is not None:
|
||||
item.properties = item_data.properties
|
||||
|
||||
item.updated_by = user.id
|
||||
|
||||
db.commit()
|
||||
db.refresh(item)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def delete_map_item(db: Session, item_id: UUID, user: User) -> None:
|
||||
"""Delete a map item."""
|
||||
item = get_map_item_by_id(db, item_id, user)
|
||||
|
||||
# If deleting a cable, remove it from device connections
|
||||
if item.type == 'cable':
|
||||
start_device_id = item.properties.get('start_device_id')
|
||||
end_device_id = item.properties.get('end_device_id')
|
||||
|
||||
if start_device_id:
|
||||
remove_device_connection(db, UUID(start_device_id), item.id)
|
||||
if end_device_id:
|
||||
remove_device_connection(db, UUID(end_device_id), item.id)
|
||||
|
||||
# If deleting an AP, delete all associated wireless mesh links
|
||||
if item.type in ['indoor_ap', 'outdoor_ap']:
|
||||
print(f"Deleting AP {item.id}, checking for wireless mesh links...")
|
||||
# Find all wireless mesh links connected to this AP
|
||||
wireless_meshes = db.query(MapItem).filter(
|
||||
MapItem.map_id == item.map_id,
|
||||
MapItem.type == 'wireless_mesh'
|
||||
).all()
|
||||
|
||||
for mesh in wireless_meshes:
|
||||
start_ap_id = mesh.properties.get('start_ap_id')
|
||||
end_ap_id = mesh.properties.get('end_ap_id')
|
||||
|
||||
if start_ap_id == str(item.id) or end_ap_id == str(item.id):
|
||||
print(f"Deleting wireless mesh {mesh.id} connected to AP {item.id}")
|
||||
db.delete(mesh)
|
||||
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
|
||||
|
||||
def remove_device_connection(db: Session, device_id: UUID, cable_id: UUID) -> None:
|
||||
"""Remove cable connection from device's connections array."""
|
||||
print(f"remove_device_connection called: device_id={device_id}, cable_id={cable_id}")
|
||||
device = db.query(MapItem).filter(MapItem.id == device_id).first()
|
||||
if not device:
|
||||
print(f"Device not found: {device_id}")
|
||||
return
|
||||
|
||||
# Create a mutable copy of properties
|
||||
properties = dict(device.properties) if device.properties else {}
|
||||
connections = properties.get('connections', [])
|
||||
|
||||
print(f"Before removal: {len(connections)} connections")
|
||||
|
||||
# Filter out the cable connection
|
||||
connections = [
|
||||
conn for conn in connections
|
||||
if isinstance(conn, dict) and conn.get('cable_id') != str(cable_id)
|
||||
]
|
||||
|
||||
print(f"After removal: {len(connections)} connections")
|
||||
|
||||
properties['connections'] = connections
|
||||
|
||||
# Mark the column as modified so SQLAlchemy detects the change
|
||||
from sqlalchemy.orm.attributes import flag_modified
|
||||
device.properties = properties
|
||||
flag_modified(device, 'properties')
|
||||
|
||||
db.commit()
|
||||
db.refresh(device)
|
||||
print(f"Removed cable connection. Device now has {len(connections)} connections")
|
||||
Reference in New Issue
Block a user