Files
darkorion 0f239d5015 feat: документация из Nextcloud, график у сотрудника, автоочистка фото, мониторинг, итог мастера, фильтр по местам; доп. сервисы в docker-compose
- docker-compose.yml: профили docs (Nextcloud), office (ONLYOFFICE Document Server), ai (Ollama + скачивание модели)
- автонастройка: адреса Nextcloud, ONLYOFFICE, портала и модели подставляются из .env или по именам сервисов
  во внутренней сети Docker; значения из интерфейса важнее
- локальная модель в своей сети (имя сервиса, частный IP) подключается без ключа API
- .env.example, docs/DEPLOY.md (раздел 7а), README
2026-10-13 13:27:20 +03:00

64 lines
2.6 KiB
Python

"""Самоочистка сервера от старых фото нарядов и заданий.
Фото, которые нужно хранить дольше (у пункта чек-листа или задания отмечено «хранить более года»), с самого начала
кладутся в отдельный каталог keep/ и очисткой не трогаются. Остальные удаляются, когда старше заданного числа дней
(настройка photo_keep_days, 0 — не удалять). Проверка — раз в сутки из планировщика, вручную — из настроек сервера."""
import logging
from datetime import datetime, timedelta
from sqlalchemy import select
from sqlalchemy.orm import Session
from . import config, media, settings_store as ss
from .models import Photo, TaskPhoto
log = logging.getLogger("photo_cleanup")
KEEP = "keep/"
EVERY = timedelta(hours=24)
def days(db: Session) -> int:
try:
return max(0, int(ss.get(db, "photo_keep_days") or 0))
except ValueError:
return 0
def _old(db: Session, model, cut: datetime):
return db.scalars(select(model).where(model.created_at < cut, model.path.not_like(KEEP + "%"))).all()
def run(db: Session, dry: bool = False, now: datetime | None = None) -> dict:
"""Удалить старые фото (dry — только посчитать). Возвращает число фото и освобождённые байты."""
n = days(db)
if n <= 0:
return {"enabled": False, "days": 0, "photos": 0, "bytes": 0}
cut = (now or config.now()) - timedelta(days=n)
root = ss.photo_root(db)
order_ph, task_ph = _old(db, Photo, cut), _old(db, TaskPhoto, cut)
out = {"enabled": True, "days": n, "before": config.iso(cut), "photos": len(order_ph) + len(task_ph),
"bytes": sum(p.size or 0 for p in order_ph + task_ph)}
if dry:
return out
for p in order_ph:
(root / p.path).unlink(missing_ok=True)
db.delete(p)
for p in task_ph:
media.remove(db, "tasks", p.path)
db.delete(p)
ss.set_(db, "photo_cleanup_at", config.now().isoformat())
db.commit()
if out["photos"]:
log.warning("Удалено старых фото: %s (%.1f МБ), старше %s дней", out["photos"], out["bytes"] / 1048576, n)
return out
def due(db: Session) -> bool:
if days(db) <= 0:
return False
at = ss.get(db, "photo_cleanup_at")
try:
return not at or config.now() - datetime.fromisoformat(at) >= EVERY
except ValueError:
return True