57 lines
2.4 KiB
Python
57 lines
2.4 KiB
Python
"""Общий каталог картинок для бота и админки (Docker volume)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import shutil
|
||
from pathlib import Path
|
||
|
||
from starlette.datastructures import UploadFile
|
||
|
||
from common.config import AdminSettings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
#: Встроенная картинка приветствия (лежит в образе бота и админки).
|
||
BUILTIN_WELCOME = Path("images/welcome_image.jpg")
|
||
|
||
#: Имя файла в volume, которое ждёт сид последовательности `welcome`.
|
||
WELCOME_IMAGE_NAME = "welcome_image.jpg"
|
||
|
||
#: Допустимые типы и максимальный размер загружаемых картинок.
|
||
ALLOWED_IMAGE_TYPES = {"image/jpeg": ".jpg", "image/png": ".png", "image/webp": ".webp"}
|
||
MAX_IMAGE_BYTES = 5 * 1024 * 1024
|
||
|
||
|
||
async def read_image(upload: UploadFile) -> tuple[bytes, str, str]:
|
||
"""Прочитать картинку из формы. Возвращает (данные, mime, расширение)."""
|
||
content_type = (upload.content_type or "").lower()
|
||
extension = ALLOWED_IMAGE_TYPES.get(content_type)
|
||
if extension is None:
|
||
raise ValueError("Картинка должна быть JPEG, PNG или WebP")
|
||
|
||
data = await upload.read()
|
||
if len(data) > MAX_IMAGE_BYTES:
|
||
raise ValueError("Картинка больше 5 МБ")
|
||
return data, content_type, extension
|
||
|
||
|
||
def ensure_media(settings: AdminSettings) -> Path:
|
||
"""Создать каталог и положить туда встроенное приветствие, если его нет."""
|
||
directory = Path(settings.media_dir)
|
||
directory.mkdir(parents=True, exist_ok=True)
|
||
|
||
target = directory / WELCOME_IMAGE_NAME
|
||
if not target.exists() and BUILTIN_WELCOME.is_file():
|
||
try:
|
||
shutil.copyfile(BUILTIN_WELCOME, target)
|
||
logger.info("Приветственная картинка скопирована в %s", target)
|
||
except OSError:
|
||
logger.warning("Не удалось скопировать приветственную картинку в %s", target, exc_info=True)
|
||
return directory
|
||
|
||
|
||
def media_path(settings: AdminSettings, name: str) -> Path:
|
||
"""Безопасный путь к файлу в media (без выхода из каталога)."""
|
||
return Path(settings.media_dir) / Path(name).name
|