Spaces:
Running
Running
Download app/api/admin_transfer.py from a3216/xtc-backend: direct link, hf CLI and curl.
- Browser
- Download file 5.66 kB
-
https://huggingface.co/spaces/a3216/xtc-backend/resolve/main/app/api/admin_transfer.py
- Command line
-
hf download hf://spaces/a3216/xtc-backend/app/api/admin_transfer.py
-
curl -L -o admin_transfer.py https://huggingface.co/spaces/a3216/xtc-backend/resolve/main/app/api/admin_transfer.py
5.66 kB
| """管理端「文件中转」API:/admin/api/transfer/* | |
| - POST /admin/api/transfer/keys 生成上传密钥(完整密钥仅返回一次) | |
| - GET /admin/api/transfer/keys 列出上传密钥(掩码) | |
| - POST /admin/api/transfer/keys/revoke 按公开短 ID 吊销密钥 | |
| - GET /admin/api/transfer/files 列出中转文件(支持 q / refresh) | |
| - GET /admin/api/transfer/files/{key}/download 下载中转文件 | |
| - DELETE /admin/api/transfer/files/{key} 删除中转文件 | |
| 持久化:内容与元信息落本地缓存 + SQLite + HF Hub(见 services/transfer_store.py), | |
| SQLite 为空(HF Space 重建)时会自动从 Hub 恢复元信息。 | |
| """ | |
| from __future__ import annotations | |
| import asyncio | |
| import logging | |
| import urllib.parse | |
| from fastapi import APIRouter, Depends, Query, Request | |
| from fastapi.responses import Response | |
| from ..errors import HttpError | |
| from ..services import transfer_store | |
| from ._common import CORS_HEADERS, audit_admin, ok_with_cors, read_json_body | |
| from .admin import _require_admin | |
| router = APIRouter(prefix="/admin/api/transfer", tags=["admin-transfer"]) | |
| logger = logging.getLogger(__name__) | |
| # ===== 上传密钥管理 ===== | |
| async def create_transfer_key( | |
| request: Request, | |
| admin_key: str = Depends(_require_admin), | |
| ): | |
| """生成上传密钥(body 可选 {name})。完整密钥仅本次响应返回。""" | |
| body = await read_json_body(request) | |
| name = str(body.get("name") or "").strip() | |
| info = await transfer_store.generate_key(name) | |
| audit_admin("admin.transfer.key.create", admin_key, target=info["id"], name=name) | |
| return ok_with_cors(info) | |
| async def list_transfer_keys(_admin: str = Depends(_require_admin)): | |
| return ok_with_cors({"keys": transfer_store.list_keys()}) | |
| async def revoke_transfer_key( | |
| request: Request, | |
| admin_key: str = Depends(_require_admin), | |
| ): | |
| """吊销上传密钥(body: {id},id 为列表中的公开短 ID)。""" | |
| body = await read_json_body(request) | |
| key_id = str(body.get("id") or "").strip() | |
| if not key_id: | |
| raise HttpError("id is required", status=400, code="bad_request") | |
| revoked = await transfer_store.revoke_key(key_id) | |
| if not revoked: | |
| raise HttpError(f"transfer key not found: {key_id}", status=404, code="not_found") | |
| audit_admin("admin.transfer.key.revoke", admin_key, target=key_id) | |
| return ok_with_cors({"revoked": True, "id": key_id}) | |
| # ===== 中转文件管理 ===== | |
| async def list_transfer_files( | |
| _admin: str = Depends(_require_admin), | |
| limit: int = Query(default=100, ge=1, le=500), | |
| offset: int = Query(default=0, ge=0), | |
| q: str | None = Query(default=None), | |
| refresh: int = Query(default=0, ge=0, le=1), | |
| ): | |
| """列出中转文件。refresh=1 时强制从 Hub 全量恢复元信息(默认按 TTL 自动恢复)。""" | |
| try: | |
| await asyncio.to_thread(transfer_store.sync_from_hub, bool(refresh)) | |
| except Exception as e: | |
| logger.warning("[admin transfer] sync from hub failed: %s", e) | |
| total, items = await asyncio.to_thread( | |
| transfer_store.list_files, q=q, limit=limit, offset=offset | |
| ) | |
| return ok_with_cors({"items": items, "count": total}) | |
| async def download_transfer_file( | |
| key: str, | |
| admin_key: str = Depends(_require_admin), | |
| ): | |
| """管理员下载中转文件内容。""" | |
| meta = transfer_store.get_file(key) | |
| if not meta: | |
| raise HttpError(f"item not found: {key}", status=404, code="not_found") | |
| try: | |
| content = await asyncio.wait_for( | |
| asyncio.to_thread(transfer_store.download_content, key), timeout=30.0 | |
| ) | |
| except asyncio.TimeoutError: | |
| raise HttpError("content fetch timeout", status=504, code="upstream_timeout") | |
| if content is None: | |
| raise HttpError("content missing", status=410, code="gone") | |
| audit_admin( | |
| "admin.transfer.file.download", | |
| admin_key, | |
| target=key, | |
| size=len(content), | |
| ) | |
| alias = meta.get("alias") or meta.get("filename") or "unnamed" | |
| encoded = urllib.parse.quote(alias) | |
| ascii_name = meta.get("filename") or "unnamed" | |
| try: | |
| ascii_name.encode("latin-1") | |
| except (UnicodeEncodeError, UnicodeDecodeError): | |
| ascii_name = key | |
| mime = meta.get("mime") or "application/octet-stream" | |
| headers = { | |
| **CORS_HEADERS, | |
| # 中转文件可能由第三方上传,强制 attachment 避免浏览器内联渲染 | |
| "Content-Disposition": f"attachment; filename=\"{ascii_name}\"; filename*=UTF-8''{encoded}", | |
| "Content-Type": mime, | |
| "Content-Length": str(len(content)), | |
| "X-Content-Type-Options": "nosniff", | |
| "X-XTC-Kind": "transfer", | |
| } | |
| return Response(content=content, status_code=200, headers=headers, media_type=mime) | |
| async def delete_transfer_file( | |
| key: str, | |
| admin_key: str = Depends(_require_admin), | |
| ): | |
| """删除中转文件(SQLite 元信息 + 本地缓存 + Hub)。""" | |
| meta = transfer_store.get_file(key) | |
| if not meta: | |
| raise HttpError(f"item not found: {key}", status=404, code="not_found") | |
| await transfer_store.delete_file(key) | |
| logger.info("[admin transfer] deleted file key=%s alias=%s", key, meta.get("alias")) | |
| audit_admin( | |
| "admin.transfer.file.delete", | |
| admin_key, | |
| target=key, | |
| alias=meta.get("alias"), | |
| size=meta.get("size"), | |
| ) | |
| return ok_with_cors({"deleted": True, "key": key}) | |