255 lines
8.0 KiB
Python
255 lines
8.0 KiB
Python
"""
|
|
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}
|