feat: Auto-create Cloudflare zone on domain creation

- Add create_zone() method to CloudflareService
- Fetch CF account API token from Admin Panel
- Create zone in Cloudflare when using company account
- Return nameservers in domain creation response
- Handle zone creation errors gracefully
This commit is contained in:
oguz ozturk 2026-01-12 17:29:05 +03:00
parent 0b2920e43f
commit 61dc963b28
2 changed files with 153 additions and 22 deletions

View File

@ -3,7 +3,7 @@ Customer Routes - Domain Management
Customer-specific endpoints with isolation Customer-specific endpoints with isolation
""" """
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from app.models.domain import db, Domain, DNSRecord, CloudflareAccount from app.models.domain import db, Domain, DNSRecord
from app.models.user import Customer from app.models.user import Customer
from app.services.auth_service import token_required from app.services.auth_service import token_required
from app.services.admin_api_service import AdminAPIService from app.services.admin_api_service import AdminAPIService
@ -25,14 +25,34 @@ def get_domains(current_user):
# Get domains with customer isolation # Get domains with customer isolation
domains = Domain.query.filter_by(customer_id=customer.id).all() 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 # Add CF account info
result = [] result = []
for domain in domains: for domain in domains:
domain_dict = domain.to_dict() domain_dict = domain.to_dict()
# Add CF account name if using company account # Add CF account name if using company account
if domain.cf_account_type == 'company' and domain.cf_account: if domain.cf_account_type == 'company' and domain.cf_account_id:
domain_dict['cf_account_name'] = domain.cf_account.name domain_dict['cf_account_name'] = cf_account_names.get(
domain.cf_account_id,
f'CF Account #{domain.cf_account_id}'
)
else: else:
domain_dict['cf_account_name'] = 'Own Account' domain_dict['cf_account_name'] = 'Own Account'
@ -71,9 +91,17 @@ def get_domain(current_user, domain_id):
domain_dict = domain.to_dict() domain_dict = domain.to_dict()
# Add CF account info # Add CF account info from Admin Panel
if domain.cf_account_type == 'company' and domain.cf_account: if domain.cf_account_type == 'company' and domain.cf_account_id:
domain_dict['cf_account_name'] = domain.cf_account.name 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 # Add DNS records
domain_dict['dns_records'] = [record.to_dict() for record in domain.dns_records] domain_dict['dns_records'] = [record.to_dict() for record in domain.dns_records]
@ -162,7 +190,41 @@ def create_domain(current_user):
'error': f'Cloudflare account is full ({cf_account_data.get("max_domains", 0)} domains max)' 'error': f'Cloudflare account is full ({cf_account_data.get("max_domains", 0)} domains max)'
}), 400 }), 400
# Create domain # 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 = Domain(
domain_name=domain_name, domain_name=domain_name,
customer_id=customer.id, customer_id=customer.id,
@ -171,7 +233,7 @@ def create_domain(current_user):
use_cloudflare=data.get('use_cloudflare', True), use_cloudflare=data.get('use_cloudflare', True),
cf_account_type=cf_account_type, cf_account_type=cf_account_type,
cf_account_id=cf_account_id if cf_account_type == 'company' else None, cf_account_id=cf_account_id if cf_account_type == 'company' else None,
cf_zone_id=data.get('cf_zone_id'), cf_zone_id=cf_zone_id,
cf_proxy_enabled=data.get('cf_proxy_enabled', True), cf_proxy_enabled=data.get('cf_proxy_enabled', True),
status='pending' status='pending'
) )
@ -183,13 +245,21 @@ def create_domain(current_user):
db.session.add(domain) db.session.add(domain)
db.session.commit() db.session.commit()
# Note: CF account domain count is managed in Admin Panel database # Increment domain count in Admin Panel if using company account
# We don't update it here since we're using separate databases 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({ return jsonify({
'message': 'Domain created successfully', 'message': 'Domain created successfully',
'domain': domain.to_dict(), 'domain': domain.to_dict(),
'cf_account_name': cf_account_data.get('name') if cf_account_data else None 'cf_account_name': cf_account_data.get('name') if cf_account_data else None,
'nameservers': cf_nameservers
}), 201 }), 201
except Exception as e: except Exception as e:
@ -258,12 +328,24 @@ def delete_domain(current_user, domain_id):
if not domain: if not domain:
return jsonify({'error': 'Domain not found'}), 404 return jsonify({'error': 'Domain not found'}), 404
# Note: CF account domain count is managed in Admin Panel database # Store CF account info before deletion
# We don't update it here since we're using separate databases cf_account_id = domain.cf_account_id
cf_account_type = domain.cf_account_type
# Delete domain
db.session.delete(domain) db.session.delete(domain)
db.session.commit() 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':
# Log warning but don't fail the request
# Domain is already deleted from customer DB
print(f"Warning: Failed to decrement domain count in Admin Panel: {decrement_result.get('error')}")
return jsonify({'message': 'Domain deleted successfully'}), 200 return jsonify({'message': 'Domain deleted successfully'}), 200
except Exception as e: except Exception as e:

View File

@ -10,6 +10,55 @@ class CloudflareService:
self.cf = CloudFlare.CloudFlare(token=api_token) self.cf = CloudFlare.CloudFlare(token=api_token)
self.api_token = api_token self.api_token = api_token
def create_zone(self, domain: str) -> Dict:
"""
Create a new zone in Cloudflare
Returns zone_id and nameservers
"""
try:
# Create zone
zone_data = {
'name': domain,
'jump_start': True # Auto-scan DNS records
}
zone = self.cf.zones.post(data=zone_data)
return {
'status': 'success',
'zone_id': zone['id'],
'zone_name': zone['name'],
'nameservers': zone.get('name_servers', []),
'zone_status': zone.get('status', 'pending'),
'created_on': zone.get('created_on')
}
except CloudFlare.exceptions.CloudFlareAPIError as e:
error_code = e.code if hasattr(e, 'code') else 0
error_message = str(e)
# Handle specific errors
if error_code == 1061: # Zone already exists
return {
'status': 'error',
'error': 'zone_exists',
'message': f'Zone {domain} already exists in this Cloudflare account'
}
return {
'status': 'error',
'error': 'cloudflare_api_error',
'message': f'Cloudflare API error: {error_message}',
'code': error_code
}
except Exception as e:
return {
'status': 'error',
'error': 'unexpected_error',
'message': f'Unexpected error: {str(e)}'
}
def validate_token_and_get_zone(self, domain: str) -> Dict: def validate_token_and_get_zone(self, domain: str) -> Dict:
""" """
API token doğrula ve zone bilgilerini al API token doğrula ve zone bilgilerini al