feat(workbench): persist topbar notification state
This commit is contained in:
88
server/src/app/services/notification_states.py
Normal file
88
server/src/app/services/notification_states.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import CurrentUserContext
|
||||
from app.db.base import Base
|
||||
from app.models.notification_state import NotificationState
|
||||
from app.schemas.notification_state import (
|
||||
NotificationStateBatchPatch,
|
||||
NotificationStateListRead,
|
||||
NotificationStateRead,
|
||||
)
|
||||
|
||||
|
||||
class NotificationStateService:
|
||||
def __init__(self, db: Session) -> None:
|
||||
self.db = db
|
||||
|
||||
def ensure_storage_ready(self) -> None:
|
||||
Base.metadata.create_all(bind=self.db.get_bind(), tables=[NotificationState.__table__])
|
||||
|
||||
def list_states(self, current_user: CurrentUserContext) -> NotificationStateListRead:
|
||||
self.ensure_storage_ready()
|
||||
stmt = (
|
||||
select(NotificationState)
|
||||
.where(NotificationState.user_id == self._user_key(current_user))
|
||||
.order_by(NotificationState.updated_at.desc())
|
||||
)
|
||||
states = list(self.db.scalars(stmt).all())
|
||||
return NotificationStateListRead(
|
||||
states=[NotificationStateRead.model_validate(item) for item in states]
|
||||
)
|
||||
|
||||
def patch_states(
|
||||
self,
|
||||
payload: NotificationStateBatchPatch,
|
||||
current_user: CurrentUserContext,
|
||||
) -> NotificationStateListRead:
|
||||
self.ensure_storage_ready()
|
||||
user_id = self._user_key(current_user)
|
||||
patches = [item for item in payload.states if item.notification_id]
|
||||
if not patches:
|
||||
return self.list_states(current_user)
|
||||
|
||||
ids = {item.notification_id for item in patches}
|
||||
existing_rows = list(
|
||||
self.db.scalars(
|
||||
select(NotificationState).where(
|
||||
NotificationState.user_id == user_id,
|
||||
NotificationState.notification_id.in_(ids),
|
||||
)
|
||||
).all()
|
||||
)
|
||||
existing_by_id = {item.notification_id: item for item in existing_rows}
|
||||
now = datetime.now(UTC)
|
||||
|
||||
for patch in patches:
|
||||
row = existing_by_id.get(patch.notification_id)
|
||||
if row is None:
|
||||
row = NotificationState(
|
||||
user_id=user_id,
|
||||
notification_id=patch.notification_id,
|
||||
context_json={},
|
||||
)
|
||||
self.db.add(row)
|
||||
existing_by_id[patch.notification_id] = row
|
||||
|
||||
if patch.read and row.read_at is None:
|
||||
row.read_at = now
|
||||
if patch.hidden and row.hidden_at is None:
|
||||
row.hidden_at = now
|
||||
if patch.context_json:
|
||||
row.context_json = self._merge_context(row.context_json, patch.context_json)
|
||||
|
||||
self.db.commit()
|
||||
return self.list_states(current_user)
|
||||
|
||||
@staticmethod
|
||||
def _user_key(current_user: CurrentUserContext) -> str:
|
||||
return str(current_user.username or current_user.name or "anonymous").strip() or "anonymous"
|
||||
|
||||
@staticmethod
|
||||
def _merge_context(current: dict | None, patch: dict) -> dict:
|
||||
base = current if isinstance(current, dict) else {}
|
||||
return {**base, **patch}
|
||||
Reference in New Issue
Block a user