"""Whiteboard 的 DAO(文本记事本)。 所有写操作均在该层 commit,service 不直接操作 session。 get_or_create 用于「访问即新建」语义(路由 GET /api/wb/{id} 不存在则建)。 """ from __future__ import annotations import logging from sqlalchemy import func, select from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from ..models.whiteboard import Whiteboard logger = logging.getLogger("zikai.whiteboard") class WhiteboardDAO: def __init__(self, db: Session) -> None: self.db = db def create(self, board: Whiteboard) -> Whiteboard: self.db.add(board) self.db.commit() self.db.refresh(board) return board def get(self, board_id: str) -> Whiteboard | None: stmt = select(Whiteboard).where(Whiteboard.board_id == board_id).limit(1) return self.db.scalars(stmt).first() def get_or_create(self, board_id: str) -> Whiteboard: """存在则返回,否则新建空板。利用 unique 约束兜底并发首访。 仅 IntegrityError(并发下另一事务已插入违反唯一约束)才回滚重读; 其他异常向上抛,避免掩盖 schema/连接等真实故障。 """ board = self.get(board_id) if board is not None: return board board = Whiteboard(board_id=board_id, content="", version=0, edit_count=0) try: return self.create(board) except IntegrityError: # 并发下另一事务已插入:回滚后重新读 self.db.rollback() return self.get(board_id) # type: ignore[return-value] def update_content(self, board_id: str, content: str) -> Whiteboard | None: """整体替换文本内容,version +1、edit_count +1。""" board = self.get(board_id) if board is None: return None board.content = content board.version = (board.version or 0) + 1 board.edit_count = (board.edit_count or 0) + 1 self.db.commit() self.db.refresh(board) return board def list_all(self, limit: int = 100, offset: int = 0) -> list[Whiteboard]: stmt = ( select(Whiteboard) .order_by(Whiteboard.updated_at.desc()) .limit(limit) .offset(offset) ) return list(self.db.scalars(stmt).all()) def count(self) -> int: return self.db.scalar(select(func.count()).select_from(Whiteboard)) or 0 def delete(self, board_id: str) -> bool: board = self.get(board_id) if board is None: return False self.db.delete(board) self.db.commit() return True