aorus
This commit is contained in:
@@ -10,6 +10,7 @@ from sqlmodel import Session, select
|
||||
from app.config import UPLOAD_DIR
|
||||
from app.database import get_session
|
||||
from app.models.media import Attachment, Media
|
||||
from app.models.settings import UserSettings
|
||||
|
||||
|
||||
class MediaPatch(BaseModel):
|
||||
@@ -102,6 +103,20 @@ def _canonicalize_rows(rows: List[Media], session: Session) -> None:
|
||||
session.commit()
|
||||
|
||||
|
||||
try:
|
||||
import pillow_heif
|
||||
pillow_heif.register_heif_opener()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def _is_heic(content_type: str, filename: str) -> bool:
|
||||
if content_type.lower() in ("image/heic", "image/heif"):
|
||||
return True
|
||||
fn = (filename or "").lower()
|
||||
return fn.endswith(".heic") or fn.endswith(".heif")
|
||||
|
||||
|
||||
def _save_webp(data: bytes, max_px: int) -> str:
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -122,12 +137,28 @@ def _save_webp(data: bytes, max_px: int) -> str:
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload_file(file: UploadFile = File(...)):
|
||||
async def upload_file(
|
||||
file: UploadFile = File(...),
|
||||
session: Session = Depends(get_session),
|
||||
):
|
||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
data = await file.read()
|
||||
ct = file.content_type or ""
|
||||
if ct.startswith("image/"):
|
||||
name = _save_webp(data, 1200)
|
||||
|
||||
# Lire la largeur max configurée (défaut 1200, 0 = taille originale)
|
||||
setting = session.exec(select(UserSettings).where(UserSettings.cle == "image_max_width")).first()
|
||||
max_px = 1200
|
||||
if setting:
|
||||
try:
|
||||
max_px = int(setting.valeur)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if max_px <= 0:
|
||||
max_px = 99999
|
||||
|
||||
heic = _is_heic(ct, file.filename or "")
|
||||
if heic or ct.startswith("image/"):
|
||||
name = _save_webp(data, max_px)
|
||||
thumb = _save_webp(data, 300)
|
||||
return {"url": f"/uploads/{name}", "thumbnail_url": f"/uploads/{thumb}"}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from starlette.background import BackgroundTask
|
||||
from sqlmodel import Session, select
|
||||
@@ -235,8 +235,8 @@ def get_debug_system_stats() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
@router.get("/settings/backup/download")
|
||||
def download_backup_zip() -> FileResponse:
|
||||
def _create_backup_zip() -> tuple[Path, str]:
|
||||
"""Crée l'archive ZIP de sauvegarde. Retourne (chemin_tmp, nom_fichier)."""
|
||||
now = datetime.now(timezone.utc)
|
||||
ts = now.strftime("%Y%m%d_%H%M%S")
|
||||
db_path = _resolve_sqlite_db_path()
|
||||
@@ -247,17 +247,12 @@ def download_backup_zip() -> FileResponse:
|
||||
os.close(fd)
|
||||
tmp_zip = Path(tmp_zip_path)
|
||||
|
||||
stats = {
|
||||
"database_files": 0,
|
||||
"upload_files": 0,
|
||||
"text_files": 0,
|
||||
}
|
||||
stats = {"database_files": 0, "upload_files": 0, "text_files": 0}
|
||||
|
||||
with zipfile.ZipFile(tmp_zip, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zipf:
|
||||
if db_path and db_path.is_file():
|
||||
zipf.write(db_path, arcname=f"db/{db_path.name}")
|
||||
stats["database_files"] = 1
|
||||
|
||||
stats["upload_files"] = _zip_directory(zipf, uploads_dir, "uploads")
|
||||
stats["text_files"] = _zip_data_text_files(zipf, data_root, db_path, uploads_dir)
|
||||
|
||||
@@ -274,10 +269,66 @@ def download_backup_zip() -> FileResponse:
|
||||
}
|
||||
zipf.writestr("manifest.json", json.dumps(manifest, ensure_ascii=False, indent=2))
|
||||
|
||||
download_name = f"jardin_backup_{ts}.zip"
|
||||
return tmp_zip, f"jardin_backup_{ts}.zip"
|
||||
|
||||
|
||||
@router.get("/settings/backup/download")
|
||||
def download_backup_zip() -> FileResponse:
|
||||
tmp_zip, download_name = _create_backup_zip()
|
||||
return FileResponse(
|
||||
path=str(tmp_zip),
|
||||
media_type="application/zip",
|
||||
filename=download_name,
|
||||
background=BackgroundTask(_safe_remove, str(tmp_zip)),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/settings/backup/samba")
|
||||
def backup_to_samba(session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||
"""Envoie une sauvegarde ZIP vers un partage Samba/CIFS."""
|
||||
|
||||
def _get(key: str, default: str = "") -> str:
|
||||
row = session.exec(select(UserSettings).where(UserSettings.cle == key)).first()
|
||||
return row.valeur if row else default
|
||||
|
||||
server = _get("samba_serveur").strip()
|
||||
share = _get("samba_partage").strip()
|
||||
username = _get("samba_utilisateur").strip()
|
||||
password = _get("samba_motdepasse")
|
||||
subfolder = _get("samba_sous_dossier").strip().strip("/\\")
|
||||
|
||||
if not server or not share:
|
||||
raise HTTPException(400, "Configuration Samba incomplète : serveur et partage requis.")
|
||||
|
||||
try:
|
||||
import smbclient # type: ignore
|
||||
except ImportError:
|
||||
raise HTTPException(500, "Module smbprotocol non installé dans l'environnement.")
|
||||
|
||||
tmp_zip, filename = _create_backup_zip()
|
||||
try:
|
||||
smbclient.register_session(server, username=username or None, password=password or None)
|
||||
|
||||
remote_dir = f"\\\\{server}\\{share}"
|
||||
if subfolder:
|
||||
remote_dir = f"{remote_dir}\\{subfolder}"
|
||||
try:
|
||||
smbclient.makedirs(remote_dir, exist_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
remote_path = f"{remote_dir}\\{filename}"
|
||||
|
||||
with open(tmp_zip, "rb") as local_f:
|
||||
data = local_f.read()
|
||||
with smbclient.open_file(remote_path, mode="wb") as smb_f:
|
||||
smb_f.write(data)
|
||||
|
||||
return {"ok": True, "fichier": filename, "chemin": remote_path}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise HTTPException(500, f"Erreur Samba : {exc}") from exc
|
||||
finally:
|
||||
_safe_remove(str(tmp_zip))
|
||||
|
||||
@@ -6,6 +6,8 @@ aiofiles==24.1.0
|
||||
pytest==8.3.3
|
||||
httpx==0.28.0
|
||||
Pillow==11.1.0
|
||||
pillow-heif==0.21.0
|
||||
smbprotocol==1.15.0
|
||||
skyfield==1.49
|
||||
pytz==2025.1
|
||||
numpy==2.2.3
|
||||
|
||||
Reference in New Issue
Block a user