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:
244
backend/app/api/v1/notifications.py
Normal file
244
backend/app/api/v1/notifications.py
Normal 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}
|
||||
Reference in New Issue
Block a user