mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 02:05:22 -04:00
Odysseus v1.0
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"""Backup routes — export/import user data (memories, presets, settings, skills, preferences)."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, Response
|
||||
from core.middleware import require_admin
|
||||
from src.auth_helpers import get_current_user
|
||||
from src.settings import load_settings, save_settings, load_features, save_features
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_backup_routes(memory_manager, preset_manager, skills_manager) -> APIRouter:
|
||||
router = APIRouter(tags=["backup"])
|
||||
|
||||
@router.get("/api/export")
|
||||
async def export_data(request: Request):
|
||||
"""Export all user data as a downloadable JSON file."""
|
||||
require_admin(request)
|
||||
user = get_current_user(request)
|
||||
|
||||
# Memories (filtered by owner when auth is enabled)
|
||||
memories = memory_manager.load(owner=user)
|
||||
|
||||
# Presets (shared across users — export all)
|
||||
presets = preset_manager.get_all()
|
||||
|
||||
# Skills (filtered by owner when auth is enabled)
|
||||
skills = skills_manager.load(owner=user)
|
||||
|
||||
# Settings
|
||||
settings = load_settings()
|
||||
|
||||
# Feature flags
|
||||
features = load_features()
|
||||
|
||||
# User preferences
|
||||
from routes.prefs_routes import _load_for_user
|
||||
preferences = _load_for_user(user)
|
||||
|
||||
export_data = {
|
||||
"version": 1,
|
||||
"exported_at": datetime.now().isoformat(),
|
||||
"exported_by": user,
|
||||
"memories": memories,
|
||||
"presets": presets,
|
||||
"skills": skills,
|
||||
"settings": settings,
|
||||
"features": features,
|
||||
"preferences": preferences,
|
||||
}
|
||||
|
||||
filename = f"odysseus_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||
return Response(
|
||||
content=json.dumps(export_data, indent=2, ensure_ascii=False),
|
||||
media_type="application/json",
|
||||
headers={"Content-Disposition": f"attachment; filename={filename}"},
|
||||
)
|
||||
|
||||
@router.post("/api/import")
|
||||
async def import_data(request: Request):
|
||||
"""Import user data from a previously exported JSON file. Merges with existing data."""
|
||||
require_admin(request)
|
||||
user = get_current_user(request)
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
raise HTTPException(400, "Invalid JSON")
|
||||
|
||||
if not isinstance(body, dict):
|
||||
raise HTTPException(400, "Expected a JSON object")
|
||||
|
||||
imported = []
|
||||
|
||||
# ── Memories ──
|
||||
if "memories" in body and isinstance(body["memories"], list):
|
||||
existing = memory_manager.load_all()
|
||||
existing_texts = {e.get("text", "").strip().lower() for e in existing}
|
||||
added = 0
|
||||
for mem in body["memories"]:
|
||||
if not isinstance(mem, dict) or not mem.get("text"):
|
||||
continue
|
||||
if mem["text"].strip().lower() in existing_texts:
|
||||
continue # skip duplicates
|
||||
# Assign owner when auth is enabled
|
||||
if user and not mem.get("owner"):
|
||||
mem["owner"] = user
|
||||
existing.append(mem)
|
||||
existing_texts.add(mem["text"].strip().lower())
|
||||
added += 1
|
||||
memory_manager.save(existing)
|
||||
imported.append(f"{added} memories")
|
||||
|
||||
# ── Skills ──
|
||||
if "skills" in body and isinstance(body["skills"], list):
|
||||
existing = skills_manager.load_all()
|
||||
existing_ids = {s.get("id") for s in existing}
|
||||
existing_titles = {s.get("title", "").strip().lower() for s in existing}
|
||||
added = 0
|
||||
for skill in body["skills"]:
|
||||
if not isinstance(skill, dict) or not skill.get("title"):
|
||||
continue
|
||||
# Skip if same id or same title already exists
|
||||
if skill.get("id") in existing_ids:
|
||||
continue
|
||||
if skill["title"].strip().lower() in existing_titles:
|
||||
continue
|
||||
if user and not skill.get("owner"):
|
||||
skill["owner"] = user
|
||||
existing.append(skill)
|
||||
existing_ids.add(skill.get("id"))
|
||||
existing_titles.add(skill["title"].strip().lower())
|
||||
added += 1
|
||||
skills_manager.save(existing)
|
||||
imported.append(f"{added} skills")
|
||||
|
||||
# ── Presets ──
|
||||
if "presets" in body and isinstance(body["presets"], dict):
|
||||
current = preset_manager.get_all()
|
||||
for key, value in body["presets"].items():
|
||||
if isinstance(value, dict):
|
||||
current[key] = value
|
||||
elif isinstance(value, list):
|
||||
current[key] = value
|
||||
preset_manager.save(current)
|
||||
imported.append("presets")
|
||||
|
||||
# ── Settings ──
|
||||
if "settings" in body and isinstance(body["settings"], dict):
|
||||
current = load_settings()
|
||||
current.update(body["settings"])
|
||||
save_settings(current)
|
||||
imported.append("settings")
|
||||
|
||||
# ── Features ──
|
||||
if "features" in body and isinstance(body["features"], dict):
|
||||
current = load_features()
|
||||
current.update(body["features"])
|
||||
save_features(current)
|
||||
imported.append("features")
|
||||
|
||||
# ── Preferences ──
|
||||
if "preferences" in body and isinstance(body["preferences"], dict):
|
||||
from routes.prefs_routes import _load_for_user, _save_for_user
|
||||
current = _load_for_user(user)
|
||||
current.update(body["preferences"])
|
||||
_save_for_user(user, current)
|
||||
imported.append("preferences")
|
||||
|
||||
if not imported:
|
||||
return {"ok": False, "message": "No recognized data found in the file"}
|
||||
|
||||
return {"ok": True, "imported": imported, "message": f"Imported: {', '.join(imported)}"}
|
||||
|
||||
return router
|
||||
Reference in New Issue
Block a user