a working product with ugly ui

This commit is contained in:
2025-12-12 20:15:27 +05:00
parent e6d04f986f
commit 4d3085623a
77 changed files with 8750 additions and 0 deletions

View 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")