feat(backup): add database backup/restore with scheduled retention and admin dashboard

- Add BackupRecord model for tracking backup archives
- Add migration 021_add_backup_records
- Add backup configuration settings (backup_enabled, backup_dir, backup_remote_destination, backup_remote_folder, backup_retain_hourly/daily/weekly)
- Add backup_tasks.py with create_backup, cleanup_old_backups, and helpers
- Register hourly/daily/weekly Celery beat schedules
- Add /api/admin/backup/* REST endpoints (list, create, download, restore, delete, cleanup)
- Add /admin/backup dashboard view and template
- Add backup link to admin dropdown navigation in base.html
- Add backup settings to SETTING_METADATA in settings_service.py
- Add comprehensive test suite (39 tests passing)

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-07 22:03:27 +00:00
parent f4a607df05
commit 2dd1ca0197
13 changed files with 1935 additions and 0 deletions
+2
View File
@@ -5,6 +5,7 @@ Aggregated view routers for the application.
from fastapi import APIRouter
from app.views.admin_users import router as admin_users_router
from app.views.backup import router as backup_router
from app.views.db_wizard import router as db_wizard_router
from app.views.dropbox import router as dropbox_router
from app.views.filemanager import router as filemanager_router
@@ -30,6 +31,7 @@ router = APIRouter()
router.include_router(wizard_router) # Wizard first (for /setup)
router.include_router(db_wizard_router) # Database wizard
router.include_router(admin_users_router) # Admin user management
router.include_router(backup_router) # Backup dashboard
router.include_router(general_router)
router.include_router(status_router)
router.include_router(onedrive_router)
+58
View File
@@ -0,0 +1,58 @@
"""
Backup management dashboard view admin only.
"""
import logging
from fastapi import Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from app.config import settings
from app.models import BackupRecord
from app.views.base import APIRouter, get_db, require_login, templates
from app.views.settings import require_admin_access
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/admin/backup")
@require_login
@require_admin_access
async def backup_dashboard(request: Request, db: Session = Depends(get_db)):
"""Backup management dashboard admin only."""
try:
records = db.query(BackupRecord).order_by(BackupRecord.created_at.desc()).limit(500).all()
# Summarise counts per tier
counts: dict[str, int] = {"hourly": 0, "daily": 0, "weekly": 0}
for r in records:
if r.backup_type in counts:
counts[r.backup_type] += 1
# Compute total local size
import os
total_size = sum(r.size_bytes for r in records if r.local_path and os.path.exists(r.local_path))
return templates.TemplateResponse(
"backup.html",
{
"request": request,
"records": records,
"counts": counts,
"total_size": total_size,
"backup_enabled": getattr(settings, "backup_enabled", True),
"backup_remote_destination": getattr(settings, "backup_remote_destination", None),
"backup_retain_hourly": getattr(settings, "backup_retain_hourly", 96),
"backup_retain_daily": getattr(settings, "backup_retain_daily", 21),
"backup_retain_weekly": getattr(settings, "backup_retain_weekly", 13),
"app_version": settings.version,
},
)
except Exception as e:
logger.error(f"Error loading backup dashboard: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to load backup dashboard",
)