from fastapi import APIRouter, Request, Depends, Form, HTTPException from fastapi.responses import HTMLResponse, JSONResponse from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from sqlalchemy import desc, func from typing import Optional import json from datetime import datetime from pathlib import Path from database import get_db, User, InventoryItem, CaseOpening, Contract from auth import get_current_admin from rpu import get_user_rpu, calculate_rpu_level, get_rpu_description admin_router = APIRouter(prefix="/admin", tags=["admin"]) templates = Jinja2Templates(directory="templates") def money_filter(value): try: return "{:,.0f}".format(float(value)).replace(",", " ") except (ValueError, TypeError): return "0" templates.env.filters["money"] = money_filter # Загрузка конфигов CASES_FILE = Path("cases.json") MAINPAGE_FILE = Path("mainpage.json") def load_cases_config(): if CASES_FILE.exists(): with open(CASES_FILE, 'r', encoding='utf-8') as f: return json.load(f) return [] def save_cases_config(cases): with open(CASES_FILE, 'w', encoding='utf-8') as f: json.dump(cases, f, ensure_ascii=False, indent=2) def load_mainpage_config(): if MAINPAGE_FILE.exists(): with open(MAINPAGE_FILE, 'r', encoding='utf-8') as f: return json.load(f) return [] def save_mainpage_config(config): with open(MAINPAGE_FILE, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) def get_rpu_level_for_user(user_id: int) -> float: """Хелпер для шаблона""" db = next(get_db()) rpu = get_user_rpu(db, user_id) return calculate_rpu_level(rpu) # ========== СТРАНИЦЫ ========== @admin_router.get("/", response_class=HTMLResponse) async def admin_dashboard( request: Request, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): """Главная страница админки""" # Основная статистика total_users = db.query(User).count() total_items = db.query(InventoryItem).count() total_openings = db.query(CaseOpening).count() total_contracts = db.query(Contract).count() total_balance = db.query(func.sum(User.balance)).scalar() or 0 # Статистика РПУ from rpu import UserRPU all_rpu = db.query(UserRPU).all() rpu_count = len(all_rpu) avg_rpu = 50.0 lucky_count = 0 normal_count = 0 unlucky_count = 0 auto_adjust_count = 0 total_spent = 0.0 if rpu_count > 0: for r in all_rpu: level = calculate_rpu_level(r) if level > 60: lucky_count += 1 elif level < 40: unlucky_count += 1 else: normal_count += 1 if r.auto_adjust: auto_adjust_count += 1 total_spent += r.total_spent avg_rpu = sum(calculate_rpu_level(r) for r in all_rpu) / rpu_count recent_users = db.query(User).order_by(desc(User.created_at)).limit(10).all() return templates.TemplateResponse(request, "admin/dashboard.html", { "user": admin, "total_users": total_users, "total_items": total_items, "total_openings": total_openings, "total_contracts": total_contracts, "total_balance": total_balance, "recent_users": recent_users, "rpu_count": rpu_count, "avg_rpu": round(avg_rpu, 1), "lucky_count": lucky_count, "normal_count": normal_count, "unlucky_count": unlucky_count, "auto_adjust_count": auto_adjust_count, "total_spent": total_spent, "get_rpu_level": get_rpu_level_for_user }) @admin_router.get("/users", response_class=HTMLResponse) async def admin_users( request: Request, search: Optional[str] = None, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): """Список пользователей""" query = db.query(User) if search: query = query.filter(User.username.contains(search)) users = query.order_by(desc(User.created_at)).limit(100).all() # Добавляем уровень РПУ для каждого пользователя users_with_rpu = [] for u in users: rpu = get_user_rpu(db, u.id) users_with_rpu.append({ "id": u.id, "username": u.username, "balance": u.balance, "is_admin": u.is_admin, "created_at": u.created_at, "case_openings": db.query(CaseOpening).filter(CaseOpening.user_id == u.id).count(), "contracts": db.query(Contract).filter(Contract.user_id == u.id).count(), "inventory_count": db.query(InventoryItem).filter(InventoryItem.user_id == u.id).count(), "rpu_level": round(calculate_rpu_level(rpu)) }) return templates.TemplateResponse(request, "admin/users.html", { "user": admin, "users": users_with_rpu, "search": search }) @admin_router.get("/user/{user_id}", response_class=HTMLResponse) async def admin_user_detail( user_id: int, request: Request, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): target_user = db.query(User).filter(User.id == user_id).first() if not target_user: raise HTTPException(404, "Пользователь не найден") total_items = db.query(InventoryItem).filter(InventoryItem.user_id == user_id).count() total_openings = db.query(CaseOpening).filter(CaseOpening.user_id == user_id).count() total_contracts = db.query(Contract).filter(Contract.user_id == user_id).count() inventory = db.query(InventoryItem).filter( InventoryItem.user_id == user_id ).order_by(desc(InventoryItem.obtained_at)).limit(50).all() return templates.TemplateResponse(request, "admin/user_detail.html", { "user": admin, "target_user": target_user, "total_items": total_items, "total_openings": total_openings, "total_contracts": total_contracts, "inventory": inventory }) @admin_router.get("/cases", response_class=HTMLResponse) async def admin_cases( request: Request, admin: User = Depends(get_current_admin) ): from frontend import ALL_ITEMS cases = load_cases_config() mainpage = load_mainpage_config() for case in cases: if not isinstance(case.get('items'), list): case['items'] = [] all_items = [] for i, item in enumerate(ALL_ITEMS[:500]): all_items.append({ "id": item.get("_id", i), "name": item.get("market_hash_name", "Unknown"), "rarity": item.get("rarity", "Unknown"), "image_url": item.get("image_url", "") }) return templates.TemplateResponse(request, "admin/cases.html", { "user": admin, "cases": cases, "mainpage": mainpage, "all_items": all_items, "cases_config": {c['name']: c for c in cases} }) # ========== API ПОЛЬЗОВАТЕЛИ ========== @admin_router.post("/api/user/{user_id}/balance") async def admin_set_balance( user_id: int, amount: float = Form(...), operation: str = Form(...), db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) old_balance = target_user.balance if operation == "add": target_user.balance += amount message = f"Добавлено {amount:.2f} ₽" elif operation == "remove": target_user.balance = max(0, target_user.balance - amount) message = f"Снято {amount:.2f} ₽" else: target_user.balance = amount message = f"Установлен баланс {amount:.2f} ₽" db.commit() return JSONResponse({ "success": True, "old_balance": old_balance, "new_balance": target_user.balance, "message": message }) @admin_router.post("/api/user/{user_id}/give-item") async def admin_give_item( user_id: int, item_id: int = Form(...), db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): from frontend import ALL_ITEMS, generate_item_float target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) item_data = None for idx, item in enumerate(ALL_ITEMS): if item.get("_id", idx) == item_id: item_data = item break if not item_data: return JSONResponse(status_code=404, content={"error": "Предмет не найден"}) item_float = generate_item_float(item_data.get("rarity", "Unknown")) inventory_item = InventoryItem( user_id=user_id, item_id=item_id, market_hash_name=item_data.get("market_hash_name", "Unknown"), rarity=item_data.get("rarity", "Unknown"), wear=item_data.get("wear", "Unknown"), float_value=item_float, type=item_data.get("type", "Normal"), obtained_from="admin" ) db.add(inventory_item) db.commit() from achievements import check_collection_achievements check_collection_achievements(db, user_id) return JSONResponse({ "success": True, "message": f"Предмет '{item_data.get('market_hash_name')}' выдан пользователю {target_user.username}" }) @admin_router.post("/api/user/{user_id}/toggle-ban") async def admin_toggle_ban( user_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) target_user.is_banned = not target_user.is_banned db.commit() return JSONResponse({ "success": True, "is_banned": target_user.is_banned, "message": f"Пользователь {'забанен' if target_user.is_banned else 'разбанен'}" }) @admin_router.post("/api/user/{user_id}/toggle-admin") async def admin_toggle_admin( user_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) if target_user.id == admin.id: return JSONResponse(status_code=400, content={"error": "Нельзя изменить свой статус"}) target_user.is_admin = not target_user.is_admin db.commit() return JSONResponse({ "success": True, "is_admin": target_user.is_admin, "message": f"Статус администратора {'включен' if target_user.is_admin else 'выключен'}" }) @admin_router.post("/api/user/{user_id}/delete-item/{inventory_id}") async def admin_delete_item( user_id: int, inventory_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): item = db.query(InventoryItem).filter( InventoryItem.id == inventory_id, InventoryItem.user_id == user_id ).first() if not item: return JSONResponse(status_code=404, content={"error": "Предмет не найден"}) db.delete(item) db.commit() return JSONResponse({"success": True, "message": "Предмет удален"}) # ========== API РПУ ========== @admin_router.get("/api/user/{user_id}/rpu") async def admin_get_user_rpu( user_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) rpu = get_user_rpu(db, user_id) return { "user_id": user_id, "username": target_user.username, "multipliers": { "consumer": rpu.consumer_multiplier, "industrial": rpu.industrial_multiplier, "mil_spec": rpu.mil_spec_multiplier, "restricted": rpu.restricted_multiplier, "classified": rpu.classified_multiplier, "covert": rpu.covert_multiplier, "rare_special": rpu.rare_special_multiplier, }, "luck_multiplier": rpu.luck_multiplier, "auto_adjust": rpu.auto_adjust, "rpu_level": calculate_rpu_level(rpu), "stats": { "total_spent": rpu.total_spent, "total_opened": rpu.total_opened } } @admin_router.post("/api/user/{user_id}/rpu/update") async def admin_update_user_rpu( user_id: int, consumer: float = Form(1.0), industrial: float = Form(1.0), mil_spec: float = Form(1.0), restricted: float = Form(1.0), classified: float = Form(1.0), covert: float = Form(1.0), rare_special: float = Form(1.0), luck: float = Form(1.0), auto_adjust: bool = Form(False), db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) rpu = get_user_rpu(db, user_id) rpu.consumer_multiplier = max(0.1, min(3.0, consumer)) rpu.industrial_multiplier = max(0.1, min(3.0, industrial)) rpu.mil_spec_multiplier = max(0.1, min(3.0, mil_spec)) rpu.restricted_multiplier = max(0.1, min(3.0, restricted)) rpu.classified_multiplier = max(0.1, min(3.0, classified)) rpu.covert_multiplier = max(0.1, min(3.0, covert)) rpu.rare_special_multiplier = max(0.1, min(3.0, rare_special)) rpu.luck_multiplier = max(0.1, min(3.0, luck)) rpu.auto_adjust = auto_adjust rpu.updated_at = datetime.utcnow() db.commit() return JSONResponse({ "success": True, "message": f"Настройки РПУ для {target_user.username} обновлены", "rpu_level": calculate_rpu_level(rpu) }) @admin_router.post("/api/user/{user_id}/rpu/preset") async def admin_set_rpu_preset( user_id: int, preset: str = Form(...), db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): # Теперь: низкий множитель = высокий РПУ (не везёт) # высокий множитель = низкий РПУ (везёт) presets = { "lucky": {"all": 2.0, "luck": 1.8}, # ~10% РПУ - очень везёт "normal": {"all": 1.0, "luck": 1.0}, # ~50% РПУ - обычно "unlucky": {"all": 0.6, "luck": 0.7}, # ~75% РПУ - не везёт "very_unlucky": {"all": 0.3, "luck": 0.4} # ~95% РПУ - проклят } if preset not in presets: return JSONResponse(status_code=400, content={"error": "Неизвестный пресет"}) target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) rpu = get_user_rpu(db, user_id) p = presets[preset] rpu.consumer_multiplier = p["all"] rpu.industrial_multiplier = p["all"] rpu.mil_spec_multiplier = p["all"] rpu.restricted_multiplier = p["all"] rpu.classified_multiplier = p["all"] rpu.covert_multiplier = p["all"] rpu.rare_special_multiplier = p["all"] rpu.luck_multiplier = p["luck"] rpu.updated_at = datetime.utcnow() db.commit() level = calculate_rpu_level(rpu) desc = get_rpu_description(level) return JSONResponse({ "success": True, "message": f"Пресет '{preset}' применен. Уровень РПУ: {level:.0f}% ({desc})" }) # ========== API КЕЙСЫ ========== @admin_router.post("/api/cases/create") async def admin_create_case( name: str = Form(...), display_name: str = Form(...), price: float = Form(...), description: str = Form(""), image_url: str = Form(""), items: str = Form(...), admin: User = Depends(get_current_admin) ): cases = load_cases_config() for case in cases: if case['name'] == name: return JSONResponse(status_code=400, content={"error": "Кейс с таким именем уже существует"}) try: item_ids = json.loads(items) except: return JSONResponse(status_code=400, content={"error": "Неверный формат списка предметов"}) new_case = { "name": name, "display_name": display_name, "price_open": price, "items": item_ids, "image_url": image_url, "description": description } cases.append(new_case) save_cases_config(cases) return JSONResponse({ "success": True, "case": new_case, "message": f"Кейс '{display_name}' создан" }) @admin_router.post("/api/cases/update/{case_name}") async def admin_update_case( case_name: str, display_name: str = Form(...), price: float = Form(...), description: str = Form(""), image_url: str = Form(""), items: str = Form(...), admin: User = Depends(get_current_admin) ): cases = load_cases_config() for case in cases: if case['name'] == case_name: try: item_ids = json.loads(items) except: return JSONResponse(status_code=400, content={"error": "Неверный формат списка предметов"}) case['display_name'] = display_name case['price_open'] = price case['description'] = description case['image_url'] = image_url case['items'] = item_ids save_cases_config(cases) return JSONResponse({ "success": True, "case": case, "message": f"Кейс '{display_name}' обновлен" }) return JSONResponse(status_code=404, content={"error": "Кейс не найден"}) @admin_router.post("/api/cases/delete/{case_name}") async def admin_delete_case( case_name: str, admin: User = Depends(get_current_admin) ): cases = load_cases_config() cases = [c for c in cases if c['name'] != case_name] save_cases_config(cases) mainpage = load_mainpage_config() for section in mainpage: section['cases'] = [c for c in section['cases'] if c != case_name] save_mainpage_config(mainpage) return JSONResponse({ "success": True, "message": f"Кейс '{case_name}' удален" }) @admin_router.post("/api/mainpage/update") async def admin_update_mainpage( config: str = Form(...), admin: User = Depends(get_current_admin) ): try: new_config = json.loads(config) except: return JSONResponse(status_code=400, content={"error": "Неверный формат JSON"}) save_mainpage_config(new_config) return JSONResponse({ "success": True, "message": "Конфигурация главной страницы обновлена" }) @admin_router.get("/api/items/search") async def admin_search_items( q: str = "", min_price: float = 0, max_price: float = 0, limit: int = 200 ): """Поиск предметов для добавления в кейс / апгрейда""" from frontend import ALL_ITEMS query = q.lower().strip() results = [] def accept_item(item, idx): if min_price > 0 and item.get("price_rub", 0) < min_price: return False if max_price > 0 and item.get("price_rub", 0) > max_price: return False return True if not query or len(query) < 2: # Без поиска возвращаем разнообразные предметы со всего ассортимента # Если есть ценовой фильтр — проходим по всем, иначе шагами if min_price > 0 or max_price > 0: for idx, item in enumerate(ALL_ITEMS): if accept_item(item, idx): item_id = item.get("_id", idx) results.append({ "id": item_id, "name": item.get("market_hash_name", "Unknown"), "rarity": item.get("rarity", "Unknown"), "image_url": item.get("image_url", ""), "price_rub": item.get("price_rub", 0), "weapon": item.get("weapon", ""), "pattern": item.get("pattern", "") }) if len(results) >= limit: break else: step = max(1, len(ALL_ITEMS) // limit) for idx in range(0, len(ALL_ITEMS), step): if not accept_item(ALL_ITEMS[idx], idx): continue item = ALL_ITEMS[idx] item_id = item.get("_id", idx) results.append({ "id": item_id, "name": item.get("market_hash_name", "Unknown"), "rarity": item.get("rarity", "Unknown"), "image_url": item.get("image_url", ""), "price_rub": item.get("price_rub", 0), "weapon": item.get("weapon", ""), "pattern": item.get("pattern", "") }) if len(results) >= 200: break else: for idx, item in enumerate(ALL_ITEMS): if not accept_item(item, idx): continue name = item.get("market_hash_name", "").lower() weapon = item.get("weapon", "").lower() pattern = item.get("pattern", "").lower() if query in name or query in weapon or query in pattern: item_id = item.get("_id", idx) results.append({ "id": item_id, "name": item.get("market_hash_name", "Unknown"), "rarity": item.get("rarity", "Unknown"), "image_url": item.get("image_url", ""), "price_rub": item.get("price_rub", 0), "weapon": item.get("weapon", ""), "pattern": item.get("pattern", "") }) if len(results) >= 200: break return results[:200] @admin_router.post("/api/mainpage/section/add") async def admin_add_mainpage_section( tab: str = Form(...), admin: User = Depends(get_current_admin) ): """Добавить новую секцию на главную""" mainpage = load_mainpage_config() # Проверяем, нет ли уже такой секции for section in mainpage: if section['tab'] == tab: return JSONResponse(status_code=400, content={"error": "Секция уже существует"}) mainpage.append({ "tab": tab, "cases": [] }) save_mainpage_config(mainpage) return JSONResponse({ "success": True, "message": f"Секция '{tab}' добавлена" }) @admin_router.post("/api/mainpage/section/delete") async def admin_delete_mainpage_section( tab: str = Form(...), admin: User = Depends(get_current_admin) ): """Удалить секцию с главной""" mainpage = load_mainpage_config() mainpage = [s for s in mainpage if s['tab'] != tab] save_mainpage_config(mainpage) return JSONResponse({ "success": True, "message": f"Секция '{tab}' удалена" }) @admin_router.post("/api/mainpage/section/add-case") async def admin_add_case_to_section( tab: str = Form(...), case_name: str = Form(...), admin: User = Depends(get_current_admin) ): """Добавить кейс в секцию""" mainpage = load_mainpage_config() for section in mainpage: if section['tab'] == tab: if case_name not in section['cases']: section['cases'].append(case_name) save_mainpage_config(mainpage) return JSONResponse({ "success": True, "message": f"Кейс '{case_name}' добавлен в секцию '{tab}'" }) else: return JSONResponse(status_code=400, content={"error": "Кейс уже в секции"}) return JSONResponse(status_code=404, content={"error": "Секция не найдена"}) @admin_router.post("/api/mainpage/section/remove-case") async def admin_remove_case_from_section( tab: str = Form(...), case_name: str = Form(...), admin: User = Depends(get_current_admin) ): """Удалить кейс из секции""" mainpage = load_mainpage_config() for section in mainpage: if section['tab'] == tab: if case_name in section['cases']: section['cases'].remove(case_name) save_mainpage_config(mainpage) return JSONResponse({ "success": True, "message": f"Кейс '{case_name}' удален из секции '{tab}'" }) return JSONResponse(status_code=404, content={"error": "Секция или кейс не найдены"}) @admin_router.get("/api/cases/list") async def admin_get_cases_list( admin: User = Depends(get_current_admin) ): """Получить список всех кейсов для выбора""" cases = load_cases_config() return [{"name": c['name'], "display_name": c.get('display_name', c['name'])} for c in cases] @admin_router.get("/api/items/{item_id}") async def admin_get_item( item_id: int, admin: User = Depends(get_current_admin) ): """Получить информацию о предмете по ID""" from frontend import ALL_ITEMS for idx, item in enumerate(ALL_ITEMS): if item.get("_id", idx) == item_id: return { "id": item_id, "name": item.get("market_hash_name", "Unknown"), "rarity": item.get("rarity", "Unknown"), "image_url": item.get("image_url", ""), "price_rub": item.get("price_rub", 0), "weapon": item.get("weapon", ""), "pattern": item.get("pattern", ""), "type": item.get("type", "Normal"), "wear": item.get("wear", "") } return JSONResponse(status_code=404, content={"error": "Предмет не найден"}) @admin_router.post("/api/reset-password/{user_id}") async def admin_reset_password( user_id: int, new_password: str = Form(...), db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): """Сброс пароля пользователя (только для админов)""" from auth import get_password_hash target_user = db.query(User).filter(User.id == user_id).first() if not target_user: return JSONResponse(status_code=404, content={"error": "Пользователь не найден"}) target_user.hashed_password = get_password_hash(new_password) db.commit() return JSONResponse({ "success": True, "message": f"Пароль для {target_user.username} изменен" })