Files
docqube_backend/app/modules/auth/schemas/role_schema.py
T
2026-09-08 11:00:05 +05:30

142 lines
4.4 KiB
Python

from pydantic import BaseModel, Field, ConfigDict
from typing import Literal, Optional, List
from datetime import datetime
from uuid import UUID
class RoleAccessOut(BaseModel):
id: UUID
name: str
access_code: str
category: str
parent_id: Optional[UUID] = None
model_config = ConfigDict(from_attributes=True)
class RoleCreate(BaseModel):
role_name: str = Field(..., max_length=100)
description: Optional[str] = None
tenant_id: Optional[UUID] = None
access_ids: Optional[List[UUID]] = []
is_default: Optional[bool] = False
class RoleUpdate(BaseModel):
role_name: Optional[str] = None
description: Optional[str] = None
access_ids: Optional[List[UUID]] = None
is_default: Optional[bool] = None
class RoleOut(BaseModel):
id: UUID
role_name: str = Field(..., alias="name")
description: Optional[str] = None
tenant_id: Optional[UUID] = None
is_default: bool = False
is_system: bool = False
created_at: Optional[datetime] = None
model_config = ConfigDict(from_attributes=True, populate_by_name=True)
class RoleWithAccessesOut(RoleOut):
accesses: List[RoleAccessOut] = []
assigned_user_count: Optional[int] = None
"""
How many principals this role reaches, or null when it was not counted.
Added to the *details* response and deliberately not to `RoleOut`: the list
endpoints render up to a hundred roles and counting each one would be an
N+1 nobody asked for. It exists so that deleting a role can say what it is
about to do — `user_roles.role_id` is `ON DELETE CASCADE` and
`users.role_id` is `ON DELETE SET NULL`, so a silent delete removes
authority from people the operator was not thinking about.
"""
class RoleAssignmentOut(BaseModel):
"""
One role a user holds, and how they hold it.
`source` is the field that keeps the interface honest:
"primary" the legacy `users.role_id`. Changed through
`PATCH /api/admin/users/{id}`, **not** revocable as a grant,
and while it is set it cannot be narrowed by anything (see
`ScopeService._resolve`)
"grant" a `user_roles` row naming this user. Added and revoked freely
"group" a `user_roles` row naming a group this user belongs to. It
confers codes; it is removed by editing the group
Rendering the three identically would imply that removing any of them is the
same action. It is not, and that is exactly where a permissions screen
misleads the person using it.
"""
grant_id: Optional[UUID] = None
role_id: UUID
role_name: str
source: Literal["primary", "grant", "group"]
org_unit_id: Optional[UUID] = None
org_unit_name: Optional[str] = None
group_id: Optional[UUID] = None
group_name: Optional[str] = None
expires_at: Optional[datetime] = None
assigned_by_id: Optional[int] = None
created_at: Optional[datetime] = None
is_expired: bool = False
model_config = ConfigDict(from_attributes=True)
class AddUserRoleIn(BaseModel):
"""
Add one role to one user.
`org_unit_id` omitted or null means **tenant-wide** — the authority every
user already has through their primary role. That default is the point of
this endpoint: the organisation tree could only ever grant *scoped to a
unit*, so "Alice is an Editor and an Approver, everywhere" was not
expressible anywhere in the product.
"""
role_id: UUID
org_unit_id: Optional[UUID] = None
expires_at: Optional[datetime] = None
class AccessAttributionOut(BaseModel):
"""Which role conferred one access code, and at what scope."""
role_id: Optional[UUID] = None
role_name: Optional[str] = None
source: str
org_unit_id: Optional[UUID] = None
org_unit_name: Optional[str] = None
expires_at: Optional[datetime] = None
class EffectiveAccessOut(BaseModel):
"""
One access code the user holds, with every reason they hold it.
Answers "why can this person do that". With one role per user the question
answered itself. With several it does not, and support cannot read the
database.
"""
access_code: str
granted_by: List[AccessAttributionOut]
class RolePaginatedOut(BaseModel):
items: List[RoleOut]
total: int
page: int
page_size: int
total_pages: int
model_config = ConfigDict(from_attributes=True)