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:
2025-12-17 22:27:32 +01:00
parent f698aa4d51
commit 8c4a555b88
76 changed files with 9751 additions and 323 deletions

View File

@@ -0,0 +1,244 @@
"""Notification API endpoints."""
import json
from typing import Any, Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from app.dependencies import get_db, get_current_user, get_current_superuser
from app.models.user import User
from app import crud
from app.schemas.notification import (
Notification,
NotificationCreate,
NotificationCreateForUser,
NotificationList,
NotificationStats,
NotificationBulkAction
)
router = APIRouter()
def serialize_notification(db_obj) -> dict:
"""Serialize notification for response."""
metadata = None
if db_obj.metadata:
try:
metadata = json.loads(db_obj.metadata)
except json.JSONDecodeError:
metadata = None
return {
"id": db_obj.id,
"user_id": db_obj.user_id,
"title": db_obj.title,
"message": db_obj.message,
"type": db_obj.type,
"link": db_obj.link,
"metadata": metadata,
"is_read": db_obj.is_read,
"created_at": db_obj.created_at,
"read_at": db_obj.read_at
}
@router.get("", response_model=NotificationList)
def get_notifications(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
unread_only: bool = False
) -> Any:
"""
Get notifications for the current user.
"""
notifications = crud.notification.get_multi_by_user(
db,
user_id=current_user.id,
skip=skip,
limit=limit,
unread_only=unread_only
)
total = crud.notification.count_by_user(db, current_user.id)
unread_count = crud.notification.count_unread_by_user(db, current_user.id)
return {
"items": [serialize_notification(n) for n in notifications],
"total": total,
"unread_count": unread_count
}
@router.get("/unread-count")
def get_unread_count(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Get unread notification count for the current user.
"""
count = crud.notification.count_unread_by_user(db, current_user.id)
return {"unread_count": count}
@router.get("/stats", response_model=NotificationStats)
def get_notification_stats(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Get notification statistics for the current user.
"""
return crud.notification.get_stats_by_user(db, current_user.id)
@router.get("/{notification_id}", response_model=Notification)
def get_notification(
notification_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Get a specific notification.
"""
db_obj = crud.notification.get(db, id=notification_id)
if not db_obj or db_obj.user_id != current_user.id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Notification not found"
)
return serialize_notification(db_obj)
@router.post("/{notification_id}/read", response_model=Notification)
def mark_as_read(
notification_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Mark a notification as read.
"""
db_obj = crud.notification.mark_as_read(
db, id=notification_id, user_id=current_user.id
)
if not db_obj:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Notification not found"
)
return serialize_notification(db_obj)
@router.post("/read-all")
def mark_all_as_read(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Mark all notifications as read.
"""
count = crud.notification.mark_all_as_read(db, user_id=current_user.id)
return {"marked_as_read": count}
@router.post("/read-multiple")
def mark_multiple_as_read(
action: NotificationBulkAction,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Mark multiple notifications as read.
"""
count = crud.notification.mark_multiple_as_read(
db,
user_id=current_user.id,
notification_ids=action.notification_ids
)
return {"marked_as_read": count}
@router.delete("/{notification_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_notification(
notification_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> None:
"""
Delete a notification.
"""
if not crud.notification.delete(db, id=notification_id, user_id=current_user.id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Notification not found"
)
@router.delete("/read/all")
def delete_all_read(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Delete all read notifications.
"""
count = crud.notification.delete_all_read(db, user_id=current_user.id)
return {"deleted": count}
@router.post("/delete-multiple")
def delete_multiple(
action: NotificationBulkAction,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
) -> Any:
"""
Delete multiple notifications.
"""
count = crud.notification.delete_multiple(
db,
user_id=current_user.id,
notification_ids=action.notification_ids
)
return {"deleted": count}
# Admin endpoints
@router.post("/admin/send", status_code=status.HTTP_201_CREATED)
def send_notification_to_user(
notification_in: NotificationCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
) -> Any:
"""
Send a notification to a specific user (admin only).
"""
db_obj = crud.notification.create(db, obj_in=notification_in)
return serialize_notification(db_obj)
@router.post("/admin/broadcast")
def broadcast_notification(
notification_in: NotificationCreateForUser,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
) -> Any:
"""
Send a notification to all users (admin only).
"""
count = crud.notification.create_for_all_users(
db,
title=notification_in.title,
message=notification_in.message,
type=notification_in.type,
link=notification_in.link,
metadata=notification_in.metadata
)
return {"sent_to": count}