Phase 2: Multiple storage configs per type with new UI structure
- Database: Add description field, remove unique constraint to allow
multiple configs of same provider type
- UI: Main page shows provider types as cards with counts
- UI: Per-type page (/storage/type/{type}) for managing configs
- API: Add get_user_storage_by_type() for filtered queries
- Form: Add description field for distinguishing configs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
50
db.py
50
db.py
@@ -118,17 +118,18 @@ CREATE TABLE IF NOT EXISTS revoked_tokens (
|
||||
);
|
||||
|
||||
-- User storage providers (IPFS pinning services, local storage, etc.)
|
||||
-- Users can have multiple configs of the same provider type
|
||||
CREATE TABLE IF NOT EXISTS user_storage (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL REFERENCES users(username),
|
||||
provider_type VARCHAR(50) NOT NULL, -- 'pinata', 'web3storage', 'filebase', 'local'
|
||||
provider_type VARCHAR(50) NOT NULL, -- 'pinata', 'web3storage', 'nftstorage', 'infura', 'filebase', 'storj', 'local'
|
||||
provider_name VARCHAR(255), -- User-friendly name
|
||||
description TEXT, -- User description to distinguish configs
|
||||
config JSONB NOT NULL DEFAULT '{}', -- API keys, endpoints, paths
|
||||
capacity_gb INTEGER NOT NULL, -- Total capacity user is contributing
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(username, provider_type, provider_name)
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Track what's stored where
|
||||
@@ -818,20 +819,33 @@ async def get_user_storage(username: str) -> list[dict]:
|
||||
"""Get all storage providers for a user."""
|
||||
async with get_connection() as conn:
|
||||
rows = await conn.fetch(
|
||||
"""SELECT id, username, provider_type, provider_name, config,
|
||||
"""SELECT id, username, provider_type, provider_name, description, config,
|
||||
capacity_gb, is_active, created_at, updated_at
|
||||
FROM user_storage WHERE username = $1
|
||||
ORDER BY created_at""",
|
||||
ORDER BY provider_type, created_at""",
|
||||
username
|
||||
)
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
async def get_user_storage_by_type(username: str, provider_type: str) -> list[dict]:
|
||||
"""Get storage providers of a specific type for a user."""
|
||||
async with get_connection() as conn:
|
||||
rows = await conn.fetch(
|
||||
"""SELECT id, username, provider_type, provider_name, description, config,
|
||||
capacity_gb, is_active, created_at, updated_at
|
||||
FROM user_storage WHERE username = $1 AND provider_type = $2
|
||||
ORDER BY created_at""",
|
||||
username, provider_type
|
||||
)
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
async def get_storage_by_id(storage_id: int) -> Optional[dict]:
|
||||
"""Get a storage provider by ID."""
|
||||
async with get_connection() as conn:
|
||||
row = await conn.fetchrow(
|
||||
"""SELECT id, username, provider_type, provider_name, config,
|
||||
"""SELECT id, username, provider_type, provider_name, description, config,
|
||||
capacity_gb, is_active, created_at, updated_at
|
||||
FROM user_storage WHERE id = $1""",
|
||||
storage_id
|
||||
@@ -844,16 +858,17 @@ async def add_user_storage(
|
||||
provider_type: str,
|
||||
provider_name: str,
|
||||
config: dict,
|
||||
capacity_gb: int
|
||||
capacity_gb: int,
|
||||
description: Optional[str] = None
|
||||
) -> Optional[int]:
|
||||
"""Add a storage provider for a user. Returns storage ID."""
|
||||
async with get_connection() as conn:
|
||||
try:
|
||||
row = await conn.fetchrow(
|
||||
"""INSERT INTO user_storage (username, provider_type, provider_name, config, capacity_gb)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
"""INSERT INTO user_storage (username, provider_type, provider_name, description, config, capacity_gb)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id""",
|
||||
username, provider_type, provider_name, json.dumps(config), capacity_gb
|
||||
username, provider_type, provider_name, description, json.dumps(config), capacity_gb
|
||||
)
|
||||
return row["id"] if row else None
|
||||
except Exception:
|
||||
@@ -862,6 +877,8 @@ async def add_user_storage(
|
||||
|
||||
async def update_user_storage(
|
||||
storage_id: int,
|
||||
provider_name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
config: Optional[dict] = None,
|
||||
capacity_gb: Optional[int] = None,
|
||||
is_active: Optional[bool] = None
|
||||
@@ -871,6 +888,14 @@ async def update_user_storage(
|
||||
params = []
|
||||
param_num = 1
|
||||
|
||||
if provider_name is not None:
|
||||
updates.append(f"provider_name = ${param_num}")
|
||||
params.append(provider_name)
|
||||
param_num += 1
|
||||
if description is not None:
|
||||
updates.append(f"description = ${param_num}")
|
||||
params.append(description)
|
||||
param_num += 1
|
||||
if config is not None:
|
||||
updates.append(f"config = ${param_num}")
|
||||
params.append(json.dumps(config))
|
||||
@@ -974,14 +999,15 @@ async def get_all_active_storage() -> list[dict]:
|
||||
"""Get all active storage providers (for distributed pinning)."""
|
||||
async with get_connection() as conn:
|
||||
rows = await conn.fetch(
|
||||
"""SELECT us.*,
|
||||
"""SELECT us.id, us.username, us.provider_type, us.provider_name, us.description,
|
||||
us.config, us.capacity_gb, us.is_active, us.created_at, us.updated_at,
|
||||
COALESCE(SUM(sp.size_bytes), 0) as used_bytes,
|
||||
COUNT(sp.id) as pin_count
|
||||
FROM user_storage us
|
||||
LEFT JOIN storage_pins sp ON us.id = sp.storage_id
|
||||
WHERE us.is_active = true
|
||||
GROUP BY us.id
|
||||
ORDER BY us.created_at"""
|
||||
ORDER BY us.provider_type, us.created_at"""
|
||||
)
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user