safe move + sqlite WAL + log indexes + backup with logs

- fix: imap folder names with spaces (RFC3501 quoting in move/create/select)
- fix: move only deletes source after COPY + Message-ID verification in target
- fix: backup endpoint hung on sqlite write locks — enable WAL + busy_timeout
- perf: indexes on filter_logs(created_at, level, account_id+created_at) for
  fast log queries on millions of rows
- feat: optional "logs mit sichern" checkbox in backup export, restore on import
- UI: backup download uses fetch+blob with error display instead of location.href

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 19:54:32 +02:00
parent 66b32ded36
commit 7e7ec67e58
9 changed files with 331 additions and 27 deletions
+12 -4
View File
@@ -1,4 +1,6 @@
from fastapi import APIRouter, Depends, UploadFile
from datetime import datetime
from fastapi import APIRouter, Depends, Query, UploadFile
from fastapi.responses import PlainTextResponse, Response
from sqlalchemy.orm import Session
@@ -23,12 +25,18 @@ async def yaml_import(file: UploadFile, db: Session = Depends(get_db)):
@router.get("/backup")
def backup_export(db: Session = Depends(get_db)):
content = export_backup(db)
def backup_export(
include_logs: bool = Query(default=False),
db: Session = Depends(get_db),
):
content = export_backup(db, include_logs=include_logs)
suffix = "-mit-logs" if include_logs else ""
timestamp = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
filename = f"mailfilter-backup{suffix}-{timestamp}.json"
return Response(
content=content,
media_type="application/json",
headers={"Content-Disposition": "attachment; filename=mailfilter-backup.json"},
headers={"Content-Disposition": f"attachment; filename={filename}"},
)