Initial commit — YT Hub

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>
This commit is contained in:
inputnoise
2026-05-25 20:09:04 +02:00
commit 1827dd6c4e
63 changed files with 14480 additions and 0 deletions

81
backend/routers/export.py Normal file
View File

@@ -0,0 +1,81 @@
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"},
)