2026-01-11 14:38:39 +00:00
|
|
|
|
"""
|
2026-01-11 21:18:33 +00:00
|
|
|
|
Admin routes - Cloudflare hesap yönetimi ve Customer yönetimi
|
2026-01-11 14:38:39 +00:00
|
|
|
|
"""
|
|
|
|
|
|
from flask import Blueprint, request, jsonify
|
2026-01-11 21:18:33 +00:00
|
|
|
|
from app.models.domain import db, CloudflareAccount, Domain
|
|
|
|
|
|
from app.models.user import User, Customer
|
2026-01-11 14:38:39 +00:00
|
|
|
|
from app.services.cloudflare_service import CloudflareService
|
2026-01-11 21:18:33 +00:00
|
|
|
|
from sqlalchemy import func
|
2026-01-11 14:38:39 +00:00
|
|
|
|
|
|
|
|
|
|
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', ''),
|
2026-01-12 13:32:43 +00:00
|
|
|
|
is_active=True,
|
|
|
|
|
|
use_for_verification=data.get('use_for_verification', True)
|
2026-01-11 14:38:39 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 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']
|
|
|
|
|
|
|
2026-01-12 13:32:43 +00:00
|
|
|
|
if 'use_for_verification' in data:
|
|
|
|
|
|
account.use_for_verification = data['use_for_verification']
|
|
|
|
|
|
|
2026-01-11 14:38:39 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
2026-01-11 21:18:33 +00:00
|
|
|
|
|
2026-01-12 13:32:43 +00:00
|
|
|
|
@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
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-11 21:18:33 +00:00
|
|
|
|
# ============================================
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|