209 lines
8.0 KiB
Python
209 lines
8.0 KiB
Python
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):
|
||
# проекция {"_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_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
|
||
}
|