add: store cape
This commit is contained in:
56
app/api/store.py
Normal file
56
app/api/store.py
Normal file
@ -0,0 +1,56 @@
|
||||
from fastapi import APIRouter, UploadFile, File, Form, Depends, HTTPException
|
||||
from app.services.store_cape import StoreCapeService
|
||||
from app.models.cape import CapeStoreUpdate, CapePurchase
|
||||
from typing import Optional
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/store",
|
||||
tags=["Store"]
|
||||
)
|
||||
|
||||
store_cape_service = StoreCapeService()
|
||||
|
||||
@router.get("/capes")
|
||||
async def get_all_capes():
|
||||
"""Получение списка всех плащей в магазине"""
|
||||
return await store_cape_service.get_all_capes()
|
||||
|
||||
@router.get("/capes/{cape_id}")
|
||||
async def get_cape_by_id(cape_id: str):
|
||||
"""Получение плаща по ID"""
|
||||
return await store_cape_service.get_cape_by_id(cape_id)
|
||||
|
||||
@router.post("/capes")
|
||||
async def add_cape(
|
||||
name: str = Form(...),
|
||||
description: str = Form(...),
|
||||
price: int = Form(...),
|
||||
cape_file: UploadFile = File(...)
|
||||
):
|
||||
"""Добавление нового плаща в магазин"""
|
||||
return await store_cape_service.add_cape(name, description, price, cape_file)
|
||||
|
||||
@router.put("/capes/{cape_id}")
|
||||
async def update_cape(cape_id: str, update_data: CapeStoreUpdate):
|
||||
"""Обновление информации о плаще"""
|
||||
return await store_cape_service.update_cape(cape_id, update_data)
|
||||
|
||||
@router.delete("/capes/{cape_id}")
|
||||
async def delete_cape(cape_id: str):
|
||||
"""Удаление плаща из магазина"""
|
||||
return await store_cape_service.delete_cape(cape_id)
|
||||
|
||||
@router.post("/purchase/cape")
|
||||
async def purchase_cape(username: str, cape_id: str):
|
||||
"""Покупка плаща пользователем"""
|
||||
return await store_cape_service.purchase_cape(username, cape_id)
|
||||
|
||||
@router.get("/user/{username}/capes")
|
||||
async def get_user_purchased_capes(username: str):
|
||||
"""Получение всех приобретенных плащей пользователя"""
|
||||
return await store_cape_service.get_user_purchased_capes(username)
|
||||
|
||||
@router.post("/user/{username}/capes/activate/{cape_id}")
|
||||
async def activate_purchased_cape(username: str, cape_id: str):
|
||||
"""Активация приобретенного плаща"""
|
||||
return await store_cape_service.activate_purchased_cape(username, cape_id)
|
@ -1,4 +1,25 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
class CapeUpdate(BaseModel):
|
||||
cape_url: str
|
||||
|
||||
class CapeStore(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
price: int
|
||||
file_name: str
|
||||
|
||||
class CapeStoreCreate(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
price: int
|
||||
|
||||
class CapeStoreUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
price: Optional[int] = None
|
||||
|
||||
class CapePurchase(BaseModel):
|
||||
cape_id: str
|
||||
|
270
app/services/store_cape.py
Normal file
270
app/services/store_cape.py
Normal file
@ -0,0 +1,270 @@
|
||||
from fastapi import HTTPException, UploadFile
|
||||
from app.db.database import users_collection
|
||||
from app.core.config import FILES_URL
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
from app.models.cape import CapeStore, CapeStoreUpdate
|
||||
|
||||
# Создаем коллекцию для плащей в БД
|
||||
from app.db.database import db
|
||||
store_capes_collection = db.store_capes
|
||||
|
||||
class StoreCapeService:
|
||||
async def add_cape(self, name: str, description: str, price: int, cape_file: UploadFile):
|
||||
"""Добавление нового плаща в магазин"""
|
||||
# Проверка типа файла
|
||||
if not cape_file.content_type.startswith('image/'):
|
||||
raise HTTPException(status_code=400, detail="Файл должен быть изображением")
|
||||
|
||||
# Определяем расширение
|
||||
ext = None
|
||||
if cape_file.content_type == "image/png":
|
||||
ext = "png"
|
||||
elif cape_file.content_type == "image/gif":
|
||||
ext = "gif"
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Поддерживаются только PNG и GIF плащи")
|
||||
|
||||
# Проверка размера файла (максимум 2MB)
|
||||
max_size = 2 * 1024 * 1024 # 2MB
|
||||
contents = await cape_file.read()
|
||||
if len(contents) > max_size:
|
||||
raise HTTPException(status_code=400, detail="Файл слишком большой (максимум 2MB)")
|
||||
|
||||
# Создаем папку для плащей магазина, если ее нет
|
||||
cape_dir = Path("app/static/capes_store")
|
||||
cape_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Генерируем ID и имя файла
|
||||
cape_id = str(uuid.uuid4())
|
||||
cape_filename = f"store_cape_{cape_id}.{ext}"
|
||||
cape_path = cape_dir / cape_filename
|
||||
|
||||
# Сохраняем файл
|
||||
with open(cape_path, "wb") as f:
|
||||
f.write(contents)
|
||||
|
||||
# Создаем запись в БД
|
||||
cape_data = {
|
||||
"id": cape_id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"price": price,
|
||||
"file_name": cape_filename,
|
||||
"created_at": datetime.utcnow()
|
||||
}
|
||||
|
||||
await store_capes_collection.insert_one(cape_data)
|
||||
|
||||
return {"id": cape_id, "status": "success"}
|
||||
|
||||
async def get_all_capes(self):
|
||||
"""Получение всех плащей из магазина"""
|
||||
capes = await store_capes_collection.find().to_list(1000)
|
||||
|
||||
result = []
|
||||
for cape in capes:
|
||||
result.append({
|
||||
"id": cape["id"],
|
||||
"name": cape["name"],
|
||||
"description": cape["description"],
|
||||
"price": cape["price"],
|
||||
"image_url": f"{FILES_URL}/capes_store/{cape['file_name']}"
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
async def get_cape_by_id(self, cape_id: str):
|
||||
"""Получение плаща по ID"""
|
||||
cape = await store_capes_collection.find_one({"id": cape_id})
|
||||
if not cape:
|
||||
raise HTTPException(status_code=404, detail="Плащ не найден")
|
||||
|
||||
return {
|
||||
"id": cape["id"],
|
||||
"name": cape["name"],
|
||||
"description": cape["description"],
|
||||
"price": cape["price"],
|
||||
"image_url": f"{FILES_URL}/capes_store/{cape['file_name']}"
|
||||
}
|
||||
|
||||
async def update_cape(self, cape_id: str, update_data: CapeStoreUpdate):
|
||||
"""Обновление информации о плаще"""
|
||||
cape = await store_capes_collection.find_one({"id": cape_id})
|
||||
if not cape:
|
||||
raise HTTPException(status_code=404, detail="Плащ не найден")
|
||||
|
||||
# Готовим данные для обновления
|
||||
update = {}
|
||||
if update_data.name:
|
||||
update["name"] = update_data.name
|
||||
if update_data.description:
|
||||
update["description"] = update_data.description
|
||||
if update_data.price is not None:
|
||||
update["price"] = update_data.price
|
||||
|
||||
if update:
|
||||
result = await store_capes_collection.update_one(
|
||||
{"id": cape_id},
|
||||
{"$set": update}
|
||||
)
|
||||
|
||||
if result.modified_count == 0:
|
||||
raise HTTPException(status_code=500, detail="Ошибка при обновлении")
|
||||
|
||||
return {"status": "success"}
|
||||
|
||||
async def delete_cape(self, cape_id: str):
|
||||
"""Удаление плаща из магазина"""
|
||||
cape = await store_capes_collection.find_one({"id": cape_id})
|
||||
if not cape:
|
||||
raise HTTPException(status_code=404, detail="Плащ не найден")
|
||||
|
||||
# Удаляем файл
|
||||
cape_path = Path(f"app/static/capes_store/{cape['file_name']}")
|
||||
if cape_path.exists():
|
||||
try:
|
||||
cape_path.unlink()
|
||||
except Exception as e:
|
||||
print(f"Ошибка при удалении файла: {e}")
|
||||
|
||||
# Удаляем из БД
|
||||
result = await store_capes_collection.delete_one({"id": cape_id})
|
||||
if result.deleted_count == 0:
|
||||
raise HTTPException(status_code=500, detail="Ошибка при удалении из БД")
|
||||
|
||||
return {"status": "success"}
|
||||
|
||||
async def purchase_cape(self, username: str, cape_id: str):
|
||||
"""Покупка плаща пользователем"""
|
||||
# Находим пользователя
|
||||
user = await users_collection.find_one({"username": username})
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||||
|
||||
# Находим плащ
|
||||
cape = await store_capes_collection.find_one({"id": cape_id})
|
||||
if not cape:
|
||||
raise HTTPException(status_code=404, detail="Плащ не найден")
|
||||
|
||||
# Проверяем достаточно ли монет
|
||||
user_coins = user.get("coins", 0)
|
||||
if user_coins < cape["price"]:
|
||||
raise HTTPException(status_code=400,
|
||||
detail=f"Недостаточно монет. Требуется: {cape['price']}, имеется: {user_coins}")
|
||||
|
||||
# Копируем плащ из хранилища магазина в персональную папку пользователя
|
||||
cape_store_path = Path(f"app/static/capes_store/{cape['file_name']}")
|
||||
|
||||
# Создаем папку для плащей пользователя
|
||||
cape_dir = Path("app/static/capes")
|
||||
cape_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Генерируем имя файла для персонального плаща
|
||||
filename_parts = cape['file_name'].split('.')
|
||||
ext = filename_parts[-1]
|
||||
cape_filename = f"{username}_{int(datetime.now().timestamp())}.{ext}"
|
||||
cape_path = cape_dir / cape_filename
|
||||
|
||||
# Копируем файл
|
||||
try:
|
||||
shutil.copy(cape_store_path, cape_path)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Ошибка при копировании файла: {e}")
|
||||
|
||||
# Обновляем данные пользователя
|
||||
# 1. Списываем монеты
|
||||
# 2. Устанавливаем новый плащ
|
||||
# 3. Добавляем плащ в список приобретенных
|
||||
result = await users_collection.update_one(
|
||||
{"username": username},
|
||||
{"$set": {
|
||||
"coins": user_coins - cape["price"],
|
||||
"cloak_url": f"{FILES_URL}/capes/{cape_filename}"
|
||||
},
|
||||
"$push": {
|
||||
"purchased_capes": {
|
||||
"cape_id": cape_id,
|
||||
"cape_name": cape["name"],
|
||||
"file_name": cape_filename,
|
||||
"purchased_at": datetime.utcnow()
|
||||
}
|
||||
}}
|
||||
)
|
||||
|
||||
if result.modified_count == 0:
|
||||
# Если обновление не удалось, удаляем файл плаща
|
||||
if os.path.exists(cape_path):
|
||||
os.remove(cape_path)
|
||||
raise HTTPException(status_code=500, detail="Ошибка при обновлении данных пользователя")
|
||||
|
||||
# Логируем покупку в БД
|
||||
purchase_data = {
|
||||
"username": username,
|
||||
"user_id": user["_id"],
|
||||
"cape_id": cape_id,
|
||||
"cape_name": cape["name"],
|
||||
"price": cape["price"],
|
||||
"purchase_date": datetime.utcnow()
|
||||
}
|
||||
|
||||
from app.db.database import db
|
||||
await db.purchases.insert_one(purchase_data)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Плащ '{cape['name']}' успешно приобретен",
|
||||
"remaining_coins": user_coins - cape["price"]
|
||||
}
|
||||
|
||||
async def get_user_purchased_capes(self, username: str):
|
||||
"""Получение всех плащей, приобретенных пользователем"""
|
||||
user = await users_collection.find_one({"username": username})
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||||
|
||||
purchased_capes = user.get("purchased_capes", [])
|
||||
result = []
|
||||
|
||||
for cape in purchased_capes:
|
||||
result.append({
|
||||
"cape_id": cape.get("cape_id"),
|
||||
"cape_name": cape.get("cape_name"),
|
||||
"image_url": f"{FILES_URL}/capes/{cape.get('file_name')}",
|
||||
"purchased_at": cape.get("purchased_at"),
|
||||
"is_active": user.get("cloak_url") == f"{FILES_URL}/capes/{cape.get('file_name')}"
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
async def activate_purchased_cape(self, username: str, cape_id: str):
|
||||
"""Активация приобретенного плаща"""
|
||||
user = await users_collection.find_one({"username": username})
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="Пользователь не найден")
|
||||
|
||||
# Проверяем, что плащ был приобретен
|
||||
purchased_capes = user.get("purchased_capes", [])
|
||||
selected_cape = None
|
||||
|
||||
for cape in purchased_capes:
|
||||
if cape.get("cape_id") == cape_id:
|
||||
selected_cape = cape
|
||||
break
|
||||
|
||||
if not selected_cape:
|
||||
raise HTTPException(status_code=404, detail="Плащ не найден среди приобретенных")
|
||||
|
||||
# Устанавливаем выбранный плащ
|
||||
await users_collection.update_one(
|
||||
{"username": username},
|
||||
{"$set": {"cloak_url": f"{FILES_URL}/capes/{selected_cape.get('file_name')}"}}
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Плащ '{selected_cape.get('cape_name')}' активирован"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
5
main.py
5
main.py
@ -1,6 +1,6 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from app.api import users, skins, capes, meta, server
|
||||
from app.api import users, skins, capes, meta, server, store
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app = FastAPI()
|
||||
@ -10,13 +10,14 @@ app.include_router(users.router)
|
||||
app.include_router(skins.router)
|
||||
app.include_router(capes.router)
|
||||
app.include_router(server.router)
|
||||
app.include_router(store.router)
|
||||
|
||||
# Монтируем статику
|
||||
app.mount("/skins", StaticFiles(directory="app/static/skins"), name="skins")
|
||||
app.mount("/capes", StaticFiles(directory="app/static/capes"), name="capes")
|
||||
app.mount("/capes_store", StaticFiles(directory="app/static/capes_store"), name="capes_store")
|
||||
|
||||
# CORS, middleware и т.д.
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
|
Reference in New Issue
Block a user