Self-hosted personal YouTube management app. FastAPI + SQLite backend, React + Vite + Tailwind frontend. Dockerfiles and compose included for Portainer deployment. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
from datetime import datetime
|
|
from fastapi import APIRouter, Depends
|
|
from fastapi.responses import JSONResponse
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import text
|
|
|
|
from ..auth_utils import get_current_user
|
|
from ..database import get_db
|
|
from ..models import User
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("")
|
|
def export_data(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
uid = current_user.id
|
|
|
|
watch_history = db.execute(text("""
|
|
SELECT v.youtube_video_id, v.title, c.name AS channel_name,
|
|
uv.watch_progress_seconds, uv.last_watched_at
|
|
FROM user_videos uv
|
|
JOIN videos v ON v.id = uv.video_id
|
|
LEFT JOIN channels c ON c.id = v.channel_id
|
|
WHERE uv.user_id = :uid AND uv.watched = 1
|
|
ORDER BY uv.last_watched_at DESC
|
|
"""), {"uid": uid}).mappings().all()
|
|
|
|
ratings = db.execute(text("""
|
|
SELECT v.youtube_video_id, v.title, c.name AS channel_name, uv.rating
|
|
FROM user_videos uv
|
|
JOIN videos v ON v.id = uv.video_id
|
|
LEFT JOIN channels c ON c.id = v.channel_id
|
|
WHERE uv.user_id = :uid AND uv.rating IS NOT NULL
|
|
ORDER BY v.title
|
|
"""), {"uid": uid}).mappings().all()
|
|
|
|
liked = db.execute(text("""
|
|
SELECT v.youtube_video_id, v.title, c.name AS channel_name, uv.liked_at
|
|
FROM user_videos uv
|
|
JOIN videos v ON v.id = uv.video_id
|
|
LEFT JOIN channels c ON c.id = v.channel_id
|
|
WHERE uv.user_id = :uid AND uv.liked = 1
|
|
ORDER BY uv.liked_at DESC
|
|
"""), {"uid": uid}).mappings().all()
|
|
|
|
bookmarks = db.execute(text("""
|
|
SELECT v.youtube_video_id, v.title, c.name AS channel_name,
|
|
vb.timestamp_seconds, vb.note, vb.created_at
|
|
FROM video_bookmarks vb
|
|
JOIN videos v ON v.id = vb.video_id
|
|
LEFT JOIN channels c ON c.id = v.channel_id
|
|
WHERE vb.user_id = :uid
|
|
ORDER BY vb.created_at DESC
|
|
"""), {"uid": uid}).mappings().all()
|
|
|
|
queue = db.execute(text("""
|
|
SELECT v.youtube_video_id, v.title, c.name AS channel_name
|
|
FROM user_videos uv
|
|
JOIN videos v ON v.id = uv.video_id
|
|
LEFT JOIN channels c ON c.id = v.channel_id
|
|
WHERE uv.user_id = :uid AND uv.queued = 1
|
|
ORDER BY v.title
|
|
"""), {"uid": uid}).mappings().all()
|
|
|
|
payload = {
|
|
"exported_at": datetime.utcnow().isoformat(),
|
|
"username": current_user.username,
|
|
"watch_history": [dict(r) for r in watch_history],
|
|
"ratings": [dict(r) for r in ratings],
|
|
"liked": [dict(r) for r in liked],
|
|
"bookmarks": [dict(r) for r in bookmarks],
|
|
"queue": [dict(r) for r in queue],
|
|
}
|
|
|
|
return JSONResponse(
|
|
content=payload,
|
|
headers={"Content-Disposition": f"attachment; filename=ythub-export-{datetime.utcnow().strftime('%Y%m%d')}.json"},
|
|
)
|