"""Domains API routes for AnchorHost.""" from domain_runtime import domain_manager from domain_service import create_domain_record, delete_domain_record, get_domain_by_id, get_user_domains, link_domain_to_server, unlink_domain_from_server from flask import Blueprint, jsonify, request from runtime_config import ( logger, ) from server_records import get_server_by_id from user_service import authenticate_user, get_bearer_token, require_admin from websocket_service import sync_user_domains_with_dns __all__ = ['create_domain_api', 'delete_domain_api', 'fix_all_dnssec_api', 'get_domains_api', 'link_domain_api', 'sync_domains_api', 'test_domain_api', 'unlink_domain_api', 'verify_dnssec_api'] bp = Blueprint("domains", __name__) @bp.route('/api/dnssec/fix-all', methods=['POST']) def fix_all_dnssec_api(): """Retired: DNSSEC now belongs to the parent zone, not user namespaces.""" try: token = get_bearer_token() user = authenticate_user(token) # Only allow admin users if not user or user.get('role') != 'admin': return jsonify({'error': 'Admin access required'}), 403 return jsonify({'error': 'Per-user DNSSEC delegation is retired; DNSSEC is managed on the parent zone.'}), 410 except Exception as e: logger.error(f"Error fixing DNSSEC: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/dnssec/verify/', methods=['GET']) def verify_dnssec_api(username): """Retired: DNSSEC is inherited from the parent zone.""" try: token = get_bearer_token() user = authenticate_user(token) # Users can only check their own DNSSEC, admins can check any if not user or (user.get('role') != 'admin' and user.get('username') != username): return jsonify({'error': 'Unauthorized'}), 403 return jsonify({'error': 'Per-user DNSSEC delegation is retired; DNSSEC is managed on the parent zone.'}), 410 except Exception as e: logger.error(f"Error verifying DNSSEC: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/domains', methods=['GET']) def get_domains_api(): """Get domains API endpoint""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 include_serverless = request.args.get('include_serverless', 'true').lower() == 'true' domains = get_user_domains(user['id'], include_serverless) return jsonify({ 'success': True, 'domains': domains, 'count': len(domains) }), 200 except Exception as e: logger.error(f"Error getting domains: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/domains/sync', methods=['POST']) def sync_domains_api(): """Sync domains between database and DNS""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 success = sync_user_domains_with_dns(user['id']) if success: domains = get_user_domains(user['id'], True) return jsonify({ 'success': True, 'message': 'DNS namespace verified and wildcard records are ready', 'domains': domains, 'count': len(domains) }), 200 else: return jsonify({'error': 'Failed to sync domains'}), 500 except Exception as e: logger.error(f"Error syncing domains: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/domains', methods=['POST']) def create_domain_api(): """Create domain API endpoint""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 if not domain_manager: return jsonify({'error': 'Domain service not available'}), 503 data = request.get_json() subdomain = data.get('subdomain') server_id = data.get('serverId') if not subdomain: return jsonify({'error': 'Subdomain name required'}), 400 # Check if subdomain is available availability_result = domain_manager.check_domain_availability(subdomain, user['username']) if not availability_result.get('available', False): return jsonify({'error': availability_result.get('message', 'Domain not available')}), 400 # Create the subdomain result = domain_manager.create_subdomain(user['username'], subdomain) if result['success'] and result.get('domain_data'): # Save to database domain_record = { 'user_id': user['id'], 'subdomain': result['domain_data']['subdomain'], 'full_domain': result['domain_data']['domain'], 'record_type': result['domain_data']['record_type'], 'ip_address': result['domain_data']['ip_address'], 'ttl': result['domain_data']['ttl'], 'server_id': server_id, 'created_at': result['domain_data']['created_at'] } db_domain = create_domain_record(domain_record) if db_domain: return jsonify({ 'success': True, 'domain': db_domain, 'message': result['message'] }), 201 else: return jsonify({'error': 'Domain created in DNS but failed to save to database'}), 500 else: return jsonify({'error': result.get('message', 'Failed to create domain')}), 500 except Exception as e: logger.error(f"Error creating domain: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/domains/', methods=['DELETE']) def delete_domain_api(domain_id): """Delete domain API endpoint""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 if not domain_manager: return jsonify({'error': 'Domain service not available'}), 503 # Get domain from database domain = get_domain_by_id(domain_id) if not domain or domain['user_id'] != user['id']: return jsonify({'error': 'Domain not found or access denied'}), 404 # Delete from DNS result = domain_manager.delete_subdomain(user['username'], domain['subdomain']) if result['success']: # Delete from database if delete_domain_record(domain_id): return jsonify({ 'success': True, 'message': result['message'] }), 200 else: return jsonify({'error': 'Domain deleted from DNS but failed to delete from database'}), 500 else: return jsonify({'error': result.get('message', 'Failed to delete domain')}), 500 except Exception as e: logger.error(f"Error deleting domain: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/domains//link', methods=['POST']) def link_domain_api(domain_id): """Link domain to server API endpoint""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 data = request.get_json() server_id = data.get('serverId') if not server_id: return jsonify({'error': 'Server ID required'}), 400 # Verify domain belongs to user domain = get_domain_by_id(domain_id) if not domain or domain['user_id'] != user['id']: return jsonify({'error': 'Domain not found or access denied'}), 404 # Verify server belongs to user server = get_server_by_id(server_id) if not server or server['owner_id'] != user['id']: return jsonify({'error': 'Server not found or access denied'}), 404 # Check if server is a website or VPS if server['type'] not in ['website', 'vps']: return jsonify({'error': 'Domain can only be linked to Website or VPS servers'}), 400 # Link domain to server (this will create IngressRoute) if link_domain_to_server(domain_id, server_id): return jsonify({ 'success': True, 'message': f'Domain linked to server {server["name"]} with IngressRoute created' }), 200 else: return jsonify({'error': 'Failed to link domain'}), 500 except Exception as e: logger.error(f"Error linking domain: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/domains//unlink', methods=['POST']) def unlink_domain_api(domain_id): """Unlink domain from server API endpoint""" try: token = get_bearer_token() user = authenticate_user(token) if not user: return jsonify({'error': 'Unauthorized'}), 401 # Verify domain belongs to user domain = get_domain_by_id(domain_id) if not domain or domain['user_id'] != user['id']: return jsonify({'error': 'Domain not found or access denied'}), 404 # Unlink domain from server (this will delete IngressRoute) if unlink_domain_from_server(domain_id): return jsonify({ 'success': True, 'message': 'Domain unlinked from server and IngressRoute removed' }), 200 else: return jsonify({'error': 'Failed to unlink domain'}), 500 except Exception as e: logger.error(f"Error unlinking domain: {e}") return jsonify({'error': str(e)}), 500 @bp.route('/api/domains/test', methods=['GET']) def test_domain_api(): """Test domain API connection""" if not require_admin(): return jsonify({'error': 'Unauthorized'}), 403 try: if not domain_manager: return jsonify({'success': False, 'message': 'Domain manager not initialized'}), 503 success = domain_manager.test_connection() return jsonify({ 'success': success, 'message': 'Domain API connection test completed', 'connected': success }), 200 except Exception as e: logger.error(f"Error testing domain API: {e}") return jsonify({'success': False, 'error': str(e)}), 500