""" app/repositories/base_workspace_repo.py — 多租户基础 Repo 所有 workspace 相关 Repo 继承此类,强制过滤 workspace_id(基石C)。 禁止 SELECT *,明确字段(db.md规范)。 """ import logging from typing import Any, Generic, TypeVar from sqlalchemy import select from sqlalchemy.orm import Session from app.core.response import raise_not_found logger = logging.getLogger(__name__) T = TypeVar("T") class BaseWorkspaceRepo(Generic[T]): """ workspace 感知的基础 Repo。 子类设置 model_class,所有查询自动注入 workspace_id 过滤。 """ model_class: type[T] def __init__(self, db: Session, workspace_id: int): self.db = db self.workspace_id = workspace_id def _base_query(self): """所有查询的基础:强制带 workspace_id 过滤(多租户红线)。""" return self.db.query(self.model_class).filter( self.model_class.workspace_id == self.workspace_id # type: ignore[attr-defined] ) def get_by_id(self, record_id: int) -> T | None: """按 ID 查单条,强制带 workspace_id 防越权读。""" return self._base_query().filter( self.model_class.id == record_id # type: ignore[attr-defined] ).first() def get_by_id_or_404(self, record_id: int) -> T: obj = self.get_by_id(record_id) if not obj: raise_not_found(f"{self.model_class.__name__} {record_id} 不存在") return obj # type: ignore[return-value] def list_all( self, offset: int = 0, limit: int = 20, filters: list[Any] | None = None, ) -> tuple[list[T], int]: """分页列表,返回 (items, total)。""" q = self._base_query() if filters: q = q.filter(*filters) total = q.count() items = q.offset(offset).limit(limit).all() return items, total def create(self, obj: T) -> T: """插入一条记录,自动设置 workspace_id。""" obj.workspace_id = self.workspace_id # type: ignore[attr-defined] self.db.add(obj) self.db.flush() self.db.refresh(obj) logger.debug("Created %s id=%s ws=%s", self.model_class.__name__, obj.id, self.workspace_id) # type: ignore[attr-defined] return obj def delete(self, obj: T) -> None: """物理删除(软删各子类自己处理 archived 态)。""" self.db.delete(obj) self.db.flush() def save(self) -> None: """提交事务,错误不静默(官网V1坑3)。""" try: self.db.commit() except Exception: self.db.rollback() logger.error("DB commit failed in %s ws=%s", self.model_class.__name__, self.workspace_id) raise