From c28642cc6ecb32c5e97e4a9da49d5ce70b4e33db Mon Sep 17 00:00:00 2001 From: oguz ozturk Date: Mon, 12 Jan 2026 16:57:46 +0300 Subject: [PATCH] feat: Integrate Admin Panel API for CF account management - Add AdminAPIService to fetch CF accounts from Admin Panel - Update customer routes to use Admin Panel API instead of local DB - Add ADMIN_API_URL and ADMIN_API_INTERNAL_KEY config - Remove local CF account count management (managed in Admin Panel) - Support automatic CF account selection from Admin Panel --- backend/app/config.py | 4 + backend/app/routes/customer.py | 99 ++++++++++-------- backend/app/services/admin_api_service.py | 120 ++++++++++++++++++++++ 3 files changed, 179 insertions(+), 44 deletions(-) create mode 100644 backend/app/services/admin_api_service.py diff --git a/backend/app/config.py b/backend/app/config.py index de7cc8a..ba0cd04 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -27,6 +27,10 @@ class Config: # Encryption (for sensitive data like API tokens) ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY") + # Admin Panel API (for fetching CF accounts) + ADMIN_API_URL = os.getenv("ADMIN_API_URL", "http://localhost:5001") + ADMIN_API_INTERNAL_KEY = os.getenv("ADMIN_API_INTERNAL_KEY", "internal-api-key-change-in-production") + # Cloudflare Platform Account (opsiyonel - deprecated, use database instead) PLATFORM_CF_API_TOKEN = os.getenv("PLATFORM_CF_API_TOKEN") PLATFORM_CF_ACCOUNT_ID = os.getenv("PLATFORM_CF_ACCOUNT_ID") diff --git a/backend/app/routes/customer.py b/backend/app/routes/customer.py index 40109df..5cccde7 100644 --- a/backend/app/routes/customer.py +++ b/backend/app/routes/customer.py @@ -6,6 +6,7 @@ from flask import Blueprint, request, jsonify from app.models.domain import db, Domain, DNSRecord, CloudflareAccount 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') @@ -115,42 +116,50 @@ def create_domain(current_user): # 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 = None + 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: - # En az dolu, aktif ve verification için kullanılabilir hesabı seç - cf_account = CloudflareAccount.query.filter( - CloudflareAccount.is_active == True, - CloudflareAccount.use_for_verification == True, - CloudflareAccount.current_domain_count < CloudflareAccount.max_domains - ).order_by( - CloudflareAccount.current_domain_count.asc() - ).first() + accounts_result = admin_api.get_available_cf_accounts() - if not cf_account: + if accounts_result['status'] != 'success' or not accounts_result.get('accounts'): return jsonify({ 'error': 'No available Cloudflare account found. Please contact administrator.' }), 404 - cf_account_id = cf_account.id + # 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ı kullan - cf_account = CloudflareAccount.query.get(cf_account_id) - if not cf_account: - return jsonify({'error': 'Cloudflare account not found'}), 404 + # Manuel seçim yapılmışsa o hesabı Admin Panel'den çek + account_result = admin_api.get_cf_account(cf_account_id) - if not cf_account.is_active: - return jsonify({'error': 'Cloudflare account is not active'}), 400 + if account_result['status'] != 'success': + return jsonify({ + 'error': account_result.get('error', 'Cloudflare account not found') + }), 404 - if not cf_account.use_for_verification: - return jsonify({'error': 'This Cloudflare account is not available for domain verification'}), 400 + cf_account_data = account_result['account'] - # Check CF account capacity - if cf_account.current_domain_count >= cf_account.max_domains: + # Validate account + if cf_account_data.get('is_full', True): return jsonify({ - 'error': f'Cloudflare account is full ({cf_account.max_domains} domains max)' + 'error': f'Cloudflare account is full ({cf_account_data.get("max_domains", 0)} domains max)' }), 400 # Create domain @@ -172,16 +181,15 @@ def create_domain(current_user): domain.set_cf_api_token(data['cf_api_token']) db.session.add(domain) - - # Update CF account domain count if using company account - if cf_account_type == 'company' and cf_account: - cf_account.current_domain_count += 1 - db.session.commit() + # Note: CF account domain count is managed in Admin Panel database + # We don't update it here since we're using separate databases + return jsonify({ '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 }), 201 except Exception as e: @@ -250,9 +258,8 @@ def delete_domain(current_user, domain_id): if not domain: return jsonify({'error': 'Domain not found'}), 404 - # Update CF account count if using company account - if domain.cf_account_type == 'company' and domain.cf_account: - domain.cf_account.current_domain_count = max(0, domain.cf_account.current_domain_count - 1) + # Note: CF account domain count is managed in Admin Panel database + # We don't update it here since we're using separate databases db.session.delete(domain) db.session.commit() @@ -297,26 +304,30 @@ def get_domain_dns(current_user, domain_id): @customer_bp.route('/cloudflare-accounts', methods=['GET']) @token_required def get_cloudflare_accounts(current_user): - """Get available Cloudflare accounts (company accounts only)""" + """Get available Cloudflare accounts from Admin Panel API""" try: - # Get active company CF accounts - accounts = CloudflareAccount.query.filter_by(is_active=True).all() + # Fetch CF accounts from Admin Panel API + admin_api = AdminAPIService() + result = admin_api.get_available_cf_accounts() - result = [] - for account in accounts: - account_dict = account.to_dict(include_token=False) - # Calculate available capacity - account_dict['available_capacity'] = account.max_domains - account.current_domain_count - account_dict['is_full'] = account.current_domain_count >= account.max_domains - result.append(account_dict) + 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, - 'total': len(result) + 'accounts': result.get('accounts', []), + 'total': result.get('total', 0) }), 200 except Exception as e: - return jsonify({'error': str(e)}), 500 + return jsonify({ + 'error': str(e), + 'accounts': [], + 'total': 0 + }), 500 @customer_bp.route('/stats', methods=['GET']) diff --git a/backend/app/services/admin_api_service.py b/backend/app/services/admin_api_service.py new file mode 100644 index 0000000..53e5b85 --- /dev/null +++ b/backend/app/services/admin_api_service.py @@ -0,0 +1,120 @@ +""" +Admin Panel API Service +Handles communication with Admin Panel API to fetch CF accounts +""" +import requests +from typing import Dict, List, Optional +from app.config import Config + + +class AdminAPIService: + """Service to communicate with Admin Panel API""" + + def __init__(self): + self.base_url = Config.ADMIN_API_URL + self.api_key = Config.ADMIN_API_INTERNAL_KEY + self.headers = { + 'X-Internal-API-Key': self.api_key, + 'Content-Type': 'application/json' + } + + def get_available_cf_accounts(self) -> Dict: + """ + Fetch available CF accounts from Admin Panel + + Returns: + { + "status": "success" | "error", + "accounts": [...], + "total": int, + "error": str (if error) + } + """ + try: + url = f"{self.base_url}/api/cf-accounts/internal/available" + response = requests.get(url, headers=self.headers, timeout=10) + + if response.status_code == 200: + return response.json() + else: + return { + 'status': 'error', + 'error': f'Admin API returned {response.status_code}: {response.text}', + 'accounts': [], + 'total': 0 + } + + except requests.exceptions.Timeout: + return { + 'status': 'error', + 'error': 'Admin API request timeout', + 'accounts': [], + 'total': 0 + } + except requests.exceptions.ConnectionError: + return { + 'status': 'error', + 'error': 'Cannot connect to Admin API', + 'accounts': [], + 'total': 0 + } + except Exception as e: + return { + 'status': 'error', + 'error': f'Admin API error: {str(e)}', + 'accounts': [], + 'total': 0 + } + + def get_cf_account(self, account_id: int) -> Dict: + """ + Fetch specific CF account with API token from Admin Panel + + Args: + account_id: CF account ID + + Returns: + { + "status": "success" | "error", + "account": {...}, + "error": str (if error) + } + """ + try: + url = f"{self.base_url}/api/cf-accounts/internal/{account_id}" + response = requests.get(url, headers=self.headers, timeout=10) + + if response.status_code == 200: + return response.json() + elif response.status_code == 404: + return { + 'status': 'error', + 'error': 'CF account not found' + } + elif response.status_code == 403: + return { + 'status': 'error', + 'error': 'CF account not available for verification' + } + else: + return { + 'status': 'error', + 'error': f'Admin API returned {response.status_code}: {response.text}' + } + + except requests.exceptions.Timeout: + return { + 'status': 'error', + 'error': 'Admin API request timeout' + } + except requests.exceptions.ConnectionError: + return { + 'status': 'error', + 'error': 'Cannot connect to Admin API' + } + except Exception as e: + return { + 'status': 'error', + 'error': f'Admin API error: {str(e)}' + } +