deerflow-code/offline-backend-20260512/backend/packages/harness/deerflow/persistence/positions/base.py
2026-09-07 18:24:55 +08:00

113 lines
4.2 KiB
Python

"""Abstract persistence API for 岗位 (positions) and their tag bindings."""
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any
class PositionStore(ABC):
# -------------------------------------------------------------- positions
@abstractmethod
async def list_positions(self) -> list[dict[str, Any]]:
"""List all positions. Each record carries a derived ``member_count``."""
raise NotImplementedError
@abstractmethod
async def get_position(self, position_id: str) -> dict[str, Any] | None:
raise NotImplementedError
@abstractmethod
async def create_position(self, data: dict[str, Any]) -> dict[str, Any]:
"""Create a position. ``data`` must carry ``id`` and ``name``.
Raises ``ValueError`` if ``name`` is not unique. When ``is_default`` is
truthy, any other default position is cleared first.
"""
raise NotImplementedError
@abstractmethod
async def update_position(self, position_id: str, data: dict[str, Any]) -> dict[str, Any] | None:
"""Update ``name`` / ``description`` / ``is_default``.
Returns ``None`` if the position does not exist. Raises ``ValueError``
on a name collision. Setting ``is_default=True`` clears the flag on the
other positions.
"""
raise NotImplementedError
@abstractmethod
async def delete_position(self, position_id: str) -> bool:
"""Delete a position, its tag bindings, and unassign its members."""
raise NotImplementedError
@abstractmethod
async def get_default_position_id(self) -> str | None:
"""Return the id of the ``is_default`` position, or ``None``."""
raise NotImplementedError
# ------------------------------------------------------------ tag bindings
@abstractmethod
async def get_tag_bindings(self, position_id: str) -> dict[str, list[str]]:
"""Return ``{resource_type: [tag_id, ...]}`` for one position.
Resource types with no bound tags are omitted (callers treat a missing
key as "use the default set").
"""
raise NotImplementedError
@abstractmethod
async def set_tag_bindings(self, position_id: str, bindings: dict[str, list[str]]) -> dict[str, list[str]]:
"""Replace the full set of tag bindings for a position.
``bindings`` maps each resource_type to its desired tag-id list. A
resource_type omitted from ``bindings`` (or mapped to ``[]``) ends up
with no rows — i.e. it falls back to the default set.
"""
raise NotImplementedError
# --------------------------------------------------------------- members
@abstractmethod
async def get_user_position_id(self, user_id: str) -> str | None:
"""Return the position id explicitly assigned to a user, or ``None``."""
raise NotImplementedError
@abstractmethod
async def list_members(self, position_id: str) -> list[dict[str, Any]]:
"""Return ``[{id, email}, ...]`` of users assigned to this position."""
raise NotImplementedError
@abstractmethod
async def assign_users(self, position_id: str, user_ids: list[str]) -> int:
"""Assign a position to many users (1:1 — overwrites any prior position).
Returns the number of users updated.
"""
raise NotImplementedError
@abstractmethod
async def unassign_users(self, user_ids: list[str]) -> int:
"""Clear the position of many users. Returns the number updated."""
raise NotImplementedError
@abstractmethod
async def list_member_ids(self, position_id: str) -> list[str]:
"""Return just the user ids assigned to this position (sync fan-out)."""
raise NotImplementedError
@abstractmethod
async def list_assigned_user_ids(self) -> list[str]:
"""Return every user id that has an explicit position (for bulk resync)."""
raise NotImplementedError
@abstractmethod
async def list_all_users(self) -> list[dict[str, Any]]:
"""Return ``[{id, email, system_role, position_id}, ...]`` for every user.
Powers the admin member-picker and the per-user position editor.
"""
raise NotImplementedError