Fix item visibility bugs and add effects web UI

- Fix recipe filter to allow owner=None (S-expression compiled recipes)
- Fix media uploads to use category (video/image/audio) not MIME type
- Fix IPFS imports to detect and store correct media type
- Add Effects navigation link between Recipes and Media
- Create effects list and detail templates with upload functionality
- Add cache/not_found.html template (was missing)
- Add type annotations to service classes
- Add tests for item visibility and effects web UI (30 tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-12 12:01:54 +00:00
parent 19e2277155
commit 585c75e846
12 changed files with 1090 additions and 53 deletions

View File

@@ -3,7 +3,11 @@ Storage Service - business logic for storage provider management.
"""
import json
from typing import Optional, List, Dict, Any
from typing import Optional, List, Dict, Any, Tuple, TYPE_CHECKING
if TYPE_CHECKING:
from database import Database
from storage_providers import StorageProvidersModule
STORAGE_PROVIDERS_INFO = {
@@ -22,7 +26,7 @@ VALID_PROVIDER_TYPES = list(STORAGE_PROVIDERS_INFO.keys())
class StorageService:
"""Service for managing user storage providers."""
def __init__(self, database, storage_providers_module):
def __init__(self, database: "Database", storage_providers_module: "StorageProvidersModule") -> None:
self.db = database
self.providers = storage_providers_module
@@ -72,7 +76,7 @@ class StorageService:
capacity_gb: int = 5,
provider_name: Optional[str] = None,
description: Optional[str] = None,
) -> tuple[Optional[int], Optional[str]]:
) -> Tuple[Optional[int], Optional[str]]:
"""Add a new storage provider. Returns (storage_id, error_message)."""
if provider_type not in VALID_PROVIDER_TYPES:
return None, f"Invalid provider type: {provider_type}"
@@ -115,7 +119,7 @@ class StorageService:
config: Optional[Dict[str, Any]] = None,
capacity_gb: Optional[int] = None,
is_active: Optional[bool] = None,
) -> tuple[bool, Optional[str]]:
) -> Tuple[bool, Optional[str]]:
"""Update a storage provider. Returns (success, error_message)."""
storage = await self.db.get_storage_by_id(storage_id)
if not storage:
@@ -145,7 +149,7 @@ class StorageService:
return success, None if success else "Failed to update storage provider"
async def delete_storage(self, storage_id: int, actor_id: str) -> tuple[bool, Optional[str]]:
async def delete_storage(self, storage_id: int, actor_id: str) -> Tuple[bool, Optional[str]]:
"""Delete a storage provider. Returns (success, error_message)."""
storage = await self.db.get_storage_by_id(storage_id)
if not storage:
@@ -156,7 +160,7 @@ class StorageService:
success = await self.db.remove_user_storage(storage_id)
return success, None if success else "Failed to remove storage provider"
async def test_storage(self, storage_id: int, actor_id: str) -> tuple[bool, str]:
async def test_storage(self, storage_id: int, actor_id: str) -> Tuple[bool, str]:
"""Test storage provider connectivity. Returns (success, message)."""
storage = await self.db.get_storage_by_id(storage_id)
if not storage:
@@ -179,7 +183,7 @@ class StorageService:
"""List storage providers of a specific type."""
return await self.db.get_user_storage_by_type(actor_id, provider_type)
def build_config_from_form(self, provider_type: str, form_data: Dict[str, Any]) -> tuple[Optional[Dict], Optional[str]]:
def build_config_from_form(self, provider_type: str, form_data: Dict[str, Any]) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
"""Build provider config from form data. Returns (config, error)."""
api_key = form_data.get("api_key")
secret_key = form_data.get("secret_key")