This commit is contained in:
xds
2026-02-03 23:16:18 +03:00
parent 43e9c263d5
commit 11c1f4f7dc
5 changed files with 80 additions and 13 deletions

View File

@@ -1,13 +1,18 @@
from fastapi import APIRouter
from typing import List, Optional
from aiogram.types import BufferedInputFile
from fastapi import APIRouter, UploadFile, File, Form
from fastapi.openapi.models import MediaType
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
from models.Asset import Asset, AssetType
from repos.dao import DAO
router = APIRouter(prefix="/api/assets", tags=["Assets"])
@router.get("/{asset_id}")
async def get_asset(asset_id: str, request: Request) -> Response:
dao = request.app.state.dao
@@ -15,15 +20,73 @@ async def get_asset(asset_id: str, request: Request) -> Response:
# 2. Проверка на существование
if not asset:
raise HTTPException(status_code=404, detail="Asset not found")
return Response(content=asset.data, media_type="image/png")
headers = {
# Кэшировать на 1 год (31536000 сек)
"Cache-Control": "public, max-age=31536000, immutable"
}
return Response(content=asset.data, media_type="image/png", headers=headers)
@router.get("")
async def get_assets(request: Request) -> JSONResponse:
async def get_assets(request: Request) -> List[Asset]:
dao: DAO = request.app.state.dao
assets = await dao.assets.get_assets()
assets_links = []
for asset in assets:
assets_links.append("/api/assets/{}".format(asset.id))
return JSONResponse(content=assets_links)
return assets
@router.post("/upload", response_model=Asset)
async def upload_asset(
request: Request,
# Файл обязателен
file: UploadFile = File(...),
# Остальные поля принимаем как Form-data (не JSON!)
name: str = Form(...),
type: AssetType = Form(...),
linked_char_id: Optional[str] = Form(None)
):
"""
Загружает файл, отправляет его в ТГ (для получения ID) и сохраняет в БД.
"""
# 1. Читаем байты файла
file_content = await file.read()
if not file_content:
raise HTTPException(status_code=400, detail="File is empty")
# 2. Получаем необходимые зависимости из state
bot = request.app.state.bot # Бот нужен, чтобы получить tg_file_id
admin_id = request.app.state.admin_id # Куда отправлять файл "на хранение"
dao = request.app.state.assets_dao
# 3. Отправляем файл в Telegram, чтобы получить tg_doc_file_id
# (Это обязательно, так как ваша модель требует этот ID)
try:
tg_msg = await bot.send_document(
chat_id=admin_id,
document=BufferedInputFile(file_content, filename=file.filename),
caption=f"📥 Uploaded via API: {name}"
)
# Получаем ID документа из ответа ТГ
tg_doc_id = tg_msg.document.file_id
# Если это картинка, можно попытаться достать и photo_id (для превью)
# Но send_document обычно возвращает именно документ.
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to upload to Telegram: {e}")
# 4. Создаем объект Asset
# Pydantic сам подставит created_at и вычислит link
new_asset = Asset(
name=name,
type=type,
linked_char_id=linked_char_id,
data=file_content, # Сохраняем байты в БД
tg_doc_file_id=tg_doc_id # ID из телеграма
)
# 5. Сохраняем через DAO
saved_asset = await dao.save_asset(new_asset)
return saved_asset

View File

@@ -28,8 +28,8 @@ async def get_character_assets(character_id: str, request: Request) -> List[Asse
@router.get("/{character_id}", response_model=Character)
async def get_character_by_id(character_id: int, request: Request) -> Character:
async def get_character_by_id(character_id: str, request: Request) -> Character:
dao: DAO = request.app.state.dao
character = await dao.chars.get_character_by_id(character_id)
character = await dao.chars.get_character(character_id)
return character

View File

@@ -22,10 +22,10 @@ class Asset(BaseModel):
# --- CALCULATED FIELD ---
@computed_field
def link(self) -> str:
def url(self) -> str:
"""
Это поле автоматически вычислится и попадет в model_dump() / .json()
"""
if self.id:
return f"/api/assets/{self.id}"
return f"/assets/{self.id}"
return ""

View File

@@ -28,9 +28,12 @@ class AssetsRepo:
return assets
async def get_asset(self, asset_id: str, with_data: bool = True) -> Asset:
projection = {"_id": 1, "name": 1, "type": 1, "tg_doc_file_id": 1}
if with_data:
projection["data"] = 1
res = await self.collection.find_one({"_id": ObjectId(asset_id)},
{"_id": 1, "name": 1, "type": 1, "tg_doc_file_id": 1,
"data": 0 if not with_data else 1})
projection)
res["id"] = str(res.pop("_id"))
return Asset(**res)

View File

@@ -34,6 +34,7 @@ pydantic==2.10.6
pydantic_core==2.27.2
pymongo==4.16.0
python-dotenv==1.2.1
python-multipart==0.0.22
requests==2.32.5
rsa==4.9.1
sniffio==1.3.1