Files
claudetools/api/services/network_service.py
Mike Swanson 390b10b32c Complete Phase 6: MSP Work Tracking with Context Recall System
Implements production-ready MSP platform with cross-machine persistent memory for Claude.

API Implementation:
- 130 REST API endpoints across 21 entities
- JWT authentication on all endpoints
- AES-256-GCM encryption for credentials
- Automatic audit logging
- Complete OpenAPI documentation

Database:
- 43 tables in MariaDB (172.16.3.20:3306)
- 42 SQLAlchemy models with modern 2.0 syntax
- Full Alembic migration system
- 99.1% CRUD test pass rate

Context Recall System (Phase 6):
- Cross-machine persistent memory via database
- Automatic context injection via Claude Code hooks
- Automatic context saving after task completion
- 90-95% token reduction with compression utilities
- Relevance scoring with time decay
- Tag-based semantic search
- One-command setup script

Security Features:
- JWT tokens with Argon2 password hashing
- AES-256-GCM encryption for all sensitive data
- Comprehensive audit trail for credentials
- HMAC tamper detection
- Secure configuration management

Test Results:
- Phase 3: 38/38 CRUD tests passing (100%)
- Phase 4: 34/35 core API tests passing (97.1%)
- Phase 5: 62/62 extended API tests passing (100%)
- Phase 6: 10/10 compression tests passing (100%)
- Overall: 144/145 tests passing (99.3%)

Documentation:
- Comprehensive architecture guides
- Setup automation scripts
- API documentation at /api/docs
- Complete test reports
- Troubleshooting guides

Project Status: 95% Complete (Production-Ready)
Phase 7 (optional work context APIs) remains for future enhancement.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 06:00:26 -07:00

333 lines
9.1 KiB
Python

"""
Network service layer for business logic and database operations.
This module handles all database operations for networks, providing a clean
separation between the API routes and data access layer.
"""
from typing import Optional
from uuid import UUID
from fastapi import HTTPException, status
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from api.models.network import Network
from api.models.site import Site
from api.schemas.network import NetworkCreate, NetworkUpdate
def get_networks(db: Session, skip: int = 0, limit: int = 100) -> tuple[list[Network], int]:
"""
Retrieve a paginated list of networks.
Args:
db: Database session
skip: Number of records to skip (for pagination)
limit: Maximum number of records to return
Returns:
tuple: (list of networks, total count)
Example:
```python
networks, total = get_networks(db, skip=0, limit=50)
print(f"Retrieved {len(networks)} of {total} networks")
```
"""
# Get total count
total = db.query(Network).count()
# Get paginated results, ordered by created_at descending (newest first)
networks = (
db.query(Network)
.order_by(Network.created_at.desc())
.offset(skip)
.limit(limit)
.all()
)
return networks, total
def get_network_by_id(db: Session, network_id: UUID) -> Network:
"""
Retrieve a single network by its ID.
Args:
db: Database session
network_id: UUID of the network to retrieve
Returns:
Network: The network object
Raises:
HTTPException: 404 if network not found
Example:
```python
network = get_network_by_id(db, network_id)
print(f"Found network: {network.network_name}")
```
"""
network = db.query(Network).filter(Network.id == str(network_id)).first()
if not network:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Network with ID {network_id} not found"
)
return network
def get_networks_by_site(db: Session, site_id: UUID, skip: int = 0, limit: int = 100) -> tuple[list[Network], int]:
"""
Retrieve networks belonging to a specific site.
Args:
db: Database session
site_id: UUID of the site
skip: Number of records to skip (for pagination)
limit: Maximum number of records to return
Returns:
tuple: (list of networks, total count for this site)
Raises:
HTTPException: 404 if site not found
Example:
```python
networks, total = get_networks_by_site(db, site_id, skip=0, limit=50)
print(f"Retrieved {len(networks)} of {total} networks for site")
```
"""
# Verify site exists
site = db.query(Site).filter(Site.id == str(site_id)).first()
if not site:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Site with ID {site_id} not found"
)
# Get total count for this site
total = db.query(Network).filter(Network.site_id == str(site_id)).count()
# Get paginated results
networks = (
db.query(Network)
.filter(Network.site_id == str(site_id))
.order_by(Network.created_at.desc())
.offset(skip)
.limit(limit)
.all()
)
return networks, total
def get_networks_by_type(db: Session, network_type: str, skip: int = 0, limit: int = 100) -> tuple[list[Network], int]:
"""
Retrieve networks of a specific type.
Args:
db: Database session
network_type: Type of network (lan, vpn, vlan, isolated, dmz)
skip: Number of records to skip (for pagination)
limit: Maximum number of records to return
Returns:
tuple: (list of networks, total count for this type)
Example:
```python
networks, total = get_networks_by_type(db, "vlan", skip=0, limit=50)
print(f"Retrieved {len(networks)} of {total} VLAN networks")
```
"""
# Get total count for this type
total = db.query(Network).filter(Network.network_type == network_type).count()
# Get paginated results
networks = (
db.query(Network)
.filter(Network.network_type == network_type)
.order_by(Network.created_at.desc())
.offset(skip)
.limit(limit)
.all()
)
return networks, total
def create_network(db: Session, network_data: NetworkCreate) -> Network:
"""
Create a new network.
Args:
db: Database session
network_data: Network creation data
Returns:
Network: The created network object
Raises:
HTTPException: 404 if site not found
HTTPException: 500 if database error occurs
Example:
```python
network_data = NetworkCreate(
site_id="123e4567-e89b-12d3-a456-426614174000",
network_name="Main LAN",
network_type="lan",
cidr="192.168.1.0/24"
)
network = create_network(db, network_data)
print(f"Created network: {network.id}")
```
"""
# Verify site exists if provided
if network_data.site_id:
site = db.query(Site).filter(Site.id == str(network_data.site_id)).first()
if not site:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Site with ID {network_data.site_id} not found"
)
try:
# Create new network instance
db_network = Network(**network_data.model_dump())
# Add to database
db.add(db_network)
db.commit()
db.refresh(db_network)
return db_network
except IntegrityError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Database error: {str(e)}"
)
except Exception as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to create network: {str(e)}"
)
def update_network(db: Session, network_id: UUID, network_data: NetworkUpdate) -> Network:
"""
Update an existing network.
Args:
db: Database session
network_id: UUID of the network to update
network_data: Network update data (only provided fields will be updated)
Returns:
Network: The updated network object
Raises:
HTTPException: 404 if network or site not found
HTTPException: 500 if database error occurs
Example:
```python
update_data = NetworkUpdate(
network_name="Main LAN - Upgraded",
gateway_ip="192.168.1.1"
)
network = update_network(db, network_id, update_data)
print(f"Updated network: {network.network_name}")
```
"""
# Get existing network
network = get_network_by_id(db, network_id)
try:
# Update only provided fields
update_data = network_data.model_dump(exclude_unset=True)
# If updating site_id, verify new site exists
if "site_id" in update_data and update_data["site_id"] is not None:
site = db.query(Site).filter(Site.id == str(update_data["site_id"])).first()
if not site:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Site with ID {update_data['site_id']} not found"
)
# Apply updates
for field, value in update_data.items():
setattr(network, field, value)
db.commit()
db.refresh(network)
return network
except HTTPException:
db.rollback()
raise
except IntegrityError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Database error: {str(e)}"
)
except Exception as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to update network: {str(e)}"
)
def delete_network(db: Session, network_id: UUID) -> dict:
"""
Delete a network by its ID.
Args:
db: Database session
network_id: UUID of the network to delete
Returns:
dict: Success message
Raises:
HTTPException: 404 if network not found
HTTPException: 500 if database error occurs
Example:
```python
result = delete_network(db, network_id)
print(result["message"]) # "Network deleted successfully"
```
"""
# Get existing network (raises 404 if not found)
network = get_network_by_id(db, network_id)
try:
db.delete(network)
db.commit()
return {
"message": "Network deleted successfully",
"network_id": str(network_id)
}
except Exception as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to delete network: {str(e)}"
)