homedocs/app/utils/app_utils.py
2025-04-03 16:58:01 +02:00

246 lines
8.1 KiB
Python

from app.core.models import App, Port, Server
from app.core.extensions import db
from flask import flash
import re
def validate_app_data(name, server_id, existing_app_id=None):
"""
Validate application data
Returns tuple (valid, error_message)
"""
if not name or not server_id:
return False, "Please fill in all required fields"
# Check if app exists with same name on same server
query = App.query.filter(App.name == name, App.server_id == server_id)
# If editing, exclude the current app
if existing_app_id:
query = query.filter(App.id != existing_app_id)
if query.first():
return False, f"Application '{name}' already exists on this server"
# Find similar app names on this server for suggestion
similar_apps = App.query.filter(
App.name.ilike(f"%{name}%"),
App.server_id == server_id
).limit(3).all()
if similar_apps and (not existing_app_id or not any(app.id == existing_app_id for app in similar_apps)):
similar_names = ", ".join([f"'{app.name}'" for app in similar_apps])
return False, f"Similar application names found on this server: {similar_names}"
return True, None
def is_port_in_use(port, server_id, exclude_app_id=None):
"""
Check if a port is already in use on a server
Args:
port: The port number to check
server_id: The ID of the server
exclude_app_id: Optional app ID to exclude from the check (for editing an app)
Returns:
bool: True if port is in use, False otherwise
"""
from app.core.models import App, Port
# Get all apps on this server
apps_on_server = App.query.filter_by(server_id=server_id).all()
for app in apps_on_server:
# Skip the app we're editing
if exclude_app_id and app.id == int(exclude_app_id):
continue
# Check if this app uses the port - use port_number rather than number
for app_port in app.ports:
if int(app_port.port_number) == int(port): # Use port_number here
return True
return False
def validate_port_data(ports, descriptions=None, server_id=None, exclude_app_id=None, protocol=None):
"""
Validate port data - works with both the API and form submissions
Args:
ports: List of port numbers or a single port number string
descriptions: List of port descriptions or a single description
server_id: The server ID
exclude_app_id: Optional app ID to exclude from port conflict check
protocol: Optional protocol (for API validation)
Returns:
For form validation: Error message string or None if valid
For API validation: (valid, clean_port, error_message) tuple
"""
# Handle the API call format
if protocol is not None:
# This is the API validation path
try:
port = int(ports)
if port < 1 or port > 65535:
return False, None, f"Port {port} is out of valid range (1-65535)"
# Check if port is already in use
if is_port_in_use(port, server_id, exclude_app_id):
return False, None, f"Port {port} is already in use on this server"
return True, port, None
except ValueError:
return False, None, "Invalid port number"
# Handle the form submission format (list of ports)
seen_ports = set()
# Make sure ports is a list
if not isinstance(ports, list):
ports = [ports]
# Make sure descriptions is a list (or empty list)
if descriptions is None:
descriptions = []
elif not isinstance(descriptions, list):
descriptions = [descriptions]
for i, port_str in enumerate(ports):
if not port_str: # Skip empty port entries
continue
try:
port = int(port_str)
if port < 1 or port > 65535:
return f"Port {port} is out of valid range (1-65535)"
# Check for duplicate ports in the submitted data
if port in seen_ports:
return f"Duplicate port {port} in submission"
seen_ports.add(port)
# Check if port is already in use on this server
if is_port_in_use(port, server_id, exclude_app_id):
return f"Port {port} is already in use on this server"
except ValueError:
return f"Invalid port number: {port_str}"
return None
def process_app_ports(app_id, port_data, server_id=None):
"""
Process port data for an application
port_data should be a list of tuples (port_number, protocol, description)
Returns (success, error_message)
"""
# Get the app's server_id if not provided
if not server_id and app_id:
app = App.query.get(app_id)
if app:
server_id = app.server_id
# If no port data is provided, that's valid (app with no ports)
if not port_data:
return True, None
# Track the port+protocol combinations we've seen to avoid duplicates
seen_ports = set()
try:
for port_number, protocol, description in port_data:
# Skip empty port entries
if not port_number or not port_number.strip():
continue
port_key = f"{port_number}/{protocol}"
# Check for duplicates within this form submission
if port_key in seen_ports:
return False, f"Duplicate port: {port_key} is specified multiple times"
seen_ports.add(port_key)
# Validate the port data
valid, clean_port, error = validate_port_data(
[port_number], [description], server_id, app_id
)
if not valid:
return False, error
# Check if port already exists for this app
existing_port = Port.query.filter_by(
app_id=app_id, port_number=clean_port, protocol=protocol
).first()
if existing_port:
# Update existing port
existing_port.description = description
else:
# Create new port
new_port = Port(
app_id=app_id,
port_number=clean_port,
protocol=protocol,
description=description,
)
db.session.add(new_port)
return True, None
except Exception as e:
db.session.rollback()
return False, str(e)
def save_app(name, server_id, documentation, port_data, app_id=None, url=None):
"""
Save or update an application
Returns (success, app, error_message)
"""
try:
# Validate application data
valid, error = validate_app_data(name, server_id, app_id)
if not valid:
return False, None, error
if app_id:
# Update existing app
app = App.query.get(app_id)
if not app:
return False, None, "Application not found"
app.name = name
app.server_id = server_id
app.documentation = documentation
app.url = url
else:
# Create new app
app = App(
name=name,
server_id=server_id,
documentation=documentation,
url=url
)
db.session.add(app)
db.session.flush() # Get the app ID without committing
# Remove all existing ports if updating
if app_id:
Port.query.filter_by(app_id=app_id).delete()
# Process and save ports
port_success, port_error = process_app_ports(app.id, port_data, server_id)
if not port_success:
db.session.rollback()
return False, None, port_error
db.session.commit()
return True, app, None
except Exception as e:
db.session.rollback()
return False, None, str(e)