feat: implement full admin panel with dashboard, user management, and platform settings
Adds complete super_admin panel with 9 pages and account owner categories page. Backend includes 5 new DB tables, ~25 API endpoints, settings manager with in-memory cache, and 29 integration tests. Frontend includes reusable admin components (DataTable, Pagination, ActionMenu, etc.) with code-split lazy loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
208
backend/app/schemas/admin.py
Normal file
208
backend/app/schemas/admin.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""Pydantic schemas for admin panel endpoints."""
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# --- Dashboard ---
|
||||
|
||||
class DashboardMetrics(BaseModel):
|
||||
total_users: int
|
||||
active_subscriptions: int
|
||||
paid_accounts: int
|
||||
total_trees: int
|
||||
|
||||
|
||||
class ActivityEntry(BaseModel):
|
||||
id: UUID
|
||||
user_email: Optional[str] = None
|
||||
action: str
|
||||
resource_type: str
|
||||
resource_id: Optional[UUID] = None
|
||||
details: Optional[dict] = None
|
||||
ip_address: Optional[str] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# --- Audit Logs ---
|
||||
|
||||
class AuditLogEntry(BaseModel):
|
||||
id: UUID
|
||||
user_id: UUID
|
||||
user_email: Optional[str] = None
|
||||
action: str
|
||||
resource_type: str
|
||||
resource_id: Optional[UUID] = None
|
||||
details: Optional[dict] = None
|
||||
ip_address: Optional[str] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class AuditLogListResponse(BaseModel):
|
||||
items: list[AuditLogEntry]
|
||||
total: int
|
||||
page: int
|
||||
per_page: int
|
||||
|
||||
|
||||
# --- Plan Limits ---
|
||||
|
||||
class PlanLimitResponse(BaseModel):
|
||||
plan: str
|
||||
max_trees: Optional[int] = None
|
||||
max_sessions_per_month: Optional[int] = None
|
||||
max_users: Optional[int] = None
|
||||
custom_branding: bool = False
|
||||
priority_support: bool = False
|
||||
export_formats: list = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PlanLimitUpdate(BaseModel):
|
||||
plan: str
|
||||
max_trees: Optional[int] = None
|
||||
max_sessions_per_month: Optional[int] = None
|
||||
max_users: Optional[int] = None
|
||||
custom_branding: bool = False
|
||||
priority_support: bool = False
|
||||
export_formats: list = Field(default_factory=lambda: ["markdown", "text"])
|
||||
|
||||
|
||||
class AccountOverrideCreate(BaseModel):
|
||||
account_display_code: str = Field(..., description="Account display code to look up")
|
||||
override_max_trees: Optional[int] = None
|
||||
override_max_sessions_per_month: Optional[int] = None
|
||||
override_max_users: Optional[int] = None
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
class AccountOverrideUpdate(BaseModel):
|
||||
override_max_trees: Optional[int] = None
|
||||
override_max_sessions_per_month: Optional[int] = None
|
||||
override_max_users: Optional[int] = None
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
class AccountOverrideResponse(BaseModel):
|
||||
id: UUID
|
||||
account_id: UUID
|
||||
account_name: Optional[str] = None
|
||||
account_display_code: Optional[str] = None
|
||||
override_max_trees: Optional[int] = None
|
||||
override_max_sessions_per_month: Optional[int] = None
|
||||
override_max_users: Optional[int] = None
|
||||
note: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# --- Feature Flags ---
|
||||
|
||||
class FeatureFlagCreate(BaseModel):
|
||||
flag_key: str = Field(..., max_length=100)
|
||||
display_name: str = Field(..., max_length=255)
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class FeatureFlagUpdate(BaseModel):
|
||||
display_name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class PlanDefaultEntry(BaseModel):
|
||||
plan: str
|
||||
enabled: bool
|
||||
|
||||
|
||||
class FeatureFlagResponse(BaseModel):
|
||||
id: UUID
|
||||
flag_key: str
|
||||
display_name: str
|
||||
description: Optional[str] = None
|
||||
plan_defaults: list[PlanDefaultEntry] = []
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class PlanDefaultUpdate(BaseModel):
|
||||
plan: str
|
||||
flag_id: UUID
|
||||
enabled: bool
|
||||
|
||||
|
||||
class AccountFeatureOverrideCreate(BaseModel):
|
||||
account_display_code: str
|
||||
flag_id: UUID
|
||||
enabled: bool
|
||||
note: Optional[str] = None
|
||||
|
||||
|
||||
class AccountFeatureOverrideResponse(BaseModel):
|
||||
id: UUID
|
||||
account_id: UUID
|
||||
account_display_code: Optional[str] = None
|
||||
flag_id: UUID
|
||||
flag_key: Optional[str] = None
|
||||
flag_display_name: Optional[str] = None
|
||||
enabled: bool
|
||||
note: Optional[str] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# --- Platform Settings ---
|
||||
|
||||
class SettingsResponse(BaseModel):
|
||||
settings: dict
|
||||
|
||||
|
||||
class SettingsUpdate(BaseModel):
|
||||
settings: dict = Field(..., description="Key-value pairs to update")
|
||||
|
||||
|
||||
# --- Global Categories ---
|
||||
|
||||
class GlobalCategoryCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
slug: str = Field(..., min_length=1, max_length=100)
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class GlobalCategoryUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, max_length=100)
|
||||
slug: Optional[str] = Field(None, max_length=100)
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class GlobalCategoryResponse(BaseModel):
|
||||
id: UUID
|
||||
name: str
|
||||
slug: str
|
||||
description: Optional[str] = None
|
||||
account_id: Optional[UUID] = None
|
||||
tree_count: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# --- Move User ---
|
||||
|
||||
class MoveUserAccount(BaseModel):
|
||||
display_code: str = Field(..., description="Target account display code")
|
||||
Reference in New Issue
Block a user