add:bonus store

This commit is contained in:
2025-07-31 07:00:07 +05:00
parent 8c4db146c9
commit fa9611cc99
5 changed files with 374 additions and 4 deletions

41
app/api/bonuses.py Normal file
View File

@ -0,0 +1,41 @@
from fastapi import APIRouter, Query, Body
from fastapi import HTTPException
from datetime import datetime, timedelta
from app.models.bonus import PurchaseBonus
import uuid
router = APIRouter(
prefix="/api/bonuses",
tags=["Bonuses"]
)
@router.get("/effects")
async def get_user_effects(username: str):
"""Получить активные эффекты пользователя для плагина"""
from app.services.bonus import BonusService
return await BonusService().get_user_active_effects(username)
@router.get("/types")
async def get_bonus_types():
"""Получить доступные типы бонусов"""
from app.services.bonus import BonusService
return await BonusService().list_available_bonuses()
@router.get("/user/{username}")
async def get_user_bonuses(username: str):
"""Получить активные бонусы пользователя"""
from app.services.bonus import BonusService
return await BonusService().get_user_bonuses(username)
@router.post("/purchase")
async def purchase_bonus(purchase_bonus: PurchaseBonus):
"""Купить бонус"""
from app.services.bonus import BonusService
return await BonusService().purchase_bonus(purchase_bonus.username, purchase_bonus.bonus_type_id)
@router.post("/upgrade")
async def upgrade_user_bonus(username: str = Body(...), bonus_id: str = Body(...)):
"""Улучшить существующий бонус"""
from app.services.bonus import BonusService
return await BonusService().upgrade_bonus(username, bonus_id)

48
app/models/bonus.py Normal file
View File

@ -0,0 +1,48 @@
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
class PurchaseBonus(BaseModel):
username: str
bonus_type_id: str
class BonusEffect(BaseModel):
effect_type: str
effect_value: float
expires_at: Optional[datetime] = None
class BonusType(BaseModel):
id: str
name: str
description: str
effect_type: str # "experience", "strength", "speed", etc.
base_effect_value: float # Базовое значение эффекта (например, 1.0 для +100%)
effect_increment: float # Прирост эффекта за уровень (например, 0.1 для +10%)
price: int # Базовая цена
upgrade_price: int # Цена улучшения за уровень
duration: int # Длительность в секундах (0 для бесконечных)
max_level: int = 0 # 0 = без ограничения уровней
class UserTypeBonus(BaseModel):
id: str
name: str
description: str
effect_type: str
effect_value: float
level: int
purchased_at: datetime
can_upgrade: bool
upgrade_price: int
expires_at: Optional[datetime] = None
is_active: bool = True
is_permanent: bool
class UserBonus(BaseModel):
id: str
user_id: str
username: str
bonus_type_id: str
level: int
purchased_at: datetime
expires_at: Optional[datetime] = None
is_active: bool

226
app/services/bonus.py Normal file
View File

@ -0,0 +1,226 @@
import uuid
from datetime import datetime, timedelta
from fastapi import HTTPException
from app.db.database import db
from app.services.coins import CoinsService
from app.models.bonus import BonusType
# Коллекции для бонусов
bonus_types_collection = db.bonus_types
user_bonuses_collection = db.user_bonuses
class BonusService:
async def get_user_active_effects(self, username: str):
"""Получить активные эффекты пользователя для плагина"""
from app.db.database import users_collection
user = await users_collection.find_one({"username": username})
if not user:
return {"effects": []}
# Находим активные бонусы с учетом бесконечных (expires_at = null) или действующих
active_bonuses = await user_bonuses_collection.find({
"user_id": str(user["_id"]),
"is_active": True,
}).to_list(50)
effects = []
for bonus in active_bonuses:
bonus_type = await bonus_types_collection.find_one({"id": bonus["bonus_type_id"]})
if bonus_type:
# Рассчитываем итоговое значение эффекта с учетом уровня
level = bonus.get("level", 1)
effect_value = bonus_type["base_effect_value"] + (level - 1) * bonus_type["effect_increment"]
effect = {
"effect_type": bonus_type["effect_type"],
"effect_value": effect_value
}
# Для временных бонусов добавляем срок
if bonus.get("expires_at"):
effect["expires_at"] = bonus["expires_at"].isoformat()
effects.append(effect)
return {"effects": effects}
async def list_available_bonuses(self):
"""Получить список доступных типов бонусов"""
bonuses = await bonus_types_collection.find().to_list(50)
return {"bonuses": [BonusType(**bonus) for bonus in bonuses]}
async def get_user_bonuses(self, username: str):
"""Получить активные бонусы пользователя"""
from app.db.database import users_collection
user = await users_collection.find_one({"username": username})
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Находим активные бонусы с учетом бесконечных (expires_at = null) или действующих
active_bonuses = await user_bonuses_collection.find({
"user_id": str(user["_id"]),
"is_active": True,
}).to_list(50)
result = []
for bonus in active_bonuses:
bonus_type = await bonus_types_collection.find_one({"id": bonus["bonus_type_id"]})
if bonus_type:
# Рассчитываем итоговое значение эффекта с учетом уровня
level = bonus.get("level", 1)
effect_value = bonus_type["base_effect_value"] + (level - 1) * bonus_type["effect_increment"]
bonus_data = {
"id": bonus["id"],
"name": bonus_type["name"],
"description": bonus_type["description"],
"effect_type": bonus_type["effect_type"],
"effect_value": effect_value,
"level": level,
"purchased_at": bonus["purchased_at"].isoformat(),
"can_upgrade": bonus_type["max_level"] == 0 or level < bonus_type["max_level"],
"upgrade_price": bonus_type["upgrade_price"]
}
# Для временных бонусов добавляем срок
if bonus.get("expires_at"):
bonus_data["expires_at"] = bonus["expires_at"].isoformat()
bonus_data["time_left"] = (bonus["expires_at"] - datetime.utcnow()).total_seconds()
else:
bonus_data["is_permanent"] = True
result.append(bonus_data)
return {"bonuses": result}
async def purchase_bonus(self, username: str, bonus_type_id: str):
"""Покупка базового бонуса пользователем"""
from app.db.database import users_collection
# Находим пользователя
user = await users_collection.find_one({"username": username})
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Находим тип бонуса
bonus_type = await bonus_types_collection.find_one({"id": bonus_type_id})
if not bonus_type:
raise HTTPException(status_code=404, detail="Бонус не найден")
# Проверяем, есть ли уже такой бонус у пользователя
existing_bonus = await user_bonuses_collection.find_one({
"user_id": str(user["_id"]),
"bonus_type_id": bonus_type_id,
"is_active": True
})
if existing_bonus:
raise HTTPException(status_code=400, detail="Этот бонус уже приобретен. Вы можете улучшить его.")
# Проверяем достаточно ли монет
coins_service = CoinsService()
user_coins = await coins_service.get_balance(username)
if user_coins < bonus_type["price"]:
raise HTTPException(status_code=400,
detail=f"Недостаточно монет. Требуется: {bonus_type['price']}, имеется: {user_coins}")
# Создаем запись о бонусе для пользователя
bonus_id = str(uuid.uuid4())
now = datetime.utcnow()
# Если бонус имеет длительность
expires_at = None
if bonus_type["duration"] > 0:
expires_at = now + timedelta(seconds=bonus_type["duration"])
user_bonus = {
"id": bonus_id,
"user_id": str(user["_id"]),
"username": username,
"bonus_type_id": bonus_type_id,
"level": 1, # Начальный уровень
"purchased_at": now,
"expires_at": expires_at,
"is_active": True
}
# Сохраняем бонус в БД
await user_bonuses_collection.insert_one(user_bonus)
# Списываем монеты
await coins_service.decrease_balance(username, bonus_type["price"])
# Формируем текст сообщения
duration_text = "навсегда" if bonus_type["duration"] == 0 else f"на {bonus_type['duration'] // 60} мин."
message = f"Бонус '{bonus_type['name']}' успешно приобретен {duration_text}"
return {
"status": "success",
"message": message,
"remaining_coins": user_coins - bonus_type["price"]
}
async def upgrade_bonus(self, username: str, bonus_id: str):
"""Улучшение уже купленного бонуса"""
from app.db.database import users_collection
# Находим пользователя
user = await users_collection.find_one({"username": username})
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Находим бонус пользователя
user_bonus = await user_bonuses_collection.find_one({
"id": bonus_id,
"user_id": str(user["_id"]),
"is_active": True
})
if not user_bonus:
raise HTTPException(status_code=404, detail="Бонус не найден или не принадлежит вам")
# Находим тип бонуса
bonus_type = await bonus_types_collection.find_one({"id": user_bonus["bonus_type_id"]})
if not bonus_type:
raise HTTPException(status_code=404, detail="Тип бонуса не найден")
# Проверяем ограничение на максимальный уровень
current_level = user_bonus["level"]
if bonus_type["max_level"] > 0 and current_level >= bonus_type["max_level"]:
raise HTTPException(status_code=400, detail="Достигнут максимальный уровень бонуса")
# Рассчитываем стоимость улучшения
upgrade_price = bonus_type["upgrade_price"]
# Проверяем достаточно ли монет
coins_service = CoinsService()
user_coins = await coins_service.get_balance(username)
if user_coins < upgrade_price:
raise HTTPException(status_code=400,
detail=f"Недостаточно монет. Требуется: {upgrade_price}, имеется: {user_coins}")
# Обновляем уровень бонуса
new_level = current_level + 1
await user_bonuses_collection.update_one(
{"id": bonus_id},
{"$set": {"level": new_level}}
)
# Списываем монеты
await coins_service.decrease_balance(username, upgrade_price)
# Рассчитываем новое значение эффекта
new_effect_value = bonus_type["base_effect_value"] + (new_level - 1) * bonus_type["effect_increment"]
return {
"status": "success",
"message": f"Бонус '{bonus_type['name']}' улучшен до уровня {new_level}",
"new_level": new_level,
"effect_value": new_effect_value,
"remaining_coins": user_coins - upgrade_price
}

View File

@ -1,6 +1,6 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from app.api import users, skins, capes, meta, server, store, pranks, marketplace from app.api import users, skins, capes, meta, server, store, pranks, marketplace, bonuses
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
app = FastAPI() app = FastAPI()
@ -13,11 +13,12 @@ app.include_router(server.router)
app.include_router(store.router) app.include_router(store.router)
app.include_router(pranks.router) app.include_router(pranks.router)
app.include_router(marketplace.router) app.include_router(marketplace.router)
app.include_router(bonuses.router)
# Монтируем статику # Монтируем статику
app.mount("/skins", StaticFiles(directory="/app/static/skins"), name="skins") app.mount("/skins", StaticFiles(directory="app/static/skins"), name="skins")
app.mount("/capes", StaticFiles(directory="/app/static/capes"), name="capes") app.mount("/capes", StaticFiles(directory="app/static/capes"), name="capes")
app.mount("/capes_store", StaticFiles(directory="/app/static/capes_store"), name="capes_store") app.mount("/capes_store", StaticFiles(directory="app/static/capes_store"), name="capes_store")
# CORS, middleware и т.д. # CORS, middleware и т.д.
app.add_middleware( app.add_middleware(

View File

@ -0,0 +1,54 @@
# scripts/add_test_bonuses.py
from app.db.database import db
import uuid
from datetime import datetime
# Коллекция для бонусов
bonus_types_collection = db.bonus_types
# Очищаем существующие записи
bonus_types_collection.delete_many({})
# Добавляем типы бонусов
bonus_types = [
{
"id": str(uuid.uuid4()),
"name": "Бонус опыта",
"description": "Увеличивает получаемый опыт на 100% (+10% за уровень)",
"effect_type": "experience",
"base_effect_value": 1.0, # +100%
"effect_increment": 0.1, # +10% за уровень
"price": 100,
"upgrade_price": 50,
"duration": 0, # Бесконечный
"max_level": 0 # Без ограничения уровня
},
{
"id": str(uuid.uuid4()),
"name": "Бонус силы",
"description": "Увеличивает силу атаки на 10% (+5% за уровень)",
"effect_type": "strength",
"base_effect_value": 0.1,
"effect_increment": 0.05,
"price": 75,
"upgrade_price": 40,
"duration": 0, # Бесконечный
"max_level": 10 # Максимум 10 уровней
},
{
"id": str(uuid.uuid4()),
"name": "Бонус скорости",
"description": "Временно увеличивает скорость передвижения на 20%",
"effect_type": "speed",
"base_effect_value": 0.2,
"effect_increment": 0.05,
"price": 40,
"upgrade_price": 30,
"duration": 1800, # 30 минут
"max_level": 5
}
]
# Вставляем бонусы в БД
bonus_types_collection.insert_many(bonus_types)
print(f"Добавлено {len(bonus_types)} типов бонусов")