Squashed 'l2/' content from commit 79caa24
git-subtree-dir: l2 git-subtree-split: 79caa24e2129bf6e2cee819327d5622425306b67
This commit is contained in:
254
app/routers/storage.py
Normal file
254
app/routers/storage.py
Normal file
@@ -0,0 +1,254 @@
|
||||
"""
|
||||
Storage provider routes for L2 server.
|
||||
|
||||
Manages user storage backends.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException, Form
|
||||
from fastapi.responses import HTMLResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from artdag_common import render
|
||||
from artdag_common.middleware import wants_html, wants_json
|
||||
|
||||
from ..config import settings
|
||||
from ..dependencies import get_templates, require_auth, get_user_from_cookie
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
STORAGE_PROVIDERS_INFO = {
|
||||
"pinata": {"name": "Pinata", "desc": "1GB free, IPFS pinning", "color": "blue"},
|
||||
"web3storage": {"name": "web3.storage", "desc": "IPFS + Filecoin", "color": "green"},
|
||||
"nftstorage": {"name": "NFT.Storage", "desc": "Free for NFTs", "color": "pink"},
|
||||
"infura": {"name": "Infura IPFS", "desc": "5GB free", "color": "orange"},
|
||||
"filebase": {"name": "Filebase", "desc": "5GB free, S3+IPFS", "color": "cyan"},
|
||||
"storj": {"name": "Storj", "desc": "25GB free", "color": "indigo"},
|
||||
"local": {"name": "Local Storage", "desc": "Your own disk", "color": "purple"},
|
||||
}
|
||||
|
||||
|
||||
class AddStorageRequest(BaseModel):
|
||||
provider_type: str
|
||||
config: Dict[str, Any]
|
||||
capacity_gb: int = 5
|
||||
provider_name: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_storage(request: Request):
|
||||
"""List user's storage providers."""
|
||||
import db
|
||||
|
||||
username = get_user_from_cookie(request)
|
||||
if not username:
|
||||
if wants_json(request):
|
||||
raise HTTPException(401, "Authentication required")
|
||||
from fastapi.responses import RedirectResponse
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
storages = await db.get_user_storage(username)
|
||||
|
||||
if wants_json(request):
|
||||
return {"storages": storages}
|
||||
|
||||
templates = get_templates(request)
|
||||
return render(templates, "storage/list.html", request,
|
||||
storages=storages,
|
||||
user={"username": username},
|
||||
providers_info=STORAGE_PROVIDERS_INFO,
|
||||
active_tab="storage",
|
||||
)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def add_storage(
|
||||
req: AddStorageRequest,
|
||||
user: dict = Depends(require_auth),
|
||||
):
|
||||
"""Add a storage provider."""
|
||||
import db
|
||||
import storage_providers
|
||||
|
||||
if req.provider_type not in STORAGE_PROVIDERS_INFO:
|
||||
raise HTTPException(400, f"Invalid provider type: {req.provider_type}")
|
||||
|
||||
# Test connection
|
||||
provider = storage_providers.create_provider(req.provider_type, {
|
||||
**req.config,
|
||||
"capacity_gb": req.capacity_gb,
|
||||
})
|
||||
if not provider:
|
||||
raise HTTPException(400, "Failed to create provider")
|
||||
|
||||
success, message = await provider.test_connection()
|
||||
if not success:
|
||||
raise HTTPException(400, f"Connection failed: {message}")
|
||||
|
||||
# Save
|
||||
storage_id = await db.add_user_storage(
|
||||
username=user["username"],
|
||||
provider_type=req.provider_type,
|
||||
provider_name=req.provider_name,
|
||||
config=req.config,
|
||||
capacity_gb=req.capacity_gb,
|
||||
)
|
||||
|
||||
return {"id": storage_id, "message": "Storage provider added"}
|
||||
|
||||
|
||||
@router.post("/add", response_class=HTMLResponse)
|
||||
async def add_storage_form(
|
||||
request: Request,
|
||||
provider_type: str = Form(...),
|
||||
provider_name: Optional[str] = Form(None),
|
||||
capacity_gb: int = Form(5),
|
||||
api_key: Optional[str] = Form(None),
|
||||
secret_key: Optional[str] = Form(None),
|
||||
api_token: Optional[str] = Form(None),
|
||||
project_id: Optional[str] = Form(None),
|
||||
project_secret: Optional[str] = Form(None),
|
||||
access_key: Optional[str] = Form(None),
|
||||
bucket: Optional[str] = Form(None),
|
||||
path: Optional[str] = Form(None),
|
||||
):
|
||||
"""Add storage via HTML form."""
|
||||
import db
|
||||
import storage_providers
|
||||
|
||||
username = get_user_from_cookie(request)
|
||||
if not username:
|
||||
return HTMLResponse('<div class="text-red-400">Not authenticated</div>', status_code=401)
|
||||
|
||||
# Build config
|
||||
config = {}
|
||||
if provider_type == "pinata":
|
||||
if not api_key or not secret_key:
|
||||
return HTMLResponse('<div class="text-red-400">Pinata requires API Key and Secret Key</div>')
|
||||
config = {"api_key": api_key, "secret_key": secret_key}
|
||||
elif provider_type in ["web3storage", "nftstorage"]:
|
||||
if not api_token:
|
||||
return HTMLResponse(f'<div class="text-red-400">{provider_type} requires API Token</div>')
|
||||
config = {"api_token": api_token}
|
||||
elif provider_type == "infura":
|
||||
if not project_id or not project_secret:
|
||||
return HTMLResponse('<div class="text-red-400">Infura requires Project ID and Secret</div>')
|
||||
config = {"project_id": project_id, "project_secret": project_secret}
|
||||
elif provider_type in ["filebase", "storj"]:
|
||||
if not access_key or not secret_key or not bucket:
|
||||
return HTMLResponse('<div class="text-red-400">Requires Access Key, Secret Key, and Bucket</div>')
|
||||
config = {"access_key": access_key, "secret_key": secret_key, "bucket": bucket}
|
||||
elif provider_type == "local":
|
||||
if not path:
|
||||
return HTMLResponse('<div class="text-red-400">Local storage requires a path</div>')
|
||||
config = {"path": path}
|
||||
else:
|
||||
return HTMLResponse(f'<div class="text-red-400">Unknown provider: {provider_type}</div>')
|
||||
|
||||
# Test
|
||||
provider = storage_providers.create_provider(provider_type, {**config, "capacity_gb": capacity_gb})
|
||||
if provider:
|
||||
success, message = await provider.test_connection()
|
||||
if not success:
|
||||
return HTMLResponse(f'<div class="text-red-400">Connection failed: {message}</div>')
|
||||
|
||||
# Save
|
||||
storage_id = await db.add_user_storage(
|
||||
username=username,
|
||||
provider_type=provider_type,
|
||||
provider_name=provider_name,
|
||||
config=config,
|
||||
capacity_gb=capacity_gb,
|
||||
)
|
||||
|
||||
return HTMLResponse(f'''
|
||||
<div class="text-green-400 mb-2">Storage provider added!</div>
|
||||
<script>setTimeout(() => window.location.href = '/storage', 1500);</script>
|
||||
''')
|
||||
|
||||
|
||||
@router.get("/{storage_id}")
|
||||
async def get_storage(
|
||||
storage_id: int,
|
||||
user: dict = Depends(require_auth),
|
||||
):
|
||||
"""Get storage details."""
|
||||
import db
|
||||
|
||||
storage = await db.get_storage_by_id(storage_id)
|
||||
if not storage:
|
||||
raise HTTPException(404, "Storage not found")
|
||||
|
||||
if storage.get("username") != user["username"]:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
|
||||
return storage
|
||||
|
||||
|
||||
@router.delete("/{storage_id}")
|
||||
async def delete_storage(
|
||||
storage_id: int,
|
||||
request: Request,
|
||||
user: dict = Depends(require_auth),
|
||||
):
|
||||
"""Delete a storage provider."""
|
||||
import db
|
||||
|
||||
storage = await db.get_storage_by_id(storage_id)
|
||||
if not storage:
|
||||
raise HTTPException(404, "Storage not found")
|
||||
|
||||
if storage.get("username") != user["username"]:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
|
||||
success = await db.remove_user_storage(storage_id)
|
||||
|
||||
if wants_html(request):
|
||||
return HTMLResponse("")
|
||||
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
@router.post("/{storage_id}/test")
|
||||
async def test_storage(
|
||||
storage_id: int,
|
||||
request: Request,
|
||||
user: dict = Depends(require_auth),
|
||||
):
|
||||
"""Test storage connectivity."""
|
||||
import db
|
||||
import storage_providers
|
||||
import json
|
||||
|
||||
storage = await db.get_storage_by_id(storage_id)
|
||||
if not storage:
|
||||
raise HTTPException(404, "Storage not found")
|
||||
|
||||
if storage.get("username") != user["username"]:
|
||||
raise HTTPException(403, "Not authorized")
|
||||
|
||||
config = storage["config"]
|
||||
if isinstance(config, str):
|
||||
config = json.loads(config)
|
||||
|
||||
provider = storage_providers.create_provider(storage["provider_type"], {
|
||||
**config,
|
||||
"capacity_gb": storage.get("capacity_gb", 5),
|
||||
})
|
||||
|
||||
if not provider:
|
||||
if wants_html(request):
|
||||
return HTMLResponse('<span class="text-red-400">Failed to create provider</span>')
|
||||
return {"success": False, "message": "Failed to create provider"}
|
||||
|
||||
success, message = await provider.test_connection()
|
||||
|
||||
if wants_html(request):
|
||||
color = "green" if success else "red"
|
||||
return HTMLResponse(f'<span class="text-{color}-400">{message}</span>')
|
||||
|
||||
return {"success": success, "message": message}
|
||||
Reference in New Issue
Block a user