""" Admin routes - Cloudflare hesap yönetimi ve Customer yönetimi """ from flask import Blueprint, request, jsonify from app.models.domain import db, CloudflareAccount, Domain from app.models.user import User, Customer from app.services.cloudflare_service import CloudflareService from sqlalchemy import func admin_bp = Blueprint('admin', __name__, url_prefix='/api/admin') @admin_bp.route('/cf-accounts', methods=['GET']) def list_cf_accounts(): """Tüm Cloudflare hesaplarını listele""" try: accounts = CloudflareAccount.query.filter_by(is_active=True).all() return jsonify({ "status": "success", "accounts": [acc.to_dict(include_token=False) for acc in accounts], "count": len(accounts) }) except Exception as e: return jsonify({ "status": "error", "message": f"Hesaplar listelenirken hata: {str(e)}" }), 500 @admin_bp.route('/cf-accounts', methods=['POST']) def create_cf_account(): """Yeni Cloudflare hesabı ekle""" try: data = request.json # Validasyon required_fields = ['name', 'email', 'api_token'] for field in required_fields: if not data.get(field): return jsonify({ "status": "error", "message": f"'{field}' alanı gerekli" }), 400 # Token'ı doğrula cf_service = CloudflareService(data['api_token']) # Basit bir API çağrısı yaparak token'ı test et try: zones = cf_service.cf.zones.get(params={'per_page': 1}) # Token geçerli except Exception as e: return jsonify({ "status": "error", "message": f"Cloudflare API token geçersiz: {str(e)}" }), 400 # Aynı isimde hesap var mı kontrol et existing = CloudflareAccount.query.filter_by(name=data['name']).first() if existing: return jsonify({ "status": "error", "message": f"'{data['name']}' isimli hesap zaten mevcut" }), 400 # Yeni hesap oluştur account = CloudflareAccount( name=data['name'], email=data['email'], max_domains=data.get('max_domains', 100), notes=data.get('notes', ''), is_active=True, use_for_verification=data.get('use_for_verification', True) ) # Token'ı şifrele ve kaydet account.set_api_token(data['api_token']) db.session.add(account) db.session.commit() return jsonify({ "status": "success", "message": "Cloudflare hesabı başarıyla eklendi", "account": account.to_dict(include_token=False) }), 201 except Exception as e: db.session.rollback() return jsonify({ "status": "error", "message": f"Hesap eklenirken hata: {str(e)}" }), 500 @admin_bp.route('/cf-accounts/', methods=['GET']) def get_cf_account(account_id): """Belirli bir Cloudflare hesabını getir""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ "status": "error", "message": "Hesap bulunamadı" }), 404 # include_token parametresi ile token'ı da döndürebiliriz (sadece admin için) include_token = request.args.get('include_token', 'false').lower() == 'true' return jsonify({ "status": "success", "account": account.to_dict(include_token=include_token) }) except Exception as e: return jsonify({ "status": "error", "message": f"Hesap getirilirken hata: {str(e)}" }), 500 @admin_bp.route('/cf-accounts/', methods=['PUT']) def update_cf_account(account_id): """Cloudflare hesabını güncelle""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ "status": "error", "message": "Hesap bulunamadı" }), 404 data = request.json # Güncellenebilir alanlar if 'name' in data: # Aynı isimde başka hesap var mı? existing = CloudflareAccount.query.filter( CloudflareAccount.name == data['name'], CloudflareAccount.id != account_id ).first() if existing: return jsonify({ "status": "error", "message": f"'{data['name']}' isimli hesap zaten mevcut" }), 400 account.name = data['name'] if 'email' in data: account.email = data['email'] if 'api_token' in data: # Yeni token'ı doğrula cf_service = CloudflareService(data['api_token']) try: zones = cf_service.cf.zones.get(params={'per_page': 1}) except Exception as e: return jsonify({ "status": "error", "message": f"Cloudflare API token geçersiz: {str(e)}" }), 400 account.set_api_token(data['api_token']) if 'max_domains' in data: account.max_domains = data['max_domains'] if 'notes' in data: account.notes = data['notes'] if 'is_active' in data: account.is_active = data['is_active'] if 'use_for_verification' in data: account.use_for_verification = data['use_for_verification'] db.session.commit() return jsonify({ "status": "success", "message": "Hesap başarıyla güncellendi", "account": account.to_dict(include_token=False) }) except Exception as e: db.session.rollback() return jsonify({ "status": "error", "message": f"Hesap güncellenirken hata: {str(e)}" }), 500 @admin_bp.route('/cf-accounts/', methods=['DELETE']) def delete_cf_account(account_id): """Cloudflare hesabını sil (soft delete)""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ "status": "error", "message": "Hesap bulunamadı" }), 404 # Bu hesabı kullanan domain var mı kontrol et if account.current_domain_count > 0: return jsonify({ "status": "error", "message": f"Bu hesap {account.current_domain_count} domain tarafından kullanılıyor. Önce domain'leri başka hesaba taşıyın." }), 400 # Soft delete (is_active = False) account.is_active = False db.session.commit() return jsonify({ "status": "success", "message": "Hesap başarıyla devre dışı bırakıldı" }) except Exception as e: db.session.rollback() return jsonify({ "status": "error", "message": f"Hesap silinirken hata: {str(e)}" }), 500 @admin_bp.route('/cf-accounts//test', methods=['POST']) def test_cf_account(account_id): """Cloudflare hesabının API bağlantısını test et""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ "status": "error", "message": "Hesap bulunamadı" }), 404 # API token'ı al api_token = account.get_api_token() # Cloudflare API'ye bağlan cf_service = CloudflareService(api_token) try: # Zone listesini al (test için) zones = cf_service.cf.zones.get(params={'per_page': 5}) return jsonify({ "status": "success", "message": "✅ Cloudflare API bağlantısı başarılı", "zone_count": len(zones), "sample_zones": [ {"name": z["name"], "status": z["status"]} for z in zones[:3] ] }) except Exception as e: return jsonify({ "status": "error", "message": f"❌ Cloudflare API bağlantı hatası: {str(e)}" }), 400 except Exception as e: return jsonify({ "status": "error", "message": f"Test sırasında hata: {str(e)}" }), 500 @admin_bp.route('/cf-accounts//permissions', methods=['GET']) def get_cf_account_permissions(account_id): """Cloudflare hesabının yetkilerini kontrol et""" try: account = CloudflareAccount.query.get(account_id) if not account: return jsonify({ "status": "error", "message": "Hesap bulunamadı" }), 404 # API token'ı al api_token = account.get_api_token() # Cloudflare API'ye bağlan cf_service = CloudflareService(api_token) try: # Token verification endpoint'i kullan verify_response = cf_service.cf.user.tokens.verify.get() # Zone listesini al zones = cf_service.cf.zones.get(params={'per_page': 1}) # Permissions bilgilerini topla permissions = { "token_status": verify_response.get("status", "unknown"), "token_id": verify_response.get("id", ""), "permissions": [], "zone_access": False, "dns_write": False, "ssl_write": False, "zone_count": len(zones) if zones else 0 } # Token policies'den permissions çıkar if "policies" in verify_response: for policy in verify_response["policies"]: if "permission_groups" in policy: for perm in policy["permission_groups"]: perm_name = perm.get("name", "") permissions["permissions"].append(perm_name) # Önemli yetkileri flag'le if "Zone" in perm_name: permissions["zone_access"] = True if "DNS" in perm_name and "Write" in perm_name: permissions["dns_write"] = True if "SSL" in perm_name and "Write" in perm_name: permissions["ssl_write"] = True # Özet bilgi permissions["summary"] = { "can_manage_zones": permissions["zone_access"], "can_manage_dns": permissions["dns_write"], "can_manage_ssl": permissions["ssl_write"], "total_zones": permissions["zone_count"], "available_domains": account.max_domains - account.current_domain_count } return jsonify({ "status": "success", "account": { "id": account.id, "name": account.name, "email": account.email }, "permissions": permissions }) except Exception as e: return jsonify({ "status": "error", "message": f"❌ Yetki kontrolü hatası: {str(e)}" }), 400 except Exception as e: return jsonify({ "status": "error", "message": f"Yetki kontrolü sırasında hata: {str(e)}" }), 500 @admin_bp.route('/cf-accounts/auto-select', methods=['POST']) def auto_select_cf_account(): """ Müşteri için otomatik CF hesap seçimi En az dolu, aktif ve verification için kullanılabilir hesabı seçer """ try: # Aktif, verification için kullanılabilir ve dolu olmayan hesapları getir available_accounts = CloudflareAccount.query.filter( CloudflareAccount.is_active == True, CloudflareAccount.use_for_verification == True, CloudflareAccount.current_domain_count < CloudflareAccount.max_domains ).order_by( # En az dolu olanı seç CloudflareAccount.current_domain_count.asc() ).all() if not available_accounts: return jsonify({ "status": "error", "message": "Kullanılabilir Cloudflare hesabı bulunamadı. Lütfen yönetici ile iletişime geçin." }), 404 # En uygun hesabı seç (ilk sıradaki) selected_account = available_accounts[0] return jsonify({ "status": "success", "account": { "id": selected_account.id, "name": selected_account.name, "available_capacity": selected_account.max_domains - selected_account.current_domain_count, "current_domains": selected_account.current_domain_count, "max_domains": selected_account.max_domains } }) except Exception as e: return jsonify({ "status": "error", "message": f"Hesap seçimi sırasında hata: {str(e)}" }), 500 # ============================================ # CUSTOMER MANAGEMENT ENDPOINTS # ============================================ @admin_bp.route('/customers', methods=['GET']) def list_customers(): """Tüm müşterileri listele""" try: # Query parameters page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 20, type=int) search = request.args.get('search', '') # Base query query = db.session.query(User, Customer).join(Customer, User.id == Customer.user_id) # Search filter if search: query = query.filter( db.or_( User.email.ilike(f'%{search}%'), User.full_name.ilike(f'%{search}%'), Customer.company_name.ilike(f'%{search}%') ) ) # Pagination total = query.count() results = query.offset((page - 1) * per_page).limit(per_page).all() # Format response customers = [] for user, customer in results: # Get domain count domain_count = Domain.query.filter_by(customer_id=customer.id).count() customer_data = { **user.to_dict(), **customer.to_dict(), 'domain_count': domain_count } customers.append(customer_data) return jsonify({ "status": "success", "customers": customers, "pagination": { "page": page, "per_page": per_page, "total": total, "pages": (total + per_page - 1) // per_page } }) except Exception as e: return jsonify({ "status": "error", "message": f"Müşteriler listelenirken hata: {str(e)}" }), 500 @admin_bp.route('/customers/', methods=['GET']) def get_customer(customer_id): """Belirli bir müşteriyi getir""" try: customer = Customer.query.get(customer_id) if not customer: return jsonify({ "status": "error", "message": "Müşteri bulunamadı" }), 404 user = User.query.get(customer.user_id) # Get domains domains = Domain.query.filter_by(customer_id=customer.id).all() return jsonify({ "status": "success", "customer": { **user.to_dict(), **customer.to_dict(), 'domains': [d.to_dict() for d in domains] } }) except Exception as e: return jsonify({ "status": "error", "message": f"Müşteri getirilirken hata: {str(e)}" }), 500 @admin_bp.route('/customers//plan', methods=['PUT']) def update_customer_plan(customer_id): """Müşterinin planını güncelle""" try: data = request.json customer = Customer.query.get(customer_id) if not customer: return jsonify({ "status": "error", "message": "Müşteri bulunamadı" }), 404 # Update plan if 'subscription_plan' in data: customer.subscription_plan = data['subscription_plan'] if 'max_domains' in data: customer.max_domains = data['max_domains'] if 'max_containers' in data: customer.max_containers = data['max_containers'] if 'subscription_status' in data: customer.subscription_status = data['subscription_status'] db.session.commit() return jsonify({ "status": "success", "message": "Plan başarıyla güncellendi", "customer": customer.to_dict() }) except Exception as e: db.session.rollback() return jsonify({ "status": "error", "message": f"Plan güncellenirken hata: {str(e)}" }), 500 @admin_bp.route('/customers//status', methods=['PUT']) def update_customer_status(customer_id): """Müşteri durumunu güncelle (aktif/pasif)""" try: data = request.json customer = Customer.query.get(customer_id) if not customer: return jsonify({ "status": "error", "message": "Müşteri bulunamadı" }), 404 user = User.query.get(customer.user_id) if 'is_active' in data: user.is_active = data['is_active'] if 'subscription_status' in data: customer.subscription_status = data['subscription_status'] db.session.commit() return jsonify({ "status": "success", "message": "Durum başarıyla güncellendi" }) except Exception as e: db.session.rollback() return jsonify({ "status": "error", "message": f"Durum güncellenirken hata: {str(e)}" }), 500 @admin_bp.route('/stats', methods=['GET']) def get_admin_stats(): """Admin dashboard istatistikleri""" try: total_customers = Customer.query.count() active_customers = db.session.query(Customer).join(User).filter(User.is_active == True).count() total_domains = Domain.query.count() active_domains = Domain.query.filter_by(status='active').count() # CF accounts stats total_cf_accounts = CloudflareAccount.query.filter_by(is_active=True).count() # Subscription breakdown subscription_stats = db.session.query( Customer.subscription_plan, func.count(Customer.id) ).group_by(Customer.subscription_plan).all() return jsonify({ "status": "success", "stats": { "customers": { "total": total_customers, "active": active_customers }, "domains": { "total": total_domains, "active": active_domains }, "cf_accounts": total_cf_accounts, "subscriptions": {plan: count for plan, count in subscription_stats} } }) except Exception as e: return jsonify({ "status": "error", "message": f"İstatistikler alınırken hata: {str(e)}" }), 500