import uuid import random from datetime import datetime from fastapi import HTTPException from app.db.database import db, users_collection from app.services.coins import CoinsService from app.models.case import CaseCreate, CaseUpdate cases_collection = db.cases case_openings_collection = db.case_openings game_servers_collection = db.game_servers marketplace_operations = db.marketplace_operations player_inventory_collection = db.player_inventory class CaseService: def __init__(self): self.coins_service = CoinsService() async def create_case(self, case_data: CaseCreate): case_id = str(uuid.uuid4()) # Генерим id для предметов, если не заданы items = [] for item in case_data.items: item_dict = item.dict() if not item_dict.get("id"): item_dict["id"] = str(uuid.uuid4()) items.append(item_dict) doc = { "id": case_id, "name": case_data.name, "description": case_data.description, "price": case_data.price, "server_ips": case_data.server_ips or ["*"], "image_url": case_data.image_url, # 🔹 сохраняем картинку "items": items, "created_at": datetime.utcnow() } await cases_collection.insert_one(doc) return {"status": "success", "id": case_id} async def list_cases(self): cases = await cases_collection.find().to_list(1000) return [ { "id": c["id"], "name": c["name"], "description": c.get("description"), "price": c["price"], "server_ips": c.get("server_ips", ["*"]), "image_url": c.get("image_url"), # 🔹 отдаем картинку "items_count": len(c.get("items", [])) } for c in cases ] async def get_case(self, case_id: str): # проекция {"_id": 0} говорит Mongo не возвращать поле _id case = await cases_collection.find_one({"id": case_id}, {"_id": 0}) if not case: raise HTTPException(status_code=404, detail="Кейс не найден") return case async def update_case(self, case_id: str, data: CaseUpdate): case = await cases_collection.find_one({"id": case_id}) if not case: raise HTTPException(status_code=404, detail="Кейс не найден") update = {} if data.name is not None: update["name"] = data.name if data.description is not None: update["description"] = data.description if data.price is not None: update["price"] = data.price if data.server_ips is not None: update["server_ips"] = data.server_ips if data.image_url is not None: update["image_url"] = data.image_url # 🔹 обновляем картинку if data.items is not None: items = [] for item in data.items: item_dict = item.dict() if not item_dict.get("id"): item_dict["id"] = str(uuid.uuid4()) items.append(item_dict) update["items"] = items if update: await cases_collection.update_one({"id": case_id}, {"$set": update}) return {"status": "success"} async def delete_case(self, case_id: str): result = await cases_collection.delete_one({"id": case_id}) if result.deleted_count == 0: raise HTTPException(status_code=404, detail="Кейс не найден") return {"status": "success"} async def open_case(self, username: str, case_id: str, server_ip: str): # 1. Пользователь user = await users_collection.find_one({"username": username}) if not user: raise HTTPException(status_code=404, detail="Пользователь не найден") # 2. Кейс case = await cases_collection.find_one({"id": case_id}) if not case: raise HTTPException(status_code=404, detail="Кейс не найден") items = case.get("items", []) if not items: raise HTTPException(status_code=400, detail="В кейсе нет предметов") allowed = case.get("server_ips") or ["*"] if "*" not in allowed and server_ip not in allowed: raise HTTPException(status_code=403, detail="Этот кейс не может быть открыт на этом сервере") # 3. Сервер server = await game_servers_collection.find_one({"ip": server_ip}) if not server: raise HTTPException(status_code=404, detail="Сервер не найден") # 4. Проверяем баланс user_balance = await self.coins_service.get_balance(username) price = case["price"] if user_balance < price: raise HTTPException( status_code=400, detail=f"Недостаточно монет. Требуется: {price}, имеется: {user_balance}" ) # 5. Выбираем предмет по весам (шансам) weights = [max(0, i.get("weight", 1)) for i in items] total_weight = sum(weights) if total_weight <= 0: raise HTTPException(status_code=500, detail="Неверно настроены шансы предметов (вес ≤ 0)") rnd = random.uniform(0, total_weight) current = 0 chosen_item = None for item, w in zip(items, weights): current += w if rnd <= current: chosen_item = item break if not chosen_item: # на всякий случай, но по логике не должно случиться chosen_item = items[-1] # 6. Списываем монеты await self.coins_service.decrease_balance(username, price) # 7. Создаём операцию для сервера — как в Marketplace, только другой type :contentReference[oaicite:3]{index=3} operation_id = str(uuid.uuid4()) item_data = { "material": chosen_item["material"], "amount": chosen_item.get("amount", 1), "meta": (chosen_item.get("meta") or {}) } inventory_item = { "id": str(uuid.uuid4()), "username": username, "server_ip": server_ip, "item_data": item_data, "source": { "type": "case", "case_id": case_id, "case_name": case.get("name"), }, "status": "stored", "created_at": datetime.utcnow(), "delivered_at": None, "withdraw_operation_id": None, } await player_inventory_collection.insert_one(inventory_item) # 8. Лог открытия кейса opening_log = { "id": str(uuid.uuid4()), "username": username, "user_id": user.get("_id"), "case_id": case_id, "case_name": case["name"], "server_ip": server_ip, "reward_item": chosen_item, "price": price, "opened_at": datetime.utcnow() } await case_openings_collection.insert_one(opening_log) # 9. Можно вернуть новый баланс new_balance = await self.coins_service.get_balance(username) return { "status": "success", "message": f"Кейс '{case['name']}' открыт", "reward": chosen_item, "inventory_item_id": inventory_item["id"], "balance": new_balance }