hosting-platform/backend/app/routes/customer.py

816 lines
28 KiB
Python
Raw Permalink Normal View History

2026-01-11 14:38:39 +00:00
"""
Customer Routes - Domain Management
Customer-specific endpoints with isolation
"""
from flask import Blueprint, request, jsonify
from app.models.domain import db, Domain, DNSRecord
2026-01-11 14:38:39 +00:00
from app.models.user import Customer
from app.services.auth_service import token_required
from app.services.admin_api_service import AdminAPIService
2026-01-11 14:38:39 +00:00
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']
2026-01-11 14:38:39 +00:00
# Add CF account info
result = []
for domain in domains:
domain_dict = domain.to_dict()
2026-01-11 14:38:39 +00:00
# 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}'
)
2026-01-11 14:38:39 +00:00
else:
domain_dict['cf_account_name'] = 'Own Account'
2026-01-11 14:38:39 +00:00
# Add DNS record count
domain_dict['dns_record_count'] = len(domain.dns_records)
2026-01-11 14:38:39 +00:00
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/<int:domain_id>', 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'
2026-01-11 14:38:39 +00:00
# Add DNS records
domain_dict['dns_records'] = [record.to_dict() for record in domain.dns_records]
2026-01-11 14:38:39 +00:00
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
2026-01-11 14:38:39 +00:00
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ç
2026-01-11 14:38:39 +00:00
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):
2026-01-11 14:38:39 +00:00
return jsonify({
'error': f'Cloudflare account is full ({cf_account_data.get("max_domains", 0)} domains max)'
2026-01-11 14:38:39 +00:00
}), 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
2026-01-11 14:38:39 +00:00
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,
2026-01-11 14:38:39 +00:00
cf_proxy_enabled=data.get('cf_proxy_enabled', True),
status='pending'
)
2026-01-11 14:38:39 +00:00
# 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')}")
2026-01-11 14:38:39 +00:00
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
2026-01-11 14:38:39 +00:00
}), 201
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@customer_bp.route('/domains/<int:domain_id>', 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/<int:domain_id>', methods=['DELETE'])
@token_required
def delete_domain(current_user, domain_id):
"""Delete domain and cleanup Cloudflare zone"""
2026-01-11 14:38:39 +00:00
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
2026-01-11 14:38:39 +00:00
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
2026-01-11 14:38:39 +00:00
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
2026-01-11 14:38:39 +00:00
except Exception as e:
db.session.rollback()
return jsonify({'error': str(e)}), 500
@customer_bp.route('/domains/<int:domain_id>/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"""
2026-01-11 14:38:39 +00:00
try:
# Fetch CF accounts from Admin Panel API
admin_api = AdminAPIService()
result = admin_api.get_available_cf_accounts()
2026-01-11 14:38:39 +00:00
if result['status'] == 'error':
return jsonify({
'error': result.get('error', 'Failed to fetch CF accounts from Admin Panel'),
'accounts': [],
'total': 0
}), 500
2026-01-11 14:38:39 +00:00
return jsonify({
'accounts': result.get('accounts', []),
'total': result.get('total', 0)
2026-01-11 14:38:39 +00:00
}), 200
except Exception as e:
return jsonify({
'error': str(e),
'accounts': [],
'total': 0
}), 500
2026-01-11 14:38:39 +00:00
@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/<int:domain_id>/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/<int:domain_id>/dns-records/<int:record_id>', 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/<int:domain_id>/dns-records/<int:record_id>', 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/<int:domain_id>/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