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

618 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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/<int:account_id>', 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/<int:account_id>', 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/<int:account_id>', 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/<int:account_id>/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/<int:account_id>/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/<int:customer_id>', 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/<int:customer_id>/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/<int:customer_id>/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