aorus
@@ -8,9 +8,10 @@ from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from starlette.background import BackgroundTask
|
||||
from sqlalchemy import text
|
||||
from sqlmodel import Session, select
|
||||
from app.database import get_session
|
||||
from app.models.settings import UserSettings
|
||||
@@ -283,6 +284,185 @@ def download_backup_zip() -> FileResponse:
|
||||
)
|
||||
|
||||
|
||||
def _merge_db_add_only(backup_db_path: Path, current_db_path: Path) -> dict[str, int]:
|
||||
"""Insère dans la BDD courante les lignes absentes de la BDD de sauvegarde (INSERT OR IGNORE)."""
|
||||
import sqlite3
|
||||
|
||||
stats = {"rows_added": 0, "rows_skipped": 0}
|
||||
backup_conn = sqlite3.connect(str(backup_db_path))
|
||||
current_conn = sqlite3.connect(str(current_db_path))
|
||||
current_conn.execute("PRAGMA foreign_keys=OFF")
|
||||
|
||||
try:
|
||||
tables = backup_conn.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
||||
).fetchall()
|
||||
|
||||
for (table,) in tables:
|
||||
try:
|
||||
cur = backup_conn.execute(f'SELECT * FROM "{table}"')
|
||||
cols = [d[0] for d in cur.description]
|
||||
rows = cur.fetchall()
|
||||
if not rows:
|
||||
continue
|
||||
col_names = ", ".join(f'"{c}"' for c in cols)
|
||||
placeholders = ", ".join(["?"] * len(cols))
|
||||
before = current_conn.execute(f'SELECT COUNT(*) FROM "{table}"').fetchone()[0]
|
||||
current_conn.executemany(
|
||||
f'INSERT OR IGNORE INTO "{table}" ({col_names}) VALUES ({placeholders})',
|
||||
rows,
|
||||
)
|
||||
after = current_conn.execute(f'SELECT COUNT(*) FROM "{table}"').fetchone()[0]
|
||||
added = after - before
|
||||
stats["rows_added"] += added
|
||||
stats["rows_skipped"] += len(rows) - added
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
current_conn.commit()
|
||||
finally:
|
||||
backup_conn.close()
|
||||
current_conn.close()
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
@router.post("/settings/backup/restore")
|
||||
async def restore_backup(
|
||||
file: UploadFile = File(...),
|
||||
overwrite: bool = Form(default=True),
|
||||
) -> dict[str, Any]:
|
||||
"""Restaure une sauvegarde ZIP (DB + uploads). overwrite=true écrase, false ajoute uniquement."""
|
||||
import shutil
|
||||
|
||||
db_path = _resolve_sqlite_db_path()
|
||||
uploads_dir = Path(UPLOAD_DIR).resolve()
|
||||
|
||||
data = await file.read()
|
||||
if len(data) < 4 or data[:2] != b'PK':
|
||||
raise HTTPException(400, "Le fichier n'est pas une archive ZIP valide.")
|
||||
|
||||
fd, tmp_zip_path = tempfile.mkstemp(suffix=".zip")
|
||||
os.close(fd)
|
||||
tmp_zip = Path(tmp_zip_path)
|
||||
tmp_extract = Path(tempfile.mkdtemp(prefix="jardin_restore_"))
|
||||
|
||||
try:
|
||||
tmp_zip.write_bytes(data)
|
||||
|
||||
with zipfile.ZipFile(tmp_zip, "r") as zipf:
|
||||
zipf.extractall(str(tmp_extract))
|
||||
|
||||
stats: dict[str, Any] = {
|
||||
"uploads_copies": 0,
|
||||
"uploads_ignores": 0,
|
||||
"db_restauree": False,
|
||||
"db_lignes_ajoutees": 0,
|
||||
"erreurs": 0,
|
||||
}
|
||||
|
||||
# --- Uploads ---
|
||||
backup_uploads = tmp_extract / "uploads"
|
||||
if backup_uploads.is_dir():
|
||||
uploads_dir.mkdir(parents=True, exist_ok=True)
|
||||
for src in backup_uploads.rglob("*"):
|
||||
if not src.is_file():
|
||||
continue
|
||||
dst = uploads_dir / src.relative_to(backup_uploads)
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
if overwrite or not dst.exists():
|
||||
try:
|
||||
shutil.copy2(str(src), str(dst))
|
||||
stats["uploads_copies"] += 1
|
||||
except Exception:
|
||||
stats["erreurs"] += 1
|
||||
else:
|
||||
stats["uploads_ignores"] += 1
|
||||
|
||||
# --- Base de données ---
|
||||
backup_db_dir = tmp_extract / "db"
|
||||
db_files = sorted(backup_db_dir.glob("*.db")) if backup_db_dir.is_dir() else []
|
||||
|
||||
if db_files and db_path:
|
||||
backup_db_file = db_files[0]
|
||||
|
||||
if overwrite:
|
||||
from app.database import engine
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)"))
|
||||
except Exception:
|
||||
pass
|
||||
engine.dispose()
|
||||
shutil.copy2(str(backup_db_file), str(db_path))
|
||||
stats["db_restauree"] = True
|
||||
else:
|
||||
merge = _merge_db_add_only(backup_db_file, db_path)
|
||||
stats["db_lignes_ajoutees"] = merge["rows_added"]
|
||||
stats["db_restauree"] = True
|
||||
|
||||
return {"ok": True, **stats}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise HTTPException(500, f"Erreur lors de la restauration : {exc}") from exc
|
||||
finally:
|
||||
_safe_remove(str(tmp_zip))
|
||||
shutil.rmtree(str(tmp_extract), ignore_errors=True)
|
||||
|
||||
|
||||
@router.post("/settings/images/resize-all")
|
||||
def resize_all_images(session: Session = Depends(get_session)) -> dict[str, Any]:
|
||||
"""Redimensionne les images pleine taille de la bibliothèque dont la largeur dépasse le paramètre configuré."""
|
||||
from PIL import Image
|
||||
import io as _io
|
||||
|
||||
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:
|
||||
return {"ok": True, "redimensionnees": 0, "ignorees": 0, "erreurs": 0,
|
||||
"message": "Taille originale configurée — aucune modification."}
|
||||
|
||||
from app.models.media import Media as MediaModel
|
||||
urls = session.exec(select(MediaModel.url)).all()
|
||||
|
||||
uploads_dir = Path(UPLOAD_DIR).resolve()
|
||||
redimensionnees = 0
|
||||
ignorees = 0
|
||||
erreurs = 0
|
||||
|
||||
for url in urls:
|
||||
if not url:
|
||||
continue
|
||||
# /uploads/filename.webp → data/uploads/filename.webp
|
||||
filename = url.lstrip("/").removeprefix("uploads/")
|
||||
file_path = uploads_dir / filename
|
||||
if not file_path.is_file():
|
||||
ignorees += 1
|
||||
continue
|
||||
try:
|
||||
with Image.open(file_path) as img:
|
||||
w, h = img.size
|
||||
if w <= max_px and h <= max_px:
|
||||
ignorees += 1
|
||||
continue
|
||||
img_copy = img.copy()
|
||||
img_copy.thumbnail((max_px, max_px), Image.LANCZOS)
|
||||
img_copy.save(file_path, "WEBP", quality=85)
|
||||
redimensionnees += 1
|
||||
except Exception:
|
||||
erreurs += 1
|
||||
|
||||
return {"ok": True, "redimensionnees": redimensionnees, "ignorees": ignorees, "erreurs": erreurs}
|
||||
|
||||
|
||||
@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."""
|
||||
|
||||
BIN
data/jardin.db
BIN
data/jardin.db-shm
Executable file
0
data/jardin.db-wal
Executable file
BIN
data/uploads/006595e2-598a-477a-a8ab-8b5c40630fda.webp
Executable file
|
After Width: | Height: | Size: 13 KiB |
0
data/uploads/01c05aed-27a4-4902-b99d-9a1a27957b91.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 3.0 MiB |
BIN
data/uploads/02e37d30-acc0-486c-9512-3f7b421e1eb2.webp
Executable file
|
After Width: | Height: | Size: 307 KiB |
BIN
data/uploads/0594f290-baf1-432e-9f38-ce6705b66283.webp
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
data/uploads/0bb04cc6-9de8-4976-9f61-9edc389d5874.webp
Executable file
|
After Width: | Height: | Size: 287 KiB |
BIN
data/uploads/0bb04cc6-9de8-4976-9f61-9edc389d5874_thumb.webp
Executable file
|
After Width: | Height: | Size: 17 KiB |
0
data/uploads/0cf928a2-4e1e-4f80-b808-aa066ce3f174.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
0
data/uploads/119cd18a-50a6-40df-8c72-b5711c6ea7f2.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
0
data/uploads/14cb5845-82fb-412e-82e8-16eeed8e920b.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.1 MiB |
BIN
data/uploads/17fc4089-d703-4365-b77c-ac9d4ce0734e.webp
Executable file
|
After Width: | Height: | Size: 75 KiB |
BIN
data/uploads/17fc4089-d703-4365-b77c-ac9d4ce0734e_thumb.webp
Executable file
|
After Width: | Height: | Size: 7.7 KiB |
0
data/uploads/185159c5-d396-4d1a-aa5b-9c2a6c6267d6.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.0 MiB After Width: | Height: | Size: 2.0 MiB |
BIN
data/uploads/2057b56f-96a4-4f39-bb9a-24b94e1144e3.webp
Executable file
|
After Width: | Height: | Size: 185 KiB |
BIN
data/uploads/21e17e36-fac4-4f8f-8217-50ed14d70ff4.webp
Executable file
|
After Width: | Height: | Size: 143 KiB |
BIN
data/uploads/22dcb011-58e2-4bcc-a77a-2ab9ea406f50.webp
Executable file
|
After Width: | Height: | Size: 198 KiB |
BIN
data/uploads/24e6659c-2a5b-49b4-9e4b-ea06f572a1c6.webp
Executable file
|
After Width: | Height: | Size: 14 KiB |
0
data/uploads/2749da16-ebef-4df0-b03f-0905468b1a08.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 44 B After Width: | Height: | Size: 44 B |
0
data/uploads/2826ce60-929c-447b-82cd-d95166d1a364.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
BIN
data/uploads/287e9fb3-71eb-40c5-9631-d3742569e094.webp
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
data/uploads/2d932f30-68b6-4a31-93b8-811086f25832.webp
Executable file
|
After Width: | Height: | Size: 166 KiB |
0
data/uploads/2e670eec-bf29-4c9e-8634-87dcea54274e.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
BIN
data/uploads/2ec78e11-8298-482d-95c5-42176e1340d1.webp
Executable file
|
After Width: | Height: | Size: 243 KiB |
BIN
data/uploads/3401b4e5-725e-4d69-830f-ff7a19f35843.webp
Executable file
|
After Width: | Height: | Size: 215 KiB |
BIN
data/uploads/34bd385f-c486-4e98-9516-02bf13321b14.webp
Executable file
|
After Width: | Height: | Size: 13 KiB |
BIN
data/uploads/37356c53-a717-4b01-849b-82ed70f32178.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/uploads/39eb57a4-0511-4171-aa81-e8dbcd2e4970.webp
Executable file
|
After Width: | Height: | Size: 165 KiB |
BIN
data/uploads/3f03ea48-1e92-4b3d-8097-a500d3398761.webp
Executable file
|
After Width: | Height: | Size: 144 KiB |
0
data/uploads/456fc305-c22b-4a69-bcb5-bc418ed7d982.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
BIN
data/uploads/465c4d68-5a9a-435b-ba76-01c7875bf633.webp
Executable file
|
After Width: | Height: | Size: 136 KiB |
BIN
data/uploads/47e3e09e-33dd-415b-9c29-552077029847.webp
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
data/uploads/48faa297-69eb-4313-ba65-8fe1888e170a.webp
Executable file
|
After Width: | Height: | Size: 14 KiB |
0
data/uploads/4b00ab47-1577-476b-9e83-cdc3cc96af0c.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
BIN
data/uploads/4fe5db07-5931-495d-ac81-a24920b77d7e.webp
Executable file
|
After Width: | Height: | Size: 170 KiB |
0
data/uploads/53ac30ee-108e-42f2-b61b-cd810dcbeeb5.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.8 MiB After Width: | Height: | Size: 3.8 MiB |
BIN
data/uploads/54838cd4-129d-46a9-b65b-63ba4cbf9a08.webp
Executable file
|
After Width: | Height: | Size: 16 KiB |
BIN
data/uploads/5557d295-43c4-4c54-9b8a-23679714909a.webp
Executable file
|
After Width: | Height: | Size: 107 KiB |
BIN
data/uploads/5bb8bb1b-1984-480b-a45c-55d00f05e673.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/uploads/66243d53-8abd-450e-94f4-f9b41f217eb4.webp
Executable file
|
After Width: | Height: | Size: 109 KiB |
BIN
data/uploads/67092250-20d1-4e37-b7cd-82398e830f22.webp
Executable file
|
After Width: | Height: | Size: 307 KiB |
BIN
data/uploads/67b7fb68-f19f-45c1-b977-25460b229a06.webp
Executable file
|
After Width: | Height: | Size: 207 KiB |
BIN
data/uploads/68d899e0-2267-4ed0-becb-c725aac5e8a9.webp
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
data/uploads/6b9c2839-12bd-4b4b-92c9-9dfc43e0457f.webp
Executable file
|
After Width: | Height: | Size: 374 KiB |
BIN
data/uploads/6b9c2839-12bd-4b4b-92c9-9dfc43e0457f_thumb.webp
Executable file
|
After Width: | Height: | Size: 26 KiB |
BIN
data/uploads/6eefdfd2-2bea-40a8-83e7-9d5d85eea8c0.webp
Executable file
|
After Width: | Height: | Size: 196 KiB |
0
data/uploads/72bc5fc7-a3eb-49e9-8bb1-4e0f54d5e4c2.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.7 MiB After Width: | Height: | Size: 2.7 MiB |
BIN
data/uploads/742d288a-1b63-409e-a38e-cbfbcf6ffae2.webp
Executable file
|
After Width: | Height: | Size: 88 KiB |
BIN
data/uploads/742d288a-1b63-409e-a38e-cbfbcf6ffae2_thumb.webp
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
data/uploads/7937782f-506a-4dc7-83e1-cf06ca7d85bd.webp
Executable file
|
After Width: | Height: | Size: 243 KiB |
BIN
data/uploads/79dabcfd-f06c-444d-82d3-7a87a7a47350.webp
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
data/uploads/7a2a0a4a-1cba-466c-b617-354015aaa5ef.webp
Executable file
|
After Width: | Height: | Size: 175 KiB |
0
data/uploads/7f71435a-a28c-4ff1-91f9-fbf0a442b6b0.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 44 B After Width: | Height: | Size: 44 B |
0
data/uploads/8913cdc5-363a-4b00-8af0-1fc62eede9f6.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 44 B After Width: | Height: | Size: 44 B |
BIN
data/uploads/89d33489-0bf8-47ab-962e-890760060c46.webp
Executable file
|
After Width: | Height: | Size: 158 KiB |
BIN
data/uploads/89d6aa47-4fd0-4cd5-8603-83526d4e610e.webp
Executable file
|
After Width: | Height: | Size: 328 KiB |
0
data/uploads/8a7d347c-b3b1-42c5-b1ab-8699ee52e87d.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 44 B After Width: | Height: | Size: 44 B |
0
data/uploads/8c108c64-f513-427b-bd16-a7b8fb99245e.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 3.4 MiB |
BIN
data/uploads/8f74ad74-7766-4634-a328-72075941848d.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/uploads/914ff0a3-7a22-4715-8fd6-6163bb0daf1c.webp
Executable file
|
After Width: | Height: | Size: 17 KiB |
0
data/uploads/92819523-a2dc-476f-8770-f8615d4a5cfe.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
BIN
data/uploads/92b9d91a-bc62-417c-9fc5-68c9a62efed6.webp
Executable file
|
After Width: | Height: | Size: 17 KiB |
0
data/uploads/945c2aa1-896d-4979-bb3c-2a74571b3050.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 3.4 MiB |
BIN
data/uploads/96a4bb6a-65d9-44ca-a386-2a193f7a28f3.webp
Executable file
|
After Width: | Height: | Size: 236 KiB |
BIN
data/uploads/96c19097-4112-4959-a2eb-e23034c6709b.webp
Executable file
|
After Width: | Height: | Size: 13 KiB |
BIN
data/uploads/9dc47fc5-aedb-47a0-aae5-ed33d2210344.webp
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
data/uploads/9f886067-a30f-48b1-ac5b-cb6b14bdfe19.webp
Executable file
|
After Width: | Height: | Size: 163 KiB |
0
data/uploads/a00759fa-ecee-472e-bfa6-a9b6f90d4c84.webp
Normal file → Executable file
|
Before Width: | Height: | Size: 44 B After Width: | Height: | Size: 44 B |
BIN
data/uploads/a3233d90-58e7-411b-835f-cd590a79776e.webp
Executable file
|
After Width: | Height: | Size: 192 KiB |
BIN
data/uploads/a7ea2222-3ffd-4e58-b392-a0ab1cd7b822.webp
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
data/uploads/a8146cd1-e7fd-422a-99ba-4e4bbbe1d6b1.webp
Executable file
|
After Width: | Height: | Size: 14 KiB |
0
data/uploads/a8641f29-f7ab-465c-a739-a1a3367f6628.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 3.0 MiB |
0
data/uploads/a9ebb40f-5402-4409-9d0a-5de42eacfba6.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.8 MiB After Width: | Height: | Size: 4.8 MiB |
BIN
data/uploads/aa287f48-2a29-4fa1-8ef9-a3e87f807247.webp
Executable file
|
After Width: | Height: | Size: 84 KiB |
BIN
data/uploads/aa287f48-2a29-4fa1-8ef9-a3e87f807247_thumb.webp
Executable file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
data/uploads/aae89675-bfeb-4770-bd9b-b41bdede15f1.webp
Executable file
|
After Width: | Height: | Size: 144 KiB |
0
data/uploads/accdf642-36b5-4d9c-b9be-465ea65afb17.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 3.3 MiB |
BIN
data/uploads/ace2dec0-754d-4b45-9597-24b299941747.webp
Executable file
|
After Width: | Height: | Size: 171 KiB |
BIN
data/uploads/ad89ff74-8793-433c-b720-55bc97dc6485.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/uploads/adaeda9f-7642-4e18-a571-3f743bcfbef0.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/uploads/ae795fd1-b820-4f8a-a69e-f056316820d9.webp
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
data/uploads/af9b7e6c-8647-42f5-a293-182e83265480.webp
Executable file
|
After Width: | Height: | Size: 19 KiB |
0
data/uploads/b2b098f7-1c09-410c-a440-e95de836bd5f.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.7 MiB After Width: | Height: | Size: 3.7 MiB |
BIN
data/uploads/b4e401a4-45e8-44a0-8575-ded9e04a1139.webp
Executable file
|
After Width: | Height: | Size: 20 KiB |
0
data/uploads/b5320f20-2d59-47e2-9c92-b48878890bb9.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.5 MiB After Width: | Height: | Size: 3.5 MiB |
BIN
data/uploads/b8d43c9c-634b-4cec-826d-ab31b6ed4d18.webp
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
data/uploads/b98e69ab-bd97-4e72-8159-bb0bac305b8a.webp
Executable file
|
After Width: | Height: | Size: 118 KiB |
0
data/uploads/bc3fe54e-b894-4ca8-8be8-1b065afaf918.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
0
data/uploads/be350e09-2e7e-4c22-ab3e-c2f248a750cd.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 2.8 MiB |
BIN
data/uploads/be60038d-a28d-469b-9b3a-d64da494a681.webp
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
data/uploads/bea5abe4-cc15-480c-83a1-02df1228d013.webp
Executable file
|
After Width: | Height: | Size: 20 KiB |
0
data/uploads/bfb03b30-ef05-4710-ab46-0c4cde2f7366.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.1 MiB After Width: | Height: | Size: 3.1 MiB |
0
data/uploads/bfdf7863-16e5-4151-95aa-087d0aa920bd.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.3 MiB |
0
data/uploads/c1414164-b276-43c5-ab08-229baef76c2b.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 2.9 MiB |
0
data/uploads/c14d8348-815f-4ebf-9ea1-bef89d122324.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 3.3 MiB |
0
data/uploads/c1e27615-b167-4513-910b-e3fa2017fe42.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 4.3 MiB After Width: | Height: | Size: 4.3 MiB |
0
data/uploads/c2d2c0bc-3994-431a-8e33-2a8e42903040.jpg
Normal file → Executable file
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
BIN
data/uploads/c44d7174-dd5a-4de4-8692-e977b01c98b1.webp
Executable file
|
After Width: | Height: | Size: 20 KiB |