Complete L1 router and template migration
- Full implementation of runs, recipes, cache routers with templates - Auth and storage routers fully migrated - Jinja2 templates for all L1 pages - Service layer for auth and storage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,32 +4,280 @@ Storage provider routes for L1 server.
|
||||
Manages user storage backends (Pinata, web3.storage, local, etc.)
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from artdag_common.middleware import UserContext, wants_html
|
||||
from fastapi import APIRouter, Request, Depends, HTTPException, Form
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..dependencies import require_auth, get_templates
|
||||
from artdag_common import render
|
||||
from artdag_common.middleware import wants_html, wants_json
|
||||
|
||||
from ..dependencies import get_database, get_current_user, require_auth, get_templates
|
||||
from ..services.auth_service import UserContext
|
||||
from ..services.storage_service import StorageService, STORAGE_PROVIDERS_INFO, VALID_PROVIDER_TYPES
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# TODO: Migrate routes from server.py lines 5473-5761
|
||||
# - GET /storage - List storage providers
|
||||
# - POST /storage - Add storage provider
|
||||
# - POST /storage/add - Add via form
|
||||
# - GET /storage/{storage_id} - Get storage details
|
||||
# - PATCH /storage/{storage_id} - Update storage
|
||||
# - DELETE /storage/{storage_id} - Delete storage
|
||||
# - POST /storage/{storage_id}/test - Test connection
|
||||
# - GET /storage/type/{provider_type} - Get provider config
|
||||
# Import storage_providers module
|
||||
import storage_providers as sp_module
|
||||
|
||||
|
||||
def get_storage_service():
|
||||
"""Get storage service instance."""
|
||||
import database
|
||||
return StorageService(database, sp_module)
|
||||
|
||||
|
||||
class AddStorageRequest(BaseModel):
|
||||
provider_type: str
|
||||
config: Dict[str, Any]
|
||||
capacity_gb: int = 5
|
||||
provider_name: Optional[str] = None
|
||||
|
||||
|
||||
class UpdateStorageRequest(BaseModel):
|
||||
config: Optional[Dict[str, Any]] = None
|
||||
capacity_gb: Optional[int] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def list_storage(
|
||||
request: Request,
|
||||
user: UserContext = Depends(require_auth),
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""List user's storage providers."""
|
||||
# TODO: Implement
|
||||
raise HTTPException(501, "Not yet migrated")
|
||||
"""List user's storage providers. HTML for browsers, JSON for API."""
|
||||
from ..services.auth_service import AuthService
|
||||
from ..dependencies import get_redis_client
|
||||
|
||||
auth_service = AuthService(get_redis_client())
|
||||
ctx = auth_service.get_user_from_cookie(request)
|
||||
|
||||
if not ctx:
|
||||
if wants_json(request):
|
||||
raise HTTPException(401, "Authentication required")
|
||||
return RedirectResponse(url="/auth", status_code=302)
|
||||
|
||||
storages = await storage_service.list_storages(ctx.actor_id)
|
||||
|
||||
if wants_json(request):
|
||||
return {"storages": storages}
|
||||
|
||||
# Render HTML template
|
||||
templates = get_templates(request)
|
||||
return render(templates, "storage/list.html", request,
|
||||
storages=storages,
|
||||
user=ctx,
|
||||
providers_info=STORAGE_PROVIDERS_INFO,
|
||||
)
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def add_storage(
|
||||
req: AddStorageRequest,
|
||||
request: Request,
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""Add a storage provider via API."""
|
||||
ctx = await require_auth(request)
|
||||
|
||||
storage_id, error = await storage_service.add_storage(
|
||||
actor_id=ctx.actor_id,
|
||||
provider_type=req.provider_type,
|
||||
config=req.config,
|
||||
capacity_gb=req.capacity_gb,
|
||||
provider_name=req.provider_name,
|
||||
)
|
||||
|
||||
if error:
|
||||
raise HTTPException(400, error)
|
||||
|
||||
return {"id": storage_id, "message": "Storage provider added"}
|
||||
|
||||
|
||||
@router.post("/add")
|
||||
async def add_storage_form(
|
||||
request: Request,
|
||||
provider_type: str = Form(...),
|
||||
provider_name: Optional[str] = Form(None),
|
||||
description: 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),
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""Add a storage provider via HTML form."""
|
||||
from ..services.auth_service import AuthService
|
||||
from ..dependencies import get_redis_client
|
||||
|
||||
auth_service = AuthService(get_redis_client())
|
||||
ctx = auth_service.get_user_from_cookie(request)
|
||||
|
||||
if not ctx:
|
||||
return HTMLResponse('<div class="text-red-400">Not authenticated</div>', status_code=401)
|
||||
|
||||
# Build config from form
|
||||
form_data = {
|
||||
"api_key": api_key,
|
||||
"secret_key": secret_key,
|
||||
"api_token": api_token,
|
||||
"project_id": project_id,
|
||||
"project_secret": project_secret,
|
||||
"access_key": access_key,
|
||||
"bucket": bucket,
|
||||
"path": path,
|
||||
}
|
||||
config, error = storage_service.build_config_from_form(provider_type, form_data)
|
||||
|
||||
if error:
|
||||
return HTMLResponse(f'<div class="text-red-400">{error}</div>')
|
||||
|
||||
storage_id, error = await storage_service.add_storage(
|
||||
actor_id=ctx.actor_id,
|
||||
provider_type=provider_type,
|
||||
config=config,
|
||||
capacity_gb=capacity_gb,
|
||||
provider_name=provider_name,
|
||||
description=description,
|
||||
)
|
||||
|
||||
if error:
|
||||
return HTMLResponse(f'<div class="text-red-400">{error}</div>')
|
||||
|
||||
return HTMLResponse(f'''
|
||||
<div class="text-green-400 mb-2">Storage provider added successfully!</div>
|
||||
<script>setTimeout(() => window.location.href = '/storage/type/{provider_type}', 1500);</script>
|
||||
''')
|
||||
|
||||
|
||||
@router.get("/{storage_id}")
|
||||
async def get_storage(
|
||||
storage_id: int,
|
||||
request: Request,
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""Get a specific storage provider."""
|
||||
ctx = await require_auth(request)
|
||||
|
||||
storage = await storage_service.get_storage(storage_id, ctx.actor_id)
|
||||
if not storage:
|
||||
raise HTTPException(404, "Storage provider not found")
|
||||
|
||||
return storage
|
||||
|
||||
|
||||
@router.patch("/{storage_id}")
|
||||
async def update_storage(
|
||||
storage_id: int,
|
||||
req: UpdateStorageRequest,
|
||||
request: Request,
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""Update a storage provider."""
|
||||
ctx = await require_auth(request)
|
||||
|
||||
success, error = await storage_service.update_storage(
|
||||
storage_id=storage_id,
|
||||
actor_id=ctx.actor_id,
|
||||
config=req.config,
|
||||
capacity_gb=req.capacity_gb,
|
||||
is_active=req.is_active,
|
||||
)
|
||||
|
||||
if error:
|
||||
raise HTTPException(400, error)
|
||||
|
||||
return {"message": "Storage provider updated"}
|
||||
|
||||
|
||||
@router.delete("/{storage_id}")
|
||||
async def delete_storage(
|
||||
storage_id: int,
|
||||
request: Request,
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""Remove a storage provider."""
|
||||
from ..services.auth_service import AuthService
|
||||
from ..dependencies import get_redis_client
|
||||
|
||||
auth_service = AuthService(get_redis_client())
|
||||
ctx = auth_service.get_user_from_cookie(request)
|
||||
|
||||
if not ctx:
|
||||
raise HTTPException(401, "Not authenticated")
|
||||
|
||||
success, error = await storage_service.delete_storage(storage_id, ctx.actor_id)
|
||||
|
||||
if error:
|
||||
raise HTTPException(400, error)
|
||||
|
||||
if wants_html(request):
|
||||
return HTMLResponse("")
|
||||
|
||||
return {"message": "Storage provider removed"}
|
||||
|
||||
|
||||
@router.post("/{storage_id}/test")
|
||||
async def test_storage(
|
||||
storage_id: int,
|
||||
request: Request,
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""Test storage provider connectivity."""
|
||||
from ..services.auth_service import AuthService
|
||||
from ..dependencies import get_redis_client
|
||||
|
||||
auth_service = AuthService(get_redis_client())
|
||||
ctx = auth_service.get_user_from_cookie(request)
|
||||
|
||||
if not ctx:
|
||||
if wants_html(request):
|
||||
return HTMLResponse('<span class="text-red-400">Not authenticated</span>', status_code=401)
|
||||
raise HTTPException(401, "Not authenticated")
|
||||
|
||||
success, message = await storage_service.test_storage(storage_id, ctx.actor_id)
|
||||
|
||||
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}
|
||||
|
||||
|
||||
@router.get("/type/{provider_type}")
|
||||
async def storage_type_page(
|
||||
provider_type: str,
|
||||
request: Request,
|
||||
storage_service: StorageService = Depends(get_storage_service),
|
||||
):
|
||||
"""Page for managing storage configs of a specific type."""
|
||||
from ..services.auth_service import AuthService
|
||||
from ..dependencies import get_redis_client
|
||||
|
||||
auth_service = AuthService(get_redis_client())
|
||||
ctx = auth_service.get_user_from_cookie(request)
|
||||
|
||||
if not ctx:
|
||||
return RedirectResponse(url="/auth", status_code=302)
|
||||
|
||||
if provider_type not in STORAGE_PROVIDERS_INFO:
|
||||
raise HTTPException(404, "Invalid provider type")
|
||||
|
||||
storages = await storage_service.list_by_type(ctx.actor_id, provider_type)
|
||||
provider_info = STORAGE_PROVIDERS_INFO[provider_type]
|
||||
|
||||
templates = get_templates(request)
|
||||
return render(templates, "storage/type.html", request,
|
||||
provider_type=provider_type,
|
||||
provider_info=provider_info,
|
||||
storages=storages,
|
||||
user=ctx,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user