video-accessibility/backend/app/models/user.py
Vadym Samoilenko 2b721d182b feat: Client → Team → Project isolation system with Project Manager role
Backend:
- New UserRole.PROJECT_MANAGER with pm_client_ids[] on User model
- New models: Client (slug-based), Team (member_user_ids[]), Project (client-scoped)
- Job model gains project_id field
- New GET/POST/PATCH/DELETE /clients, /clients/{id}/teams, /clients/{id}/projects,
  /clients/{id}/pm routes (admin-only client CRUD; PM or admin for teams/projects)
- get_accessible_project_ids() helper: staff→all, PM→their clients' projects,
  CLIENT→projects from teams they belong to (with legacy owner fallback)
- list_jobs, get_job, bulk_download, get_vtt_content, delete_job all use new isolation

Frontend:
- UserRole type gains 'project_manager'
- Job, JobCreateRequest gain project_id field
- Client, Team, Project, PMUser types added
- ApiClient: full client/team/project/PM CRUD methods
- useClients hook with all query/mutation hooks
- Admin pages: ClientList + ClientDetail (teams, members, projects, PM assignment)
- NewJob form: client + project picker (shown when clients exist)
- Sidebar: Clients nav item for admin and project_manager roles
- Routes: /admin/clients and /admin/clients/:clientId behind RoleGate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 15:11:13 +01:00

68 lines
1.7 KiB
Python

from datetime import datetime
from enum import Enum
from typing import Optional, Annotated
from bson import ObjectId
from pydantic import BaseModel, EmailStr, Field, BeforeValidator
def validate_object_id(v) -> str:
"""Convert ObjectId to string"""
if isinstance(v, ObjectId):
return str(v)
if isinstance(v, str):
return v
raise ValueError('Invalid ObjectId')
PyObjectId = Annotated[str, BeforeValidator(validate_object_id)]
class UserRole(str, Enum):
CLIENT = "client"
REVIEWER = "reviewer"
LINGUIST = "linguist"
PRODUCTION = "production"
PROJECT_MANAGER = "project_manager"
ADMIN = "admin"
class AuthProvider(str, Enum):
LOCAL = "local"
MICROSOFT = "microsoft"
class User(BaseModel):
id: Optional[PyObjectId] = Field(None, alias="_id")
email: EmailStr
hashed_password: Optional[str] = None # Optional for Microsoft users
full_name: str
role: UserRole = UserRole.CLIENT
auth_provider: AuthProvider = AuthProvider.LOCAL
is_active: bool = True
pm_client_ids: list[str] = [] # Client IDs where this user is Project Manager (admin-assigned)
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class Config:
populate_by_name = True
use_enum_values = True
class UserInDB(User):
pass
class UserCreate(BaseModel):
email: EmailStr
password: str
full_name: str
role: UserRole = UserRole.CLIENT
class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
full_name: Optional[str] = None
role: Optional[UserRole] = None
is_active: Optional[bool] = None
pm_client_ids: Optional[list[str]] = None