test cases

This commit is contained in:
2025-12-07 01:04:04 +05:00
parent 8f0a5abfb3
commit 26a96468cc
3 changed files with 277 additions and 0 deletions

38
app/api/case.py Normal file
View File

@ -0,0 +1,38 @@
from fastapi import APIRouter, Depends
from typing import List
from app.services.case import CaseService
from app.models.case import CaseCreate, CaseUpdate
router = APIRouter(prefix="/cases", tags=["Cases"])
def get_case_service():
return CaseService()
@router.get("/")
async def list_cases(case_service: CaseService = Depends(get_case_service)):
return await case_service.list_cases()
@router.post("/")
async def create_case(case_data: CaseCreate, case_service: CaseService = Depends(get_case_service)):
return await case_service.create_case(case_data)
@router.get("/{case_id}")
async def get_case(case_id: str, case_service: CaseService = Depends(get_case_service)):
return await case_service.get_case(case_id)
@router.put("/{case_id}")
async def update_case(case_id: str, data: CaseUpdate, case_service: CaseService = Depends(get_case_service)):
return await case_service.update_case(case_id, data)
@router.delete("/{case_id}")
async def delete_case(case_id: str, case_service: CaseService = Depends(get_case_service)):
return await case_service.delete_case(case_id)
@router.post("/{case_id}/open")
async def open_case(
case_id: str,
username: str,
server_id: str,
case_service: CaseService = Depends(get_case_service)
):
return await case_service.open_case(username=username, case_id=case_id, server_id=server_id)

32
app/models/case.py Normal file
View File

@ -0,0 +1,32 @@
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
class CaseItemMeta(BaseModel):
display_name: Optional[str] = None
lore: Optional[List[str]] = None
enchants: Optional[Dict[str, int]] = None
durability: Optional[int] = None
class CaseItem(BaseModel):
id: Optional[str] = None
name: str
material: str
amount: int = 1
weight: int = 1
meta: Optional[CaseItemMeta] = None
class CaseCreate(BaseModel):
name: str
description: Optional[str] = None
price: int = Field(gt=0)
server_ids: List[str] = Field(default_factory=lambda: ["*"])
image_url: Optional[str] = None # 🔹 Картинка кейса
items: List[CaseItem]
class CaseUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[int] = Field(default=None, gt=0)
server_ids: Optional[List[str]] = None
image_url: Optional[str] = None # 🔹 Можно менять картинку
items: Optional[List[CaseItem]] = None

207
app/services/case.py Normal file
View File

@ -0,0 +1,207 @@
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
}