Add comprehensive backend features and mobile UI improvements
Backend: - Add 2FA authentication with TOTP support - Add API keys management system - Add audit logging for security events - Add file upload/management system - Add notifications system with preferences - Add session management - Add webhooks integration - Add analytics endpoints - Add export functionality - Add password policy enforcement - Add new database migrations for core tables Frontend: - Add module position system (top/bottom sidebar sections) - Add search and notifications module configuration tabs - Add mobile logo replacing hamburger menu - Center page title absolutely when no tabs present - Align sidebar footer toggles with navigation items - Add lighter icon color in dark theme for mobile - Add API keys management page - Add notifications page with context - Add admin analytics and audit logs pages 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
162
backend/app/api/v1/audit.py
Normal file
162
backend/app/api/v1/audit.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""Audit log API endpoints."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.dependencies import get_db, get_current_superuser
|
||||
from app.models.user import User
|
||||
from app import crud
|
||||
from app.schemas.audit_log import (
|
||||
AuditLog,
|
||||
AuditLogList,
|
||||
AuditLogFilter,
|
||||
AuditLogStats
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=AuditLogList)
|
||||
def get_audit_logs(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
user_id: Optional[str] = None,
|
||||
username: Optional[str] = None,
|
||||
action: Optional[str] = None,
|
||||
resource_type: Optional[str] = None,
|
||||
resource_id: Optional[str] = None,
|
||||
status_filter: Optional[str] = Query(None, alias="status"),
|
||||
start_date: Optional[datetime] = None,
|
||||
end_date: Optional[datetime] = None,
|
||||
):
|
||||
"""
|
||||
Get paginated audit logs with optional filtering.
|
||||
Requires superuser authentication.
|
||||
"""
|
||||
filters = AuditLogFilter(
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
resource_id=resource_id,
|
||||
status=status_filter,
|
||||
start_date=start_date,
|
||||
end_date=end_date
|
||||
)
|
||||
|
||||
skip = (page - 1) * page_size
|
||||
items, total = crud.audit_log.get_multi(
|
||||
db, skip=skip, limit=page_size, filters=filters
|
||||
)
|
||||
|
||||
total_pages = (total + page_size - 1) // page_size
|
||||
|
||||
return AuditLogList(
|
||||
items=items,
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
total_pages=total_pages
|
||||
)
|
||||
|
||||
|
||||
@router.get("/stats", response_model=AuditLogStats)
|
||||
def get_audit_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
):
|
||||
"""
|
||||
Get audit log statistics.
|
||||
Requires superuser authentication.
|
||||
"""
|
||||
return crud.audit_log.get_stats(db)
|
||||
|
||||
|
||||
@router.get("/actions")
|
||||
def get_distinct_actions(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
):
|
||||
"""
|
||||
Get list of distinct action types for filtering.
|
||||
"""
|
||||
return {"actions": crud.audit_log.get_distinct_actions(db)}
|
||||
|
||||
|
||||
@router.get("/resource-types")
|
||||
def get_distinct_resource_types(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
):
|
||||
"""
|
||||
Get list of distinct resource types for filtering.
|
||||
"""
|
||||
return {"resource_types": crud.audit_log.get_distinct_resource_types(db)}
|
||||
|
||||
|
||||
@router.get("/user/{user_id}", response_model=AuditLogList)
|
||||
def get_user_audit_logs(
|
||||
user_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
):
|
||||
"""
|
||||
Get audit logs for a specific user.
|
||||
Requires superuser authentication.
|
||||
"""
|
||||
filters = AuditLogFilter(user_id=user_id)
|
||||
skip = (page - 1) * page_size
|
||||
items, total = crud.audit_log.get_multi(
|
||||
db, skip=skip, limit=page_size, filters=filters
|
||||
)
|
||||
|
||||
total_pages = (total + page_size - 1) // page_size
|
||||
|
||||
return AuditLogList(
|
||||
items=items,
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
total_pages=total_pages
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{log_id}", response_model=AuditLog)
|
||||
def get_audit_log(
|
||||
log_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
):
|
||||
"""
|
||||
Get a specific audit log entry.
|
||||
Requires superuser authentication.
|
||||
"""
|
||||
log = crud.audit_log.get(db, id=log_id)
|
||||
if not log:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Audit log not found"
|
||||
)
|
||||
return log
|
||||
|
||||
|
||||
@router.delete("/cleanup")
|
||||
def cleanup_old_logs(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_superuser),
|
||||
days: int = Query(90, ge=1, le=365),
|
||||
):
|
||||
"""
|
||||
Delete audit logs older than specified days.
|
||||
Requires superuser authentication.
|
||||
Default: 90 days.
|
||||
"""
|
||||
deleted = crud.audit_log.delete_old(db, days=days)
|
||||
return {"deleted": deleted, "days_threshold": days}
|
||||
Reference in New Issue
Block a user