from dataclasses import dataclass from datetime import time from fastapi import HTTPException, status from app.modules.feishu_users.constants import ( FeishuCapability, FeishuUserRole, FeishuUserStatus, capabilities_for_role, ) from app.modules.feishu_users.models import FeishuUser @dataclass(frozen=True, slots=True) class FeishuMention: """Structured mention identity supplied by a verified Feishu event.""" key: str | None = None name: str | None = None tenant_key: str | None = None open_id: str | None = None union_id: str | None = None user_id: str | None = None @dataclass(frozen=True, slots=True) class FeishuPrincipal: """Authenticated Feishu user plus the current chat context.""" owner_id: int user_code: str tenant_key: str open_id: str union_id: str | None feishu_user_id: str | None role: str status: str timezone: str quiet_hours_start: time | None quiet_hours_end: time | None chat_id: str | None = None chat_type: str | None = None mentions: tuple[FeishuMention, ...] = () @property def is_active(self) -> bool: return self.status == FeishuUserStatus.ACTIVE @property def is_admin(self) -> bool: return self.role == FeishuUserRole.ADMIN def has_capability(self, capability: str | FeishuCapability) -> bool: if not self.is_active: return False try: required = FeishuCapability(capability) return required in capabilities_for_role(self.role) except ValueError: return False def require_active(self) -> None: if not self.is_active: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Feishu user is disabled", ) def require_capability(self, capability: str | FeishuCapability) -> None: self.require_active() if not self.has_capability(capability): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Feishu user is not authorized for this capability", ) @classmethod def from_user( cls, user: FeishuUser, *, chat_id: str | None = None, chat_type: str | None = None, mentions: tuple[FeishuMention, ...] = (), ) -> "FeishuPrincipal": return cls( owner_id=user.id, user_code=user.code, tenant_key=user.tenant_key, open_id=user.open_id, union_id=user.union_id, feishu_user_id=user.user_id, role=user.role, status=user.status, timezone=user.timezone, quiet_hours_start=user.quiet_hours_start, quiet_hours_end=user.quiet_hours_end, chat_id=chat_id, chat_type=chat_type, mentions=mentions, )