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, Upgrade, TransactionLog 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("/user/{user_id}/history", response_class=HTMLResponse) async def admin_user_history( 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, "Пользователь не найден") # Все открытия кейсов openings = db.query(CaseOpening).filter( CaseOpening.user_id == user_id ).order_by(desc(CaseOpening.opened_at)).limit(200).all() # Все апгрейды upgrades = db.query(Upgrade).filter( Upgrade.user_id == user_id ).order_by(desc(Upgrade.created_at)).limit(200).all() # Все транзакции transactions = db.query(TransactionLog).filter( TransactionLog.user_id == user_id ).order_by(desc(TransactionLog.created_at)).limit(200).all() from rpu import calculate_ceiling, get_user_total_value, get_user_rpu ceiling = calculate_ceiling(db, user_id) total_value = get_user_total_value(db, user_id) rpu = get_user_rpu(db, user_id) return templates.TemplateResponse(request, "admin/user_history.html", { "user": admin, "target_user": target_user, "openings": openings, "upgrades": upgrades, "transactions": transactions, "ceiling": ceiling, "total_value": total_value, "rpu": rpu, }) @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'] = [] case['items_count'] = len(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 = item_data.get("mid_float_used") or 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"), image_url=item_data.get("image_url", ""), price_rub=item_data.get("price_rub", 0), 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) from promo_phase import get_promo_phase_info promo = get_promo_phase_info() # Потолок from rpu import calculate_ceiling ceiling = calculate_ceiling(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, "total_value_received": rpu.total_value_received, "current_streak": rpu.current_streak, "best_streak": rpu.best_streak, "worst_streak": rpu.worst_streak }, # ── RPU v2 ── "v2": { "ceiling": { "total_deposited": target_user.total_deposited or 0, "ceiling_multiplier": rpu.ceiling_multiplier, "current_ceiling": ceiling, "break_count": rpu.ceiling_break_count, "break_session": rpu.ceiling_break_session, }, "session": { "luck_budget": round(rpu.luck_budget, 1), "session_spent": round(rpu.session_spent, 0), "session_won": round(rpu.session_won, 0), "reset_date": rpu.session_reset_date.isoformat() if rpu.session_reset_date else None, }, "comeback": { "active": rpu.comeback_active, "openings_left": rpu.comeback_openings_left, "multiplier": rpu.comeback_multiplier, "consecutive_loss_value": round(rpu.consecutive_loss_value or 0, 0), "consecutive_loss_count": rpu.consecutive_loss_count or 0, }, "hot_cold": { "hot_score": round(rpu.hot_score or 0, 2), "last_activity": rpu.last_activity_date.isoformat() if rpu.last_activity_date else None, }, "promo_phase": { "phase": promo["phase"], "effect": promo["effect"], "next_switch": promo["next_switch"], "remaining_minutes": promo["remaining_minutes"], } } } @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( case_name: str = Form(...), display_name: str = Form(...), price_open: float = Form(...), description: str = Form(""), image_url: str = Form(""), items_json: str = Form(...), admin: User = Depends(get_current_admin) ): cases = load_cases_config() for case in cases: if case['name'] == case_name: return JSONResponse(status_code=400, content={"error": "Кейс с таким именем уже существует"}) try: parsed_items = json.loads(items_json) except: return JSONResponse(status_code=400, content={"error": "Неверный формат списка предметов"}) new_case = { "name": case_name, "display_name": display_name, "price_open": price_open, "items": parsed_items, "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_open: float = Form(...), description: str = Form(""), image_url: str = Form(""), items_json: str = Form(...), new_name: str = Form(""), admin: User = Depends(get_current_admin) ): cases = load_cases_config() for case in cases: if case['name'] == case_name: try: parsed_items = json.loads(items_json) except: return JSONResponse(status_code=400, content={"error": "Неверный формат списка предметов"}) target_name = new_name or case_name # Проверяем конфликт имени при переименовании if new_name and new_name != case_name: for c in cases: if c['name'] == new_name: return JSONResponse(status_code=400, content={"error": "Кейс с таким именем уже существует"}) case['name'] = new_name case['display_name'] = display_name case['price_open'] = price_open case['description'] = description case['image_url'] = image_url case['items'] = parsed_items save_cases_config(cases) # Обновляем mainpage если переименован if new_name and new_name != case_name: mainpage = load_mainpage_config() for section in mainpage: section['cases'] = [new_name if c == case_name else c for c in section['cases']] save_mainpage_config(mainpage) 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, rarity_filter: str = "", collection_filter: str = "", type_filter: str = "", weapon_filter: str = "", sort_by: str = "", sort_order: str = "asc", include_subvariants: bool = False ): """Поиск предметов для добавления в кейс / апгрейда.""" from frontend import ALL_ITEMS from backend import COLLECTIONS_INDEX query = q.lower().strip() results = [] seen_bases = set() def accept_item(item, idx): if not include_subvariants and item.get("is_subvariant"): return False 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 if rarity_filter: item_rarity = item.get("rarity", "").lower() if rarity_filter.lower() not in item_rarity: return False if collection_filter: item_coll = item.get("collection", "").lower() if collection_filter.lower() not in item_coll: return False if type_filter: item_type = item.get("type", "").lower() if type_filter.lower() != item_type: return False if weapon_filter: item_weapon = item.get("weapon", "").lower() if weapon_filter.lower() != item_weapon: return False return True def make_result(item, idx): name = item.get("market_hash_name", "Unknown") dedup_key = f"{name}|{item.get('weapon','')}|{item.get('pattern','')}|{item.get('rarity','')}" if dedup_key in seen_bases: return None seen_bases.add(dedup_key) return { "id": item.get("_id", idx), "name": name, "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", ""), "collection": item.get("collection", ""), "type": item.get("type", "Normal"), } if query and len(query) < 2: return [] if not query or len(query) < 2: if any([min_price > 0, max_price > 0, rarity_filter, collection_filter, type_filter, weapon_filter]): for idx, item in enumerate(ALL_ITEMS): if not accept_item(item, idx): continue r = make_result(item, idx) if r: results.append(r) if len(results) >= limit: break else: step = max(1, len(ALL_ITEMS) // limit) for idx in range(0, len(ALL_ITEMS), step): item = ALL_ITEMS[idx] if not accept_item(item, idx): continue r = make_result(item, idx) if r: results.append(r) if len(results) >= limit: 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() coll = item.get("collection", "").lower() if query in name or query in weapon or query in pattern or query in coll: r = make_result(item, idx) if r: results.append(r) if len(results) >= limit: break # Сортировка if sort_by in ("price_rub", "name", "rarity"): reverse = sort_order == "desc" if sort_by == "rarity": rarity_rank = { "Consumer Grade": 0, "Industrial Grade": 1, "Mil-Spec": 2, "Mil-Spec Grade": 2, "Restricted": 3, "Classified": 4, "Covert": 5, "Rare Special Item": 6, "Extraordinary": 6, "Contraband": 7 } results.sort(key=lambda x: rarity_rank.get(x.get("rarity", ""), 0), reverse=reverse) else: results.sort(key=lambda x: x.get(sort_by, ""), reverse=reverse) return results[:200] @admin_router.get("/api/items/filters") async def admin_items_filters(): """Возвращает списки уникальных значений для фильтров""" from frontend import ALL_ITEMS weapons = set() types = set() for item in ALL_ITEMS: if item.get("is_subvariant"): continue w = item.get("weapon", "") if w: weapons.add(w) t = item.get("type", "") if t: types.add(t) return { "weapons": sorted(weapons), "types": sorted(types), } @admin_router.get("/api/collections") async def admin_list_collections(): """Возвращает список всех коллекций для фильтра""" from backend import COLLECTIONS_INDEX return {"collections": sorted(COLLECTIONS_INDEX.keys())} @admin_router.get("/api/items/by-collection") async def admin_items_by_collection( collection: str = "", rarity_filter: str = "", type_filter: str = "", weapon_filter: str = "", include_subvariants: bool = False ): """Возвращает предметы из указанной коллекции""" from backend import COLLECTIONS_INDEX from frontend import get_item results = [] seen_bases = set() if collection not in COLLECTIONS_INDEX: return results for idx in COLLECTIONS_INDEX[collection]: item = get_item(idx) if not item: continue if not include_subvariants and item.get("is_subvariant"): continue if rarity_filter: if rarity_filter.lower() not in item.get("rarity", "").lower(): continue if type_filter: if type_filter.lower() != item.get("type", "").lower(): continue if weapon_filter: if weapon_filter.lower() != item.get("weapon", "").lower(): continue name = item.get("market_hash_name", "Unknown") dedup_key = f"{name}|{item.get('weapon','')}|{item.get('pattern','')}|{item.get('rarity','')}" if dedup_key in seen_bases: continue seen_bases.add(dedup_key) results.append({ "id": item.get("_id", idx), "name": name, "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", ""), "collection": item.get("collection", ""), "type": item.get("type", "Normal"), }) return results @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": "Предмет не найден"}) # ========== API ПРОМОКОДЫ ========== @admin_router.get("/api/promocodes") async def admin_list_promocodes( db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): codes = db.query(PromoCode).order_by(desc(PromoCode.created_at)).limit(100).all() return [{ "id": c.id, "code": c.code, "reward_type": c.reward_type, "reward_amount": c.reward_amount, "max_uses": c.max_uses, "used_count": c.used_count, "start_date": c.start_date.isoformat() if c.start_date else None, "end_date": c.end_date.isoformat() if c.end_date else None, "is_active": c.is_active, } for c in codes] @admin_router.post("/api/promocodes/create") async def admin_create_promocode( code: str = Form(...), reward_type: str = Form(...), reward_amount: float = Form(0), reward_data: str = Form(""), max_uses: int = Form(1), end_date_str: str = Form(""), is_active: bool = Form(True), db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): existing = db.query(PromoCode).filter(PromoCode.code == code).first() if existing: return JSONResponse(status_code=400, content={"error": "Промокод с таким кодом уже существует"}) end_date = None if end_date_str: try: end_date = datetime.fromisoformat(end_date_str) except: pass promo = PromoCode( code=code, reward_type=reward_type, reward_amount=reward_amount, reward_data=reward_data, max_uses=max_uses, end_date=end_date, is_active=is_active, created_by=admin.id, ) db.add(promo) db.commit() return {"success": True, "message": f"Промокод '{code}' создан"} @admin_router.post("/api/promocodes/toggle/{promo_id}") async def admin_toggle_promocode( promo_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): promo = db.query(PromoCode).filter(PromoCode.id == promo_id).first() if not promo: return JSONResponse(status_code=404, content={"error": "Промокод не найден"}) promo.is_active = not promo.is_active db.commit() return {"success": True, "is_active": promo.is_active} @admin_router.post("/api/promocodes/delete/{promo_id}") async def admin_delete_promocode( promo_id: int, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): promo = db.query(PromoCode).filter(PromoCode.id == promo_id).first() if not promo: return JSONResponse(status_code=404, content={"error": "Промокод не найден"}) db.delete(promo) db.commit() return {"success": True, "message": "Промокод удален"} @admin_router.get("/api/promo-phase") async def admin_get_promo_phase( admin: User = Depends(get_current_admin) ): """Информация о текущей промо-фазе (для админки)""" from promo_phase import get_phase_debug_info return get_phase_debug_info() # ========== API ТРАНЗАКЦИИ ========== @admin_router.get("/api/transactions") async def admin_list_transactions( user_id: Optional[int] = None, limit: int = 100, db: Session = Depends(get_db), admin: User = Depends(get_current_admin) ): query = db.query(TransactionLog) if user_id: query = query.filter(TransactionLog.user_id == user_id) txns = query.order_by(desc(TransactionLog.created_at)).limit(limit).all() return [{ "id": t.id, "user_id": t.user_id, "tx_type": t.tx_type, "amount": t.amount, "fee": t.fee, "promo_code": t.promo_code, "item_name": t.item_name, "details": t.details, "created_at": t.created_at.isoformat(), } for t in txns] @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} изменен" })