Files
popa_minecraft_launcher_api/app/services/case.py
2025-12-07 01:04:04 +05:00

208 lines
7.9 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.

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 # уже есть в Marketplace.py :contentReference[oaicite:1]{index=1}
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_ids": case_data.server_ids 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_ids": c.get("server_ids", ["*"]),
"image_url": c.get("image_url"), # 🔹 отдаем картинку
"items_count": len(c.get("items", []))
}
for c in cases
]
async def get_case(self, case_id: str):
case = await cases_collection.find_one({"id": case_id})
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_ids is not None:
update["server_ids"] = data.server_ids
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_id: 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="В кейсе нет предметов")
# 3. Сервер
server = await game_servers_collection.find_one({"id": server_id})
if not server:
raise HTTPException(status_code=404, detail="Сервер не найден")
# Проверяем, доступен ли кейс на этом сервере (логика как у пакостей) :contentReference[oaicite:2]{index=2}
server_ids = case.get("server_ids", ["*"])
if server_ids and "*" not in server_ids and server_id not in server_ids:
raise HTTPException(status_code=400, 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 {})
}
operation = {
"id": operation_id,
"type": "case_reward",
"player_name": username,
"item_data": item_data,
"price": 0, # тут можно не использовать, т.к. уже оплачен сам кейс
"server_ip": server.get("ip"),
"status": "pending",
"created_at": datetime.utcnow(),
"case_id": case_id,
"case_name": case["name"],
}
await marketplace_operations.insert_one(operation)
# 8. Лог открытия кейса
opening_log = {
"id": str(uuid.uuid4()),
"username": username,
"user_id": user.get("_id"),
"case_id": case_id,
"case_name": case["name"],
"server_id": server_id,
"server_ip": server.get("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,
"operation_id": operation_id,
"balance": new_balance
}