Files
app-service/backend/app/crud/audit_log.py
matteoscrugli 8c4a555b88 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>
2025-12-17 22:27:32 +01:00

229 lines
7.4 KiB
Python

"""CRUD operations for Audit Log model."""
import json
from datetime import datetime, timedelta
from typing import Optional, List, Any
from sqlalchemy.orm import Session
from sqlalchemy import func, desc
from app.models.audit_log import AuditLog
from app.schemas.audit_log import AuditLogCreate, AuditLogFilter
class CRUDAuditLog:
"""CRUD operations for Audit Log model."""
def create(
self,
db: Session,
*,
obj_in: AuditLogCreate
) -> AuditLog:
"""Create a new audit log entry."""
db_obj = AuditLog(
user_id=obj_in.user_id,
username=obj_in.username,
action=obj_in.action,
resource_type=obj_in.resource_type,
resource_id=obj_in.resource_id,
details=obj_in.details,
ip_address=obj_in.ip_address,
user_agent=obj_in.user_agent,
status=obj_in.status
)
db.add(db_obj)
db.commit()
db.refresh(db_obj)
return db_obj
def log_action(
self,
db: Session,
*,
user_id: Optional[str] = None,
username: Optional[str] = None,
action: str,
resource_type: Optional[str] = None,
resource_id: Optional[str] = None,
details: Optional[dict] = None,
ip_address: Optional[str] = None,
user_agent: Optional[str] = None,
status: str = "success"
) -> AuditLog:
"""Convenience method to log an action."""
details_str = json.dumps(details) if details else None
obj_in = AuditLogCreate(
user_id=user_id,
username=username,
action=action,
resource_type=resource_type,
resource_id=resource_id,
details=details_str,
ip_address=ip_address,
user_agent=user_agent,
status=status
)
return self.create(db, obj_in=obj_in)
def get(self, db: Session, id: str) -> Optional[AuditLog]:
"""Get a single audit log entry by ID."""
return db.query(AuditLog).filter(AuditLog.id == id).first()
def get_multi(
self,
db: Session,
*,
skip: int = 0,
limit: int = 100,
filters: Optional[AuditLogFilter] = None
) -> tuple[List[AuditLog], int]:
"""Get multiple audit log entries with optional filtering."""
query = db.query(AuditLog)
if filters:
if filters.user_id:
query = query.filter(AuditLog.user_id == filters.user_id)
if filters.username:
query = query.filter(AuditLog.username.ilike(f"%{filters.username}%"))
if filters.action:
query = query.filter(AuditLog.action == filters.action)
if filters.resource_type:
query = query.filter(AuditLog.resource_type == filters.resource_type)
if filters.resource_id:
query = query.filter(AuditLog.resource_id == filters.resource_id)
if filters.status:
query = query.filter(AuditLog.status == filters.status)
if filters.start_date:
query = query.filter(AuditLog.created_at >= filters.start_date)
if filters.end_date:
query = query.filter(AuditLog.created_at <= filters.end_date)
total = query.count()
items = query.order_by(desc(AuditLog.created_at)).offset(skip).limit(limit).all()
return items, total
def get_by_user(
self,
db: Session,
*,
user_id: str,
skip: int = 0,
limit: int = 100
) -> List[AuditLog]:
"""Get audit logs for a specific user."""
return db.query(AuditLog)\
.filter(AuditLog.user_id == user_id)\
.order_by(desc(AuditLog.created_at))\
.offset(skip)\
.limit(limit)\
.all()
def get_recent(
self,
db: Session,
*,
hours: int = 24,
limit: int = 100
) -> List[AuditLog]:
"""Get recent audit logs within specified hours."""
since = datetime.utcnow() - timedelta(hours=hours)
return db.query(AuditLog)\
.filter(AuditLog.created_at >= since)\
.order_by(desc(AuditLog.created_at))\
.limit(limit)\
.all()
def get_stats(self, db: Session) -> dict[str, Any]:
"""Get audit log statistics."""
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
week_start = today_start - timedelta(days=today_start.weekday())
month_start = today_start.replace(day=1)
# Total entries
total = db.query(func.count(AuditLog.id)).scalar()
# Entries today
entries_today = db.query(func.count(AuditLog.id))\
.filter(AuditLog.created_at >= today_start)\
.scalar()
# Entries this week
entries_week = db.query(func.count(AuditLog.id))\
.filter(AuditLog.created_at >= week_start)\
.scalar()
# Entries this month
entries_month = db.query(func.count(AuditLog.id))\
.filter(AuditLog.created_at >= month_start)\
.scalar()
# Actions breakdown
actions_query = db.query(
AuditLog.action,
func.count(AuditLog.id).label('count')
).group_by(AuditLog.action).all()
actions_breakdown = {action: count for action, count in actions_query}
# Top users (by action count)
top_users_query = db.query(
AuditLog.user_id,
AuditLog.username,
func.count(AuditLog.id).label('count')
).filter(AuditLog.user_id.isnot(None))\
.group_by(AuditLog.user_id, AuditLog.username)\
.order_by(desc('count'))\
.limit(10)\
.all()
top_users = [
{"user_id": uid, "username": uname, "count": count}
for uid, uname, count in top_users_query
]
# Recent failures (last 24h)
recent_failures = db.query(func.count(AuditLog.id))\
.filter(AuditLog.status == "failure")\
.filter(AuditLog.created_at >= today_start - timedelta(days=1))\
.scalar()
return {
"total_entries": total or 0,
"entries_today": entries_today or 0,
"entries_this_week": entries_week or 0,
"entries_this_month": entries_month or 0,
"actions_breakdown": actions_breakdown,
"top_users": top_users,
"recent_failures": recent_failures or 0
}
def delete_old(
self,
db: Session,
*,
days: int = 90
) -> int:
"""Delete audit logs older than specified days."""
cutoff = datetime.utcnow() - timedelta(days=days)
count = db.query(AuditLog)\
.filter(AuditLog.created_at < cutoff)\
.delete()
db.commit()
return count
def get_distinct_actions(self, db: Session) -> List[str]:
"""Get list of distinct action types."""
result = db.query(AuditLog.action).distinct().all()
return [r[0] for r in result]
def get_distinct_resource_types(self, db: Session) -> List[str]:
"""Get list of distinct resource types."""
result = db.query(AuditLog.resource_type)\
.filter(AuditLog.resource_type.isnot(None))\
.distinct().all()
return [r[0] for r in result]
# Create instance
audit_log = CRUDAuditLog()