""" Customer Routes - Domain Management Customer-specific endpoints with isolation """ from flask import Blueprint, request, jsonify from app.models.domain import db, Domain, DNSRecord from app.models.user import Customer from app.services.auth_service import token_required from app.services.admin_api_service import AdminAPIService from datetime import datetime customer_bp = Blueprint('customer', __name__, url_prefix='/api/customer') @customer_bp.route('/domains', methods=['GET']) @token_required def get_domains(current_user): """Get all domains for the current customer""" try: # Get customer customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domains with customer isolation domains = Domain.query.filter_by(customer_id=customer.id).all() # Fetch CF account names from Admin Panel (batch) cf_account_names = {} if domains: # Get unique CF account IDs cf_account_ids = set( d.cf_account_id for d in domains if d.cf_account_type == 'company' and d.cf_account_id ) if cf_account_ids: admin_api = AdminAPIService() accounts_result = admin_api.get_available_cf_accounts() if accounts_result['status'] == 'success': for acc in accounts_result.get('accounts', []): cf_account_names[acc['id']] = acc['name'] # Add CF account info result = [] for domain in domains: domain_dict = domain.to_dict() # Add CF account name if using company account if domain.cf_account_type == 'company' and domain.cf_account_id: domain_dict['cf_account_name'] = cf_account_names.get( domain.cf_account_id, f'CF Account #{domain.cf_account_id}' ) else: domain_dict['cf_account_name'] = 'Own Account' # Add DNS record count domain_dict['dns_record_count'] = len(domain.dns_records) result.append(domain_dict) return jsonify({ 'domains': result, 'total': len(result), 'limit': customer.max_domains }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains/', methods=['GET']) @token_required def get_domain(current_user, domain_id): """Get specific domain details""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 domain_dict = domain.to_dict() # Add CF account info from Admin Panel if domain.cf_account_type == 'company' and domain.cf_account_id: admin_api = AdminAPIService() account_result = admin_api.get_cf_account(domain.cf_account_id) if account_result['status'] == 'success': domain_dict['cf_account_name'] = account_result['account']['name'] else: domain_dict['cf_account_name'] = f'CF Account #{domain.cf_account_id}' else: domain_dict['cf_account_name'] = 'Own Account' # Add DNS records domain_dict['dns_records'] = [record.to_dict() for record in domain.dns_records] return jsonify(domain_dict), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains', methods=['POST']) @token_required def create_domain(current_user): """Create a new domain""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 data = request.get_json() # Validate required fields if not data.get('domain_name'): return jsonify({'error': 'domain_name is required'}), 400 domain_name = data['domain_name'].lower().strip() # Check domain limit current_count = Domain.query.filter_by(customer_id=customer.id).count() if current_count >= customer.max_domains: return jsonify({ 'error': f'Domain limit reached. Maximum {customer.max_domains} domains allowed.' }), 403 # Check if domain already exists existing = Domain.query.filter_by(domain_name=domain_name).first() if existing: return jsonify({'error': 'Domain already exists'}), 409 # Validate CF account if using company account cf_account_id = data.get('cf_account_id') cf_account_type = data.get('cf_account_type', 'company') cf_account_data = None if cf_account_type == 'company': # Fetch available CF accounts from Admin Panel admin_api = AdminAPIService() # Otomatik hesap seçimi - cf_account_id verilmemişse en uygun hesabı seç if not cf_account_id: accounts_result = admin_api.get_available_cf_accounts() if accounts_result['status'] != 'success' or not accounts_result.get('accounts'): return jsonify({ 'error': 'No available Cloudflare account found. Please contact administrator.' }), 404 # En az dolu hesabı seç available_accounts = [ acc for acc in accounts_result['accounts'] if not acc.get('is_full', True) ] if not available_accounts: return jsonify({ 'error': 'All Cloudflare accounts are full. Please contact administrator.' }), 404 # En az dolu olanı seç cf_account_data = min(available_accounts, key=lambda x: x.get('current_domains', 0)) cf_account_id = cf_account_data['id'] else: # Manuel seçim yapılmışsa o hesabı Admin Panel'den çek account_result = admin_api.get_cf_account(cf_account_id) if account_result['status'] != 'success': return jsonify({ 'error': account_result.get('error', 'Cloudflare account not found') }), 404 cf_account_data = account_result['account'] # Validate account if cf_account_data.get('is_full', True): return jsonify({ 'error': f'Cloudflare account is full ({cf_account_data.get("max_domains", 0)} domains max)' }), 400 # Create zone in Cloudflare if using company account cf_zone_id = None cf_nameservers = [] cf_api_token = None if cf_account_type == 'company' and cf_account_id: # Get CF account with API token from Admin Panel admin_api = AdminAPIService() account_result = admin_api.get_cf_account(cf_account_id) if account_result['status'] != 'success': return jsonify({ 'error': f"Failed to get CF account: {account_result.get('error')}" }), 500 cf_api_token = account_result['account'].get('api_token') if not cf_api_token: return jsonify({ 'error': 'CF account API token not found' }), 500 # Create zone in Cloudflare from app.services.cloudflare_service import CloudflareService cf_service = CloudflareService(cf_api_token) zone_result = cf_service.create_zone(domain_name) if zone_result['status'] != 'success': return jsonify({ 'error': f"Failed to create Cloudflare zone: {zone_result.get('message', 'Unknown error')}" }), 500 cf_zone_id = zone_result['zone_id'] cf_nameservers = zone_result.get('nameservers', []) # Create domain in database domain = Domain( domain_name=domain_name, customer_id=customer.id, created_by=current_user.id, project_name=data.get('project_name'), use_cloudflare=data.get('use_cloudflare', True), cf_account_type=cf_account_type, cf_account_id=cf_account_id if cf_account_type == 'company' else None, cf_zone_id=cf_zone_id, cf_proxy_enabled=data.get('cf_proxy_enabled', True), status='pending' ) # If using own CF account, save encrypted token if cf_account_type == 'own' and data.get('cf_api_token'): domain.set_cf_api_token(data['cf_api_token']) db.session.add(domain) db.session.commit() # Increment domain count in Admin Panel if using company account if cf_account_type == 'company' and cf_account_id: admin_api = AdminAPIService() increment_result = admin_api.increment_domain_count(cf_account_id) if increment_result['status'] != 'success': # Log warning but don't fail the request # Domain is already created in customer DB print(f"Warning: Failed to increment domain count in Admin Panel: {increment_result.get('error')}") return jsonify({ 'message': 'Domain created successfully', 'domain': domain.to_dict(), 'cf_account_name': cf_account_data.get('name') if cf_account_data else None, 'nameservers': cf_nameservers }), 201 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains/', methods=['PUT']) @token_required def update_domain(current_user, domain_id): """Update domain""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 data = request.get_json() # Update allowed fields if 'project_name' in data: domain.project_name = data['project_name'] if 'cf_proxy_enabled' in data: domain.cf_proxy_enabled = data['cf_proxy_enabled'] if 'status' in data and data['status'] in ['pending', 'active', 'suspended', 'error']: domain.status = data['status'] domain.updated_at = datetime.utcnow() db.session.commit() return jsonify({ 'message': 'Domain updated successfully', 'domain': domain.to_dict() }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains/', methods=['DELETE']) @token_required def delete_domain(current_user, domain_id): """Delete domain and cleanup Cloudflare zone""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 # Store info before deletion cf_account_id = domain.cf_account_id cf_account_type = domain.cf_account_type cf_zone_id = domain.cf_zone_id cleanup_errors = [] # Delete Cloudflare zone if exists if cf_zone_id: try: # Get CF API token if cf_account_type == 'company' and cf_account_id: admin_api = AdminAPIService() account_result = admin_api.get_cf_account(cf_account_id) if account_result['status'] == 'success': api_token = account_result['account'].get('api_token') else: api_token = None cleanup_errors.append('Failed to get CF account API token') elif cf_account_type == 'own': api_token = domain.get_cf_api_token() else: api_token = None if api_token: from app.services.cloudflare_service import CloudflareService cf_service = CloudflareService(api_token) # Delete zone from Cloudflare delete_result = cf_service.delete_zone(cf_zone_id) if delete_result['status'] != 'success': cleanup_errors.append(f"Failed to delete CF zone: {delete_result.get('error')}") except Exception as e: cleanup_errors.append(f"Error deleting CF zone: {str(e)}") # Delete DNS records from database DNSRecord.query.filter_by(domain_id=domain.id).delete() # Delete domain from database db.session.delete(domain) db.session.commit() # Decrement domain count in Admin Panel if using company account if cf_account_type == 'company' and cf_account_id: admin_api = AdminAPIService() decrement_result = admin_api.decrement_domain_count(cf_account_id) if decrement_result['status'] != 'success': cleanup_errors.append(f"Failed to decrement domain count: {decrement_result.get('error')}") response = {'message': 'Domain deleted successfully'} if cleanup_errors: response['warnings'] = cleanup_errors return jsonify(response), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains//dns', methods=['GET']) @token_required def get_domain_dns(current_user, domain_id): """Get DNS records for a domain""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 records = [record.to_dict() for record in domain.dns_records] return jsonify({ 'domain': domain.domain_name, 'records': records, 'total': len(records) }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @customer_bp.route('/cloudflare-accounts', methods=['GET']) @token_required def get_cloudflare_accounts(current_user): """Get available Cloudflare accounts from Admin Panel API""" try: # Fetch CF accounts from Admin Panel API admin_api = AdminAPIService() result = admin_api.get_available_cf_accounts() if result['status'] == 'error': return jsonify({ 'error': result.get('error', 'Failed to fetch CF accounts from Admin Panel'), 'accounts': [], 'total': 0 }), 500 return jsonify({ 'accounts': result.get('accounts', []), 'total': result.get('total', 0) }), 200 except Exception as e: return jsonify({ 'error': str(e), 'accounts': [], 'total': 0 }), 500 @customer_bp.route('/stats', methods=['GET']) @token_required def get_customer_stats(current_user): """Get customer statistics""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Count domains by status total_domains = Domain.query.filter_by(customer_id=customer.id).count() active_domains = Domain.query.filter_by(customer_id=customer.id, status='active').count() pending_domains = Domain.query.filter_by(customer_id=customer.id, status='pending').count() return jsonify({ 'total_domains': total_domains, 'active_domains': active_domains, 'pending_domains': pending_domains, 'max_domains': customer.max_domains, 'available_slots': customer.max_domains - total_domains, 'subscription_plan': customer.subscription_plan }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains//dns-records', methods=['POST']) @token_required def create_dns_record(current_user, domain_id): """Create a new DNS record for a domain""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 if not domain.cf_zone_id: return jsonify({'error': 'Domain does not have a Cloudflare zone'}), 400 data = request.get_json() # Validate required fields required = ['record_type', 'name', 'content'] for field in required: if not data.get(field): return jsonify({'error': f'{field} is required'}), 400 # Get CF API token if domain.cf_account_type == 'company' and domain.cf_account_id: admin_api = AdminAPIService() account_result = admin_api.get_cf_account(domain.cf_account_id) if account_result['status'] != 'success': return jsonify({'error': 'Failed to get CF account'}), 500 api_token = account_result['account'].get('api_token') elif domain.cf_account_type == 'own': api_token = domain.get_cf_api_token() else: return jsonify({'error': 'No CF account configured'}), 400 if not api_token: return jsonify({'error': 'CF API token not found'}), 500 # Create DNS record in Cloudflare from app.services.cloudflare_service import CloudflareService cf_service = CloudflareService(api_token) result = cf_service.create_dns_record( zone_id=domain.cf_zone_id, record_type=data['record_type'], name=data['name'], content=data['content'], ttl=data.get('ttl', 1), proxied=data.get('proxied', False) ) if result['status'] != 'success': return jsonify(result), 500 # Save to database dns_record = DNSRecord( domain_id=domain.id, record_type=data['record_type'], name=data['name'], content=data['content'], ttl=data.get('ttl', 1), proxied=data.get('proxied', False), cf_record_id=result['record']['id'] ) db.session.add(dns_record) db.session.commit() return jsonify({ 'message': 'DNS record created successfully', 'record': dns_record.to_dict() }), 201 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains//dns-records/', methods=['PUT']) @token_required def update_dns_record(current_user, domain_id, record_id): """Update a DNS record""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 # Get DNS record dns_record = DNSRecord.query.filter_by( id=record_id, domain_id=domain.id ).first() if not dns_record: return jsonify({'error': 'DNS record not found'}), 404 data = request.get_json() # Get CF API token if domain.cf_account_type == 'company' and domain.cf_account_id: admin_api = AdminAPIService() account_result = admin_api.get_cf_account(domain.cf_account_id) if account_result['status'] != 'success': return jsonify({'error': 'Failed to get CF account'}), 500 api_token = account_result['account'].get('api_token') elif domain.cf_account_type == 'own': api_token = domain.get_cf_api_token() else: return jsonify({'error': 'No CF account configured'}), 400 if not api_token: return jsonify({'error': 'CF API token not found'}), 500 # Update DNS record in Cloudflare from app.services.cloudflare_service import CloudflareService cf_service = CloudflareService(api_token) result = cf_service.update_dns_record( zone_id=domain.cf_zone_id, record_id=dns_record.cf_record_id, record_type=data.get('record_type', dns_record.record_type), name=data.get('name', dns_record.name), content=data.get('content', dns_record.content), ttl=data.get('ttl', dns_record.ttl), proxied=data.get('proxied', dns_record.proxied) ) if result['status'] != 'success': return jsonify(result), 500 # Update database if 'record_type' in data: dns_record.record_type = data['record_type'] if 'name' in data: dns_record.name = data['name'] if 'content' in data: dns_record.content = data['content'] if 'ttl' in data: dns_record.ttl = data['ttl'] if 'proxied' in data: dns_record.proxied = data['proxied'] db.session.commit() return jsonify({ 'message': 'DNS record updated successfully', 'record': dns_record.to_dict() }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains//dns-records/', methods=['DELETE']) @token_required def delete_dns_record(current_user, domain_id, record_id): """Delete a DNS record""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 # Get DNS record dns_record = DNSRecord.query.filter_by( id=record_id, domain_id=domain.id ).first() if not dns_record: return jsonify({'error': 'DNS record not found'}), 404 # Get CF API token if domain.cf_account_type == 'company' and domain.cf_account_id: admin_api = AdminAPIService() account_result = admin_api.get_cf_account(domain.cf_account_id) if account_result['status'] != 'success': return jsonify({'error': 'Failed to get CF account'}), 500 api_token = account_result['account'].get('api_token') elif domain.cf_account_type == 'own': api_token = domain.get_cf_api_token() else: return jsonify({'error': 'No CF account configured'}), 400 if not api_token: return jsonify({'error': 'CF API token not found'}), 500 # Delete DNS record from Cloudflare from app.services.cloudflare_service import CloudflareService cf_service = CloudflareService(api_token) result = cf_service.delete_dns_record( zone_id=domain.cf_zone_id, record_id=dns_record.cf_record_id ) if result['status'] != 'success': return jsonify(result), 500 # Delete from database db.session.delete(dns_record) db.session.commit() return jsonify({ 'message': 'DNS record deleted successfully' }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @customer_bp.route('/domains//sync-dns', methods=['POST']) @token_required def sync_dns_records(current_user, domain_id): """Sync DNS records from Cloudflare to database""" try: customer = current_user.customer if not customer: return jsonify({'error': 'Customer profile not found'}), 404 # Get domain with customer isolation domain = Domain.query.filter_by( id=domain_id, customer_id=customer.id ).first() if not domain: return jsonify({'error': 'Domain not found'}), 404 if not domain.cf_zone_id: return jsonify({'error': 'Domain does not have a Cloudflare zone'}), 400 # Get CF API token if domain.cf_account_type == 'company' and domain.cf_account_id: admin_api = AdminAPIService() account_result = admin_api.get_cf_account(domain.cf_account_id) if account_result['status'] != 'success': return jsonify({'error': 'Failed to get CF account'}), 500 api_token = account_result['account'].get('api_token') elif domain.cf_account_type == 'own': api_token = domain.get_cf_api_token() else: return jsonify({'error': 'No CF account configured'}), 400 if not api_token: return jsonify({'error': 'CF API token not found'}), 500 # Get DNS records from Cloudflare from app.services.cloudflare_service import CloudflareService cf_service = CloudflareService(api_token) result = cf_service.get_dns_records(domain.cf_zone_id) if result['status'] != 'success': return jsonify(result), 500 # Sync to database synced = 0 for cf_record in result['records']: # Check if record exists existing = DNSRecord.query.filter_by( domain_id=domain.id, cf_record_id=cf_record['id'] ).first() if existing: # Update existing record existing.record_type = cf_record['type'] existing.name = cf_record['name'] existing.content = cf_record['content'] existing.ttl = cf_record['ttl'] existing.proxied = cf_record['proxied'] else: # Create new record new_record = DNSRecord( domain_id=domain.id, record_type=cf_record['type'], name=cf_record['name'], content=cf_record['content'], ttl=cf_record['ttl'], proxied=cf_record['proxied'], cf_record_id=cf_record['id'] ) db.session.add(new_record) synced += 1 db.session.commit() return jsonify({ 'message': 'DNS records synced successfully', 'synced': synced, 'total': result['total'] }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500