first commit
Some checks failed
Deploy / lint (push) Failing after 7s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped

This commit is contained in:
Dennis Thiessen
2026-02-20 17:31:01 +01:00
commit 61ab24490d
160 changed files with 17034 additions and 0 deletions

193
app/routers/admin.py Normal file
View File

@@ -0,0 +1,193 @@
"""Admin router: user management, system settings, data cleanup, job control.
All endpoints require admin role.
"""
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db, require_admin
from app.models.user import User
from app.schemas.admin import (
CreateUserRequest,
DataCleanupRequest,
JobToggle,
PasswordReset,
RegistrationToggle,
SystemSettingUpdate,
UserManagement,
)
from app.schemas.common import APIEnvelope
from app.services import admin_service
router = APIRouter(tags=["admin"])
def _user_dict(user: User) -> dict:
return {
"id": user.id,
"username": user.username,
"role": user.role,
"has_access": user.has_access,
"created_at": user.created_at.isoformat() if user.created_at else None,
"updated_at": user.updated_at.isoformat() if user.updated_at else None,
}
# ---------------------------------------------------------------------------
# User management
# ---------------------------------------------------------------------------
@router.get("/admin/users", response_model=APIEnvelope)
async def list_users(
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""List all user accounts."""
users = await admin_service.list_users(db)
return APIEnvelope(status="success", data=[_user_dict(u) for u in users])
@router.post("/admin/users", response_model=APIEnvelope, status_code=201)
async def create_user(
body: CreateUserRequest,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Create a new user account."""
user = await admin_service.create_user(
db, body.username, body.password, body.role, body.has_access
)
return APIEnvelope(status="success", data=_user_dict(user))
@router.put("/admin/users/{user_id}/access", response_model=APIEnvelope)
async def set_user_access(
user_id: int,
body: UserManagement,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Grant or revoke API access for a user."""
user = await admin_service.set_user_access(db, user_id, body.has_access)
return APIEnvelope(status="success", data=_user_dict(user))
@router.put("/admin/users/{user_id}/password", response_model=APIEnvelope)
async def reset_password(
user_id: int,
body: PasswordReset,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Reset a user's password."""
user = await admin_service.reset_password(db, user_id, body.new_password)
return APIEnvelope(status="success", data=_user_dict(user))
# ---------------------------------------------------------------------------
# Registration toggle
# ---------------------------------------------------------------------------
@router.put("/admin/settings/registration", response_model=APIEnvelope)
async def toggle_registration(
body: RegistrationToggle,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Enable or disable user registration."""
setting = await admin_service.toggle_registration(db, body.enabled)
return APIEnvelope(
status="success",
data={"key": setting.key, "value": setting.value},
)
# ---------------------------------------------------------------------------
# System settings
# ---------------------------------------------------------------------------
@router.get("/admin/settings", response_model=APIEnvelope)
async def list_settings(
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""List all system settings."""
settings_list = await admin_service.list_settings(db)
return APIEnvelope(
status="success",
data=[
{"key": s.key, "value": s.value, "updated_at": s.updated_at.isoformat() if s.updated_at else None}
for s in settings_list
],
)
@router.put("/admin/settings/{key}", response_model=APIEnvelope)
async def update_setting(
key: str,
body: SystemSettingUpdate,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Create or update a system setting."""
setting = await admin_service.update_setting(db, key, body.value)
return APIEnvelope(
status="success",
data={"key": setting.key, "value": setting.value, "updated_at": setting.updated_at.isoformat() if setting.updated_at else None},
)
# ---------------------------------------------------------------------------
# Data cleanup
# ---------------------------------------------------------------------------
@router.post("/admin/data/cleanup", response_model=APIEnvelope)
async def cleanup_data(
body: DataCleanupRequest,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Delete OHLCV, sentiment, and fundamental data older than N days."""
counts = await admin_service.cleanup_data(db, body.older_than_days)
return APIEnvelope(status="success", data=counts)
# ---------------------------------------------------------------------------
# Job control
# ---------------------------------------------------------------------------
@router.get("/admin/jobs", response_model=APIEnvelope)
async def list_jobs(
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""List all scheduled jobs with their current status."""
jobs = await admin_service.list_jobs(db)
return APIEnvelope(status="success", data=jobs)
@router.post("/admin/jobs/{job_name}/trigger", response_model=APIEnvelope)
async def trigger_job(
job_name: str,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Trigger a manual job run (placeholder)."""
result = await admin_service.trigger_job(db, job_name)
return APIEnvelope(status="success", data=result)
@router.put("/admin/jobs/{job_name}/toggle", response_model=APIEnvelope)
async def toggle_job(
job_name: str,
body: JobToggle,
_admin: User = Depends(require_admin),
db: AsyncSession = Depends(get_db),
):
"""Enable or disable a scheduled job (placeholder)."""
setting = await admin_service.toggle_job(db, job_name, body.enabled)
return APIEnvelope(
status="success",
data={"key": setting.key, "value": setting.value},
)