from typing import Optional, List from datetime import datetime from pydantic import BaseModel, SecretStr class TokenRequest(BaseModel): username: str password: SecretStr class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" class UserInfo(BaseModel): sub: Optional[str] = None # Keycloak subject ID preferred_username: str email: Optional[str] = None full_name: Optional[str] = None db_user_id: Optional[int] = None # Database user ID (populated after login) role: Optional[str] = None # User role name role_id: Optional[int] = None # User role ID (1=editor, 2=auditor, 3=admin) # Role schemas ROLE_DISPLAY_NAMES = { "editor": "Editor", "auditor": "Auditor", "admin": "Project Admin" } class RoleResponse(BaseModel): """Response schema for a role.""" id: int role_name: str display_name: str class Config: from_attributes = True @classmethod def from_role(cls, role) -> "RoleResponse": """Create a RoleResponse from a Role model.""" return cls( id=role.id, role_name=role.role_name, display_name=ROLE_DISPLAY_NAMES.get(role.role_name, role.role_name.title()) ) class ProjectMemberResponse(BaseModel): """Response schema for a project member with role info.""" id: int sub: str role_id: int role_name: str role_display_name: str created_at: Optional[datetime] = None class Config: from_attributes = True class UserRoleUpdateRequest(BaseModel): """Request schema for updating a user's role.""" role_id: int # Project schemas class ProjectBase(BaseModel): """Base schema for projects.""" project_name: str project_desc: Optional[str] = None class ProjectResponse(BaseModel): """Response schema for a single project.""" id: int project_name: str project_desc: Optional[str] = None created_at: Optional[datetime] = None class Config: from_attributes = True class ProjectCreateRequest(BaseModel): """Request schema for creating a project.""" project_name: str project_desc: Optional[str] = None class ProjectUpdateRequest(BaseModel): """Request schema for updating a project.""" project_name: Optional[str] = None project_desc: Optional[str] = None class ProjectListResponse(BaseModel): """Response schema for list of projects.""" projects: List[ProjectResponse] class ProjectMemberRequest(BaseModel): """Request schema for adding/removing project members.""" user_id: int # Group schemas class GroupResponse(BaseModel): """Response schema for a single group.""" id: int group_name: str hex_color: str class Config: from_attributes = True class GroupListResponse(BaseModel): """Response schema for list of groups.""" groups: List[GroupResponse] # Tag schemas class TagResponse(BaseModel): """Response schema for a single tag.""" id: int tag_code: str tag_description: str class Config: from_attributes = True class TagListResponse(BaseModel): """Response schema for list of tags.""" tags: List[TagResponse] # Priority schemas class PriorityResponse(BaseModel): """Response schema for a priority.""" id: int priority_name: str priority_num: int class Config: from_attributes = True # Requirement Status schemas class RequirementStatusResponse(BaseModel): """Response schema for a requirement lifecycle status.""" id: int status_code: str status_name: str description: Optional[str] = None class Config: from_attributes = True # Validation schemas class ValidationStatusResponse(BaseModel): """Response schema for a validation status.""" id: int status_name: str class Config: from_attributes = True class ValidationResponse(BaseModel): """Response schema for a validation.""" id: int status_name: str req_version_snapshot: int comment: Optional[str] = None created_at: Optional[datetime] = None class Config: from_attributes = True class ValidationHistoryResponse(BaseModel): """Response schema for validation history with validator info.""" id: int status_name: str status_id: int req_version_snapshot: int comment: Optional[str] = None created_at: Optional[datetime] = None validator_username: str validator_id: int class Config: from_attributes = True class ValidationCreateRequest(BaseModel): """Request schema for creating a validation.""" status_id: int comment: Optional[str] = None # Requirement schemas class RequirementResponse(BaseModel): """Response schema for a single requirement.""" id: int project_id: int req_name: str req_desc: Optional[str] = None version: int created_at: Optional[datetime] = None updated_at: Optional[datetime] = None tag: TagResponse priority: Optional[PriorityResponse] = None groups: List[GroupResponse] = [] # Requirement lifecycle status (Draft, Regular) status: Optional[RequirementStatusResponse] = None # Validation status (Approved, Denied, etc.) validation_status: Optional[str] = None # Computed from latest validation validated_by: Optional[str] = None # Username of the validator validated_at: Optional[datetime] = None # When the latest validation was made validation_version: Optional[int] = None # Version at which requirement was validated author_username: Optional[str] = None # Display name of who created the requirement last_editor_username: Optional[str] = None # Display name of who last edited the requirement class Config: from_attributes = True class RequirementListResponse(BaseModel): """Response schema for list of requirements.""" requirements: List[RequirementResponse] class RequirementCreateRequest(BaseModel): """Request schema for creating a requirement.""" project_id: int tag_id: int req_name: str req_desc: Optional[str] = None priority_id: Optional[int] = None group_ids: Optional[List[int]] = None status_id: Optional[int] = None # Defaults to Draft (1) if not provided class RequirementUpdateRequest(BaseModel): """Request schema for updating a requirement.""" req_name: Optional[str] = None req_desc: Optional[str] = None tag_id: Optional[int] = None priority_id: Optional[int] = None group_ids: Optional[List[int]] = None status_id: Optional[int] = None class RequirementHistoryResponse(BaseModel): """Response schema for requirement history (previous versions).""" history_id: int version: Optional[int] = None req_name: Optional[str] = None req_desc: Optional[str] = None tag_code: Optional[str] = None priority_name: Optional[str] = None edited_by_username: Optional[str] = None valid_from: Optional[datetime] = None valid_to: Optional[datetime] = None class Config: from_attributes = True # Relationship Type schemas class RelationshipTypeResponse(BaseModel): """Response schema for a relationship type.""" id: int project_id: int type_name: str type_description: Optional[str] = None inverse_type_name: Optional[str] = None class Config: from_attributes = True class RelationshipTypeCreateRequest(BaseModel): """Request schema for creating a relationship type.""" type_name: str type_description: Optional[str] = None inverse_type_name: Optional[str] = None class RelationshipTypeUpdateRequest(BaseModel): """Request schema for updating a relationship type.""" type_name: Optional[str] = None type_description: Optional[str] = None inverse_type_name: Optional[str] = None # Requirement Link schemas class LinkedRequirementInfo(BaseModel): """Brief info about a linked requirement.""" id: int req_name: str tag_code: str class Config: from_attributes = True class RequirementLinkResponse(BaseModel): """Response schema for a requirement link with direction.""" id: int direction: str # 'outgoing' or 'incoming' type_name: str type_id: int inverse_type_name: Optional[str] = None linked_requirement: LinkedRequirementInfo created_by_username: Optional[str] = None created_by_id: Optional[int] = None created_at: Optional[datetime] = None class Config: from_attributes = True class RequirementLinkCreateRequest(BaseModel): """Request schema for creating a requirement link.""" relationship_type_id: int target_requirement_id: int # Requirement Search schemas class RequirementSearchResult(BaseModel): """Response schema for requirement search results (for autocomplete).""" id: int req_name: str tag_code: str class Config: from_attributes = True # Comment schemas class CommentReplyResponse(BaseModel): """Response schema for a comment reply.""" id: int reply_text: str created_at: Optional[datetime] = None updated_at: Optional[datetime] = None author_id: Optional[int] = None author_username: Optional[str] = None author_full_name: Optional[str] = None author_role: Optional[str] = None class Config: from_attributes = True class CommentResponse(BaseModel): """Response schema for a comment with its replies.""" id: int comment_text: str created_at: Optional[datetime] = None updated_at: Optional[datetime] = None author_id: Optional[int] = None author_username: Optional[str] = None author_full_name: Optional[str] = None author_role: Optional[str] = None replies: List[CommentReplyResponse] = [] class Config: from_attributes = True class CommentCreateRequest(BaseModel): """Request schema for creating a comment.""" comment_text: str class ReplyCreateRequest(BaseModel): """Request schema for creating a reply.""" reply_text: str