trim_read looped a db.get()+db.delete() per id (N+1). The ids are already in hand, so one DELETE ... WHERE id IN (...) replaces the loop.
59 lines
1.9 KiB
Python
59 lines
1.9 KiB
Python
"""Helpers for the durable, server-backed per-user notification inbox (phase 1).
|
|
|
|
The inbox COEXISTS with the client-side transient "bell" (localStorage toasts): these rows
|
|
survive reloads/devices and are produced server-side. First producer is the maintenance
|
|
job. Unread rows never auto-expire; read rows are trimmed past a soft per-user cap so the
|
|
table can't grow without bound for a heavy user.
|
|
"""
|
|
from sqlalchemy import delete, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models import Notification
|
|
|
|
# Keep at most this many READ notifications per user; older read ones are pruned. Unread
|
|
# rows are never pruned (the user hasn't seen them yet).
|
|
READ_SOFT_CAP = 200
|
|
|
|
|
|
def create_notification(
|
|
db: Session,
|
|
user_id: int,
|
|
type: str,
|
|
title: str,
|
|
body: str | None = None,
|
|
data: dict | None = None,
|
|
*,
|
|
commit: bool = True,
|
|
) -> Notification:
|
|
"""Insert one notification for a user. Callers that batch many inserts in a single
|
|
transaction can pass commit=False and commit once themselves."""
|
|
notif = Notification(
|
|
user_id=user_id, type=type, title=title, body=body, data=data
|
|
)
|
|
db.add(notif)
|
|
if commit:
|
|
db.commit()
|
|
db.refresh(notif)
|
|
return notif
|
|
|
|
|
|
def trim_read(db: Session, user_id: int, cap: int = READ_SOFT_CAP) -> int:
|
|
"""Delete a user's oldest READ notifications beyond `cap`. Returns the number removed.
|
|
Idempotent and cheap; safe to call after marking things read."""
|
|
ids = (
|
|
db.execute(
|
|
select(Notification.id)
|
|
.where(Notification.user_id == user_id, Notification.read.is_(True))
|
|
.order_by(Notification.created_at.desc())
|
|
.offset(cap)
|
|
)
|
|
.scalars()
|
|
.all()
|
|
)
|
|
if not ids:
|
|
return 0
|
|
# One bulk DELETE rather than a get+delete per row (the ids are already in hand).
|
|
db.execute(delete(Notification).where(Notification.id.in_(ids)))
|
|
db.commit()
|
|
return len(ids)
|