143 lines
5.6 KiB
Python
143 lines
5.6 KiB
Python
"""Admin management routes."""
|
|
import json
|
|
import os
|
|
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func, or_
|
|
from app.database import get_db
|
|
from app.models import Video, DownloadLog, AppSetting
|
|
from app.schemas import (
|
|
VideoInfo, VideoListResponse, StorageStats,
|
|
DownloadLogInfo, DownloadLogListResponse,
|
|
CleanupConfig, CleanupStatus, DiskStats,
|
|
)
|
|
from app.auth import get_current_user
|
|
from app.services.cleanup import get_setting, set_setting, disk_stats, run_cleanup
|
|
|
|
router = APIRouter(prefix="/api/admin", tags=["admin"])
|
|
|
|
|
|
def human_size(size_bytes: int) -> str:
|
|
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
if size_bytes < 1024:
|
|
return f"{size_bytes:.1f} {unit}"
|
|
size_bytes /= 1024
|
|
return f"{size_bytes:.1f} PB"
|
|
|
|
|
|
@router.get("/videos", response_model=VideoListResponse)
|
|
async def list_videos(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(20, ge=1, le=100),
|
|
search: str = Query("", max_length=200),
|
|
status: str = Query("", max_length=20),
|
|
user: dict = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
query = select(Video).order_by(Video.created_at.desc())
|
|
count_query = select(func.count(Video.id))
|
|
|
|
if search:
|
|
flt = or_(Video.title.contains(search), Video.url.contains(search))
|
|
query = query.where(flt)
|
|
count_query = count_query.where(flt)
|
|
if status:
|
|
query = query.where(Video.status == status)
|
|
count_query = count_query.where(Video.status == status)
|
|
|
|
total = (await db.execute(count_query)).scalar() or 0
|
|
videos = (await db.execute(query.offset((page - 1) * page_size).limit(page_size))).scalars().all()
|
|
|
|
return VideoListResponse(
|
|
videos=[VideoInfo.model_validate(v) for v in videos],
|
|
total=total,
|
|
page=page,
|
|
page_size=page_size,
|
|
)
|
|
|
|
|
|
@router.delete("/videos/{video_id}")
|
|
async def delete_video(video_id: int, user: dict = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
|
video = (await db.execute(select(Video).where(Video.id == video_id))).scalar_one_or_none()
|
|
if not video:
|
|
raise HTTPException(status_code=404, detail="Video not found")
|
|
# Delete file from disk
|
|
if video.file_path and os.path.exists(video.file_path):
|
|
os.remove(video.file_path)
|
|
await db.delete(video)
|
|
await db.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/stats", response_model=StorageStats)
|
|
async def storage_stats(user: dict = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
|
total = (await db.execute(select(func.count(Video.id)).where(Video.status == "done"))).scalar() or 0
|
|
total_size = (await db.execute(select(func.sum(Video.file_size)).where(Video.status == "done"))).scalar() or 0
|
|
return StorageStats(total_videos=total, total_size=total_size, total_size_human=human_size(total_size))
|
|
|
|
|
|
@router.get("/download-logs", response_model=DownloadLogListResponse)
|
|
async def download_logs(
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(50, ge=1, le=200),
|
|
video_id: int = Query(None),
|
|
user: dict = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
from sqlalchemy.orm import joinedload
|
|
query = (
|
|
select(DownloadLog)
|
|
.options(joinedload(DownloadLog.video))
|
|
.order_by(DownloadLog.downloaded_at.desc())
|
|
)
|
|
count_query = select(func.count(DownloadLog.id))
|
|
if video_id is not None:
|
|
query = query.where(DownloadLog.video_id == video_id)
|
|
count_query = count_query.where(DownloadLog.video_id == video_id)
|
|
total = (await db.execute(count_query)).scalar() or 0
|
|
logs = (await db.execute(query.offset((page - 1) * page_size).limit(page_size))).scalars().all()
|
|
|
|
items = []
|
|
for l in logs:
|
|
d = DownloadLogInfo.model_validate(l)
|
|
if l.video:
|
|
d.video_title = l.video.title or ""
|
|
d.video_platform = l.video.platform or ""
|
|
items.append(d)
|
|
|
|
return DownloadLogListResponse(logs=items, total=total, page=page, page_size=page_size)
|
|
|
|
|
|
@router.get("/settings/cleanup", response_model=CleanupStatus)
|
|
async def get_cleanup_settings(user: dict = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
|
video_base = os.getenv("VIDEO_BASE_PATH", "/home/xdl/xdl_videos")
|
|
last_result_raw = await get_setting(db, "cleanup_last_result", "{}")
|
|
try:
|
|
last_result = json.loads(last_result_raw) if last_result_raw else {}
|
|
except Exception:
|
|
last_result = {}
|
|
return CleanupStatus(
|
|
config=CleanupConfig(
|
|
enabled=(await get_setting(db, "cleanup_enabled", "true")) == "true",
|
|
retention_minutes=int(await get_setting(db, "cleanup_retention_minutes", "10080")),
|
|
storage_limit_pct=int(await get_setting(db, "cleanup_storage_limit_pct", "80")),
|
|
),
|
|
disk=DiskStats(**disk_stats(video_base)),
|
|
last_run=await get_setting(db, "cleanup_last_run", ""),
|
|
last_result=last_result,
|
|
)
|
|
|
|
|
|
@router.put("/settings/cleanup", response_model=CleanupStatus)
|
|
async def update_cleanup_settings(cfg: CleanupConfig, user: dict = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
|
await set_setting(db, "cleanup_enabled", "true" if cfg.enabled else "false")
|
|
await set_setting(db, "cleanup_retention_minutes", str(cfg.retention_minutes))
|
|
await set_setting(db, "cleanup_storage_limit_pct", str(cfg.storage_limit_pct))
|
|
return await get_cleanup_settings(user=user, db=db)
|
|
|
|
|
|
@router.post("/cleanup/run")
|
|
async def trigger_cleanup(user: dict = Depends(get_current_user)):
|
|
result = await run_cleanup()
|
|
return result
|