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>
234 lines
6.8 KiB
Python
234 lines
6.8 KiB
Python
"""CRUD operations for Notification model."""
|
|
|
|
import json
|
|
from datetime import datetime
|
|
from typing import Optional, List
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func, desc
|
|
|
|
from app.models.notification import Notification
|
|
from app.schemas.notification import NotificationCreate
|
|
|
|
|
|
class CRUDNotification:
|
|
"""CRUD operations for Notification model."""
|
|
|
|
def create(
|
|
self,
|
|
db: Session,
|
|
*,
|
|
obj_in: NotificationCreate
|
|
) -> Notification:
|
|
"""Create a new notification."""
|
|
extra_data_str = json.dumps(obj_in.extra_data) if obj_in.extra_data else None
|
|
|
|
db_obj = Notification(
|
|
user_id=obj_in.user_id,
|
|
title=obj_in.title,
|
|
message=obj_in.message,
|
|
type=obj_in.type,
|
|
link=obj_in.link,
|
|
extra_data=extra_data_str,
|
|
is_read=False
|
|
)
|
|
db.add(db_obj)
|
|
db.commit()
|
|
db.refresh(db_obj)
|
|
return db_obj
|
|
|
|
def create_for_user(
|
|
self,
|
|
db: Session,
|
|
*,
|
|
user_id: str,
|
|
title: str,
|
|
message: Optional[str] = None,
|
|
type: str = "info",
|
|
link: Optional[str] = None,
|
|
extra_data: Optional[dict] = None
|
|
) -> Notification:
|
|
"""Convenience method to create a notification for a user."""
|
|
obj_in = NotificationCreate(
|
|
user_id=user_id,
|
|
title=title,
|
|
message=message,
|
|
type=type,
|
|
link=link,
|
|
extra_data=extra_data
|
|
)
|
|
return self.create(db, obj_in=obj_in)
|
|
|
|
def create_for_all_users(
|
|
self,
|
|
db: Session,
|
|
*,
|
|
title: str,
|
|
message: Optional[str] = None,
|
|
type: str = "system",
|
|
link: Optional[str] = None,
|
|
extra_data: Optional[dict] = None
|
|
) -> int:
|
|
"""Create a notification for all users (system notification)."""
|
|
from app.models.user import User
|
|
|
|
users = db.query(User).filter(User.is_active == True).all()
|
|
count = 0
|
|
|
|
for user in users:
|
|
self.create_for_user(
|
|
db,
|
|
user_id=user.id,
|
|
title=title,
|
|
message=message,
|
|
type=type,
|
|
link=link,
|
|
extra_data=extra_data
|
|
)
|
|
count += 1
|
|
|
|
return count
|
|
|
|
def get(self, db: Session, id: str) -> Optional[Notification]:
|
|
"""Get a notification by ID."""
|
|
return db.query(Notification).filter(Notification.id == id).first()
|
|
|
|
def get_multi_by_user(
|
|
self,
|
|
db: Session,
|
|
*,
|
|
user_id: str,
|
|
skip: int = 0,
|
|
limit: int = 50,
|
|
unread_only: bool = False
|
|
) -> List[Notification]:
|
|
"""Get notifications for a user."""
|
|
query = db.query(Notification).filter(Notification.user_id == user_id)
|
|
|
|
if unread_only:
|
|
query = query.filter(Notification.is_read == False)
|
|
|
|
return query.order_by(desc(Notification.created_at))\
|
|
.offset(skip)\
|
|
.limit(limit)\
|
|
.all()
|
|
|
|
def count_by_user(self, db: Session, user_id: str) -> int:
|
|
"""Count total notifications for a user."""
|
|
return db.query(Notification).filter(Notification.user_id == user_id).count()
|
|
|
|
def count_unread_by_user(self, db: Session, user_id: str) -> int:
|
|
"""Count unread notifications for a user."""
|
|
return db.query(Notification)\
|
|
.filter(Notification.user_id == user_id)\
|
|
.filter(Notification.is_read == False)\
|
|
.count()
|
|
|
|
def mark_as_read(self, db: Session, *, id: str, user_id: str) -> Optional[Notification]:
|
|
"""Mark a notification as read."""
|
|
db_obj = db.query(Notification)\
|
|
.filter(Notification.id == id)\
|
|
.filter(Notification.user_id == user_id)\
|
|
.first()
|
|
|
|
if db_obj and not db_obj.is_read:
|
|
db_obj.is_read = True
|
|
db_obj.read_at = datetime.utcnow()
|
|
db.add(db_obj)
|
|
db.commit()
|
|
db.refresh(db_obj)
|
|
|
|
return db_obj
|
|
|
|
def mark_all_as_read(self, db: Session, *, user_id: str) -> int:
|
|
"""Mark all notifications as read for a user."""
|
|
count = db.query(Notification)\
|
|
.filter(Notification.user_id == user_id)\
|
|
.filter(Notification.is_read == False)\
|
|
.update({
|
|
"is_read": True,
|
|
"read_at": datetime.utcnow()
|
|
})
|
|
db.commit()
|
|
return count
|
|
|
|
def mark_multiple_as_read(
|
|
self,
|
|
db: Session,
|
|
*,
|
|
user_id: str,
|
|
notification_ids: List[str]
|
|
) -> int:
|
|
"""Mark multiple notifications as read."""
|
|
count = db.query(Notification)\
|
|
.filter(Notification.id.in_(notification_ids))\
|
|
.filter(Notification.user_id == user_id)\
|
|
.filter(Notification.is_read == False)\
|
|
.update({
|
|
"is_read": True,
|
|
"read_at": datetime.utcnow()
|
|
}, synchronize_session=False)
|
|
db.commit()
|
|
return count
|
|
|
|
def delete(self, db: Session, *, id: str, user_id: str) -> bool:
|
|
"""Delete a notification."""
|
|
obj = db.query(Notification)\
|
|
.filter(Notification.id == id)\
|
|
.filter(Notification.user_id == user_id)\
|
|
.first()
|
|
|
|
if obj:
|
|
db.delete(obj)
|
|
db.commit()
|
|
return True
|
|
return False
|
|
|
|
def delete_all_read(self, db: Session, *, user_id: str) -> int:
|
|
"""Delete all read notifications for a user."""
|
|
count = db.query(Notification)\
|
|
.filter(Notification.user_id == user_id)\
|
|
.filter(Notification.is_read == True)\
|
|
.delete()
|
|
db.commit()
|
|
return count
|
|
|
|
def delete_multiple(
|
|
self,
|
|
db: Session,
|
|
*,
|
|
user_id: str,
|
|
notification_ids: List[str]
|
|
) -> int:
|
|
"""Delete multiple notifications."""
|
|
count = db.query(Notification)\
|
|
.filter(Notification.id.in_(notification_ids))\
|
|
.filter(Notification.user_id == user_id)\
|
|
.delete(synchronize_session=False)
|
|
db.commit()
|
|
return count
|
|
|
|
def get_stats_by_user(self, db: Session, user_id: str) -> dict:
|
|
"""Get notification statistics for a user."""
|
|
total = self.count_by_user(db, user_id)
|
|
unread = self.count_unread_by_user(db, user_id)
|
|
|
|
# Count by type
|
|
type_counts = db.query(
|
|
Notification.type,
|
|
func.count(Notification.id).label('count')
|
|
).filter(Notification.user_id == user_id)\
|
|
.group_by(Notification.type)\
|
|
.all()
|
|
|
|
by_type = {t: c for t, c in type_counts}
|
|
|
|
return {
|
|
"total": total,
|
|
"unread": unread,
|
|
"by_type": by_type
|
|
}
|
|
|
|
|
|
# Create instance
|
|
notification = CRUDNotification()
|