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
+106
View File
@@ -0,0 +1,106 @@
from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from app.models.user import User
from app.schemas.user import UserCreate
from app.schemas.auth import TokenResponse
from app.utils.password import hash_password, verify_password
from app.utils.security import create_access_token, create_refresh_token
from app.config import settings
def authenticate_user(db: Session, username: str, password: str) -> User:
"""
Authenticate a user by username and password.
Args:
db: Database session
username: Username
password: Plain text password
Returns:
User object if authentication successful
Raises:
HTTPException: If authentication fails
"""
user = db.query(User).filter(User.username == username).first()
if not user or not verify_password(password, user.password_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return user
def create_user(db: Session, user_data: UserCreate) -> User:
"""
Create a new user.
Args:
db: Database session
user_data: User creation data
Returns:
Created user object
Raises:
HTTPException: If username or email already exists
"""
# Check if registration is allowed
if not settings.ALLOW_REGISTRATION:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="User registration is currently disabled"
)
# Check if username already exists
if db.query(User).filter(User.username == user_data.username).first():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already registered"
)
# Check if email already exists
if db.query(User).filter(User.email == user_data.email).first():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered"
)
# Create new user
hashed_password = hash_password(user_data.password)
db_user = User(
username=user_data.username,
email=user_data.email,
password_hash=hashed_password,
is_admin=False
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def create_tokens_for_user(user: User) -> TokenResponse:
"""
Create access and refresh tokens for a user.
Args:
user: User object
Returns:
TokenResponse with access and refresh tokens
"""
access_token = create_access_token(str(user.id))
refresh_token = create_refresh_token(str(user.id))
return TokenResponse(
access_token=access_token,
refresh_token=refresh_token,
token_type="bearer"
)
+227
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")
+103
View File
@@ -0,0 +1,103 @@
"""Map service for business logic."""
from typing import List, Optional
from uuid import UUID
from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from app.models.map import Map
from app.models.user import User
from app.schemas.map import MapCreate, MapUpdate
def get_user_maps(db: Session, user_id: UUID) -> List[Map]:
"""Get all maps owned by a user."""
return db.query(Map).filter(Map.owner_id == user_id).order_by(Map.updated_at.desc()).all()
def get_map_by_id(db: Session, map_id: UUID, user: Optional[User] = None) -> Map:
"""Get a map by ID with optional authorization check."""
map_obj = db.query(Map).filter(Map.id == map_id).first()
if not map_obj:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Map not found"
)
# If user is provided, check authorization
if user:
if map_obj.owner_id != user.id and not user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have permission to access this map"
)
return map_obj
def get_default_public_map(db: Session) -> Optional[Map]:
"""Get the default public map (first map with is_default_public=True)."""
return db.query(Map).filter(Map.is_default_public == True).first()
def create_map(db: Session, map_data: MapCreate, user_id: UUID) -> Map:
"""Create a new map."""
map_obj = Map(
name=map_data.name,
description=map_data.description,
owner_id=user_id,
is_default_public=False
)
db.add(map_obj)
db.commit()
db.refresh(map_obj)
return map_obj
def update_map(db: Session, map_id: UUID, map_data: MapUpdate, user: User) -> Map:
"""Update a map. Only owner or admin can update."""
map_obj = get_map_by_id(db, map_id, user)
# Update fields if provided
if map_data.name is not None:
map_obj.name = map_data.name
if map_data.description is not None:
map_obj.description = map_data.description
db.commit()
db.refresh(map_obj)
return map_obj
def delete_map(db: Session, map_id: UUID, user: User) -> None:
"""Delete a map. Only owner or admin can delete."""
map_obj = get_map_by_id(db, map_id, user)
db.delete(map_obj)
db.commit()
def set_default_public_map(db: Session, map_id: UUID, user: User) -> Map:
"""Set a map as the default public map. Only admins can do this."""
if not user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only administrators can set the default public map"
)
# Get the map (admin check is done in get_map_by_id)
map_obj = get_map_by_id(db, map_id, user)
# Unset any existing default public maps
db.query(Map).filter(Map.is_default_public == True).update({"is_default_public": False})
# Set this map as default public
map_obj.is_default_public = True
db.commit()
db.refresh(map_obj)
return map_obj