from pydantic import BaseModel, EmailStr, field_validator from typing import Optional, List, Any import datetime from enum import Enum # Role & Permission class RoleBase(BaseModel): name: str description: Optional[str] = None class RoleCreate(RoleBase): pass class Role(RoleBase): id: int class Config: from_attributes = True class SecurityIndexBase(BaseModel): soc: int = 0 pentest: int = 0 access_policy: int = 0 two_fa_policy: int = 0 config_network: int = 0 config_server: int = 0 patch_security: int = 0 month_id: int unit_id: int description: Optional[str] = None class SecurityIndexCreate(SecurityIndexBase): pass class SecurityIndex(SecurityIndexBase): id: int created_at: datetime.datetime class Config: from_attributes = True class SecurityIndexDetail(SecurityIndex): unit_name: Optional[str] = None at1: float = 0 at2: float = 0 at: float = 0 rank: Optional[int] = None # class PermissionBase và PermissionCreate giữ nguyên class PermissionBase(BaseModel): name: str path: str method: str description: Optional[str] = None class PermissionCreate(PermissionBase): pass class Permission(PermissionBase): id: int created_at: datetime.datetime class Config: from_attributes = True # Units & Users class UnitBase(BaseModel): name: str description: Optional[str] = None class UnitCreate(UnitBase): pass class Unit(UnitBase): id: int class Config: from_attributes = True class UserBase(BaseModel): email: EmailStr fullname: str unit_id: Optional[int] = None role_id: Optional[int] = None class UserCreate(UserBase): password: Optional[str] = None class User(UserBase): id: int status: int created_at: datetime.datetime updated_at: datetime.datetime class Config: from_attributes = True class UserUpdate(BaseModel): email: Optional[EmailStr] = None fullname: Optional[str] = None unit_id: Optional[int] = None status: Optional[int] = None password: Optional[str] = None role_id: Optional[int] = None # Category & Posts class CategoryBase(BaseModel): name: str class CategoryCreate(CategoryBase): pass class Category(CategoryBase): id: int class Config: from_attributes = True class PostBase(BaseModel): content: str title: str created_by: Optional[int] = None category_id: Optional[int] = None status: Optional[bool] = True thumbnail: Optional[str] = None thumbnail_base64: Optional[str] = None thumbnail_filename: Optional[str] = None class PostCreate(PostBase): pass class PostUpdate(BaseModel): content: Optional[str] = None title: Optional[str] = None created_by: Optional[int] = None category_id: Optional[int] = None status: Optional[bool] = None thumbnail: Optional[str] = None thumbnail_base64: Optional[str] = None thumbnail_filename: Optional[str] = None class Post(PostBase): id: int created_at: datetime.datetime class Config: from_attributes = True class PostResponse(Post): created_by_name: Optional[str] = None files: Optional[List[str]] = None class Config: from_attributes = True # Month & stats class MonthBase(BaseModel): name: str from_date: datetime.datetime end_date: datetime.datetime class MonthCreate(MonthBase): pass class Month(MonthBase): id: int class Config: from_attributes = True # Generic small stat create schemas class NACCreate(BaseModel): month_id: int unit_id: int total: Optional[int] = 0 installed: Optional[int] = 0 ignored: Optional[int] = 0 class NAC(BaseModel): id: int month_id: int unit_id: int total: int installed: int ignored: int rate: float created_at: datetime.datetime class Config: from_attributes = True # Ticket / others class TicketCreate(BaseModel): unit_id: int month_id: int open_count: Optional[int] = 0 in_process: Optional[int] = 0 completed: Optional[int] = 0 class Ticket(BaseModel): id: int open_count: int in_process: int completed: int unit_id: int month_id: int created_at: datetime.datetime class Config: from_attributes = True # Mail and uploads class MailHistoryCreate(BaseModel): post_id: Optional[int] subject: Optional[str] = None role_id: Optional[int] = None content: Optional[str] status: Optional[int] = None class MailHistory(BaseModel): id: int post_id: Optional[int] = None subject: Optional[str] = None role_id: Optional[int] = None content: Optional[str] = None status: Optional[int] = None created_at: datetime.datetime class Config: from_attributes = True class MailHistoryUpdate(BaseModel): post_id: Optional[int] = None subject: Optional[str] = None role_id: Optional[int] = None content: Optional[str] = None status: Optional[int] = None class FileUploadCreate(BaseModel): path: str table_name: str table_id: int class ImportHistoryCreate(BaseModel): type: str file_path: str month_id: int class ImportHistory(BaseModel): id: int type: str file_path: str month_id: int created_at: datetime.datetime class Config: from_attributes = True # Auth class LoginRequest(BaseModel): email: EmailStr password: str class LoginSSORequest(BaseModel): email: EmailStr password: str otp: Optional[str] = None class UserLoginResponse(BaseModel): """User info returned in login response with additional details.""" id: int email: EmailStr fullname: str unit_id: Optional[int] = None unit_name: Optional[str] = None role_id: Optional[int] = None role_name: Optional[str] = None status: int status_name: str created_at: datetime.datetime updated_at: datetime.datetime class LoginResponse(BaseModel): access_token: str token_type: str = "bearer" user: UserLoginResponse class Config: from_attributes = True class APIResponse(BaseModel): message: str data: Optional[object] = None class SystemGroupBase(BaseModel): name: str description: Optional[str] = None class SystemGroupCreate(SystemGroupBase): pass class SystemGroupUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None class SystemGroup(SystemGroupBase): id: int class Config: from_attributes = True # Pentest Service class PentestServiceBase(BaseModel): name: str type: int description: Optional[str] = None due_date: Optional[datetime.datetime] = None unit_id: Optional[int] = None target_id: Optional[int] = None class PentestServiceCreate(PentestServiceBase): user_id: Optional[int] = None list_process: Optional[List[int]] = None other_assign: Optional[List[str]] = None class PentestServiceUpdate(BaseModel): name: Optional[str] = None type: Optional[int] = None description: Optional[str] = None due_date: Optional[datetime.datetime] = None status: Optional[int] = None target_id: Optional[int] = None unit_id: Optional[int] = None user_id: Optional[int] = None list_process: Optional[List[int]] = None other_assign: Optional[List[str]] = None class PentestService(PentestServiceBase): id: int code: str file_path: Optional[str] = None status: int status_name: Optional[str] = None unit_name: Optional[str] = None user_id: Optional[int] = None user_fullname: Optional[str] = None list_assign: Optional[List[str]] = None list_assign_name: Optional[List[str]] = None other_assign: Optional[str] = None created_at: datetime.datetime created_at_str: Optional[str] = None due_date_str: Optional[str] = None class Config: from_attributes = True # SOC Target class SocTargetAssignBase(BaseModel): user_id: Optional[int] = None email: Optional[str] = None class SocTargetAssignCreate(SocTargetAssignBase): pass class SocTargetAssign(SocTargetAssignBase): id: int target_id: int name: Optional[str] = None created_at: datetime.datetime class Config: from_attributes = True class SocTargetAttachmentBase(BaseModel): file_name: str file_path: str file_size: Optional[int] = None file_type: Optional[str] = None class SocTargetAttachmentCreate(SocTargetAttachmentBase): pass class SocTargetAttachment(SocTargetAttachmentBase): id: int target_id: int uploaded_by: Optional[int] = None created_at: datetime.datetime class Config: from_attributes = True class SocTargetCommentBase(BaseModel): content: str user_id: Optional[int] = None email: Optional[str] = None class SocTargetCommentCreate(SocTargetCommentBase): pass class SocTargetComment(SocTargetCommentBase): id: Any target_id: Any name: Optional[str] = None created_at: datetime.datetime updated_at: datetime.datetime class Config: from_attributes = True class SocTargetHistoryBase(BaseModel): field_name: str old_value: Optional[str] = None new_value: Optional[str] = None class SocTargetHistory(SocTargetHistoryBase): id: Any target_id: Any changed_by: Optional[int] = None changed_by_name: Optional[str] = None changed_at: datetime.datetime class Config: from_attributes = True class SocTargetTypeBase(BaseModel): name: str class SocTargetType(SocTargetTypeBase): id: int class Config: from_attributes = True class TicketRelatedBase(BaseModel): soc_target_id: int related_soc_target_id: int class TicketRelated(TicketRelatedBase): id: int created_at: datetime.datetime related_ticket_name: Optional[str] = None related_ticket_code: Optional[str] = None class Config: from_attributes = True class SocTargetBase(BaseModel): unit_id: int priority: int name: str code: Optional[str] = None status: int = 0 deadline: Optional[datetime.datetime] = None description: str system_id: Optional[int] = None system_type: Optional[int] = None source_ip: Optional[str] = None destination_ip: Optional[str] = None soc_target_type_id: Optional[int] = None class SocTargetCreate(SocTargetBase): assign_internal: Optional[str] = None assign_external: Optional[str] = None related_ticket_ids: Optional[str] = None # CSV string of related ticket IDs class SocTargetUpdate(BaseModel): unit_id: Optional[int] = None priority: Optional[int] = None name: Optional[str] = None status: Optional[int] = None deadline: Optional[datetime.datetime] = None description: Optional[str] = None system_id: Optional[int] = None system_type: Optional[int] = None source_ip: Optional[str] = None destination_ip: Optional[str] = None soc_target_type_id: Optional[int] = None assign_internal: Optional[str] = None assign_external: Optional[str] = None related_ticket_ids: Optional[str] = None # CSV string of related ticket IDs class SocTarget(SocTargetBase): id: Any created_at: datetime.datetime updated_at: datetime.datetime created_by: Optional[int] = None class Config: from_attributes = True class SocTargetDetail(SocTarget): unit_name: Optional[str] = None system_name: Optional[str] = None system_type_name: Optional[str] = None soc_type_name: Optional[str] = None created_by_name: Optional[str] = None updated_by_name: Optional[str] = None assignees: List[SocTargetAssign] = [] attachments: List[SocTargetAttachment] = [] related_list: List[TicketRelated] = [] sla_status: Optional[str] = None # overdue, on_time, near_deadline class Config: from_attributes = True class SocTargetPagination(BaseModel): items: List[SocTargetDetail] total: int page: int size: int pages: int class PentestServiceCommentBase(BaseModel): content: str pentest_service_id: int class PentestServiceCommentAdd(BaseModel): content: str class PentestServiceCommentCreate(PentestServiceCommentBase): pass class PentestServiceComment(PentestServiceCommentBase): id: int user_id: int created_at: datetime.datetime user_fullname: Optional[str] = None class Config: from_attributes = True class PentestServiceHistory(BaseModel): id: int pentest_service_id: int old_status: Optional[int] = None new_status: int user_id: int created_at: datetime.datetime user_fullname: Optional[str] = None old_status_name: Optional[str] = None new_status_name: Optional[str] = None class Config: from_attributes = True count: Optional[int] = None # Post Comments class PostCommentBase(BaseModel): content: str class PostCommentCreate(PostCommentBase): pass class PostCommentUpdate(PostCommentBase): pass class PostComment(PostCommentBase): id: int post_id: int user_id: int created_at: datetime.datetime updated_at: datetime.datetime user_fullname: Optional[str] = None class Config: from_attributes = True class Config: from_attributes = True class SystemBase(BaseModel): name: str url_ip: Optional[str] = None unit_id: Optional[int] = None system_group_id: Optional[int] = None description: Optional[str] = None status: Optional[int] = None class SystemCreate(SystemBase): pass class SystemUpdate(BaseModel): name: Optional[str] = None url_ip: Optional[str] = None unit_id: Optional[int] = None system_group_id: Optional[int] = None description: Optional[str] = None status: Optional[int] = None class System(BaseModel): id: int name: str url_ip: Optional[str] = None unit_id: Optional[int] = None system_group_id: Optional[int] = None description: Optional[str] = None status: Optional[int] = None unit_name: Optional[str] = None system_group_name: Optional[str] = None class Config: from_attributes = True # Response wrappers for SystemGroup APIs class SystemGroupResponse(BaseModel): message: str data: SystemGroup class SystemGroupListResponse(BaseModel): message: str data: List[SystemGroup] # Response wrappers for System APIs class SystemResponse(BaseModel): message: str data: System class SystemListResponse(BaseModel): message: str data: List[System] class ManageSystemBase(BaseModel): name: str level: int url_ip: Optional[str] = None unit_id: Optional[int] = None parent_id: Optional[int] = None type: Optional[int] = None private: Optional[bool] = None priority_level: Optional[int] = None status: Optional[bool] = None @field_validator("unit_id", "parent_id", "type", "priority_level", mode="before") def _coerce_int_optional(cls, v): if v is None: return None if isinstance(v, str) and v.strip() == "": return None return v @field_validator("private", "status", mode="before") def _coerce_bool_optional(cls, v): if v is None: return None if isinstance(v, str) and v.strip() == "": return None return v class ManageSystemCreate(ManageSystemBase): pass class ManageSystemUpdate(BaseModel): name: Optional[str] = None level: Optional[int] = None url_ip: Optional[str] = None unit_id: Optional[int] = None parent_id: Optional[int] = None type: Optional[int] = None private: Optional[bool] = None priority_level: Optional[int] = None status: Optional[bool] = None class ManageSystem(BaseModel): id: int name: str level: int url_ip: Optional[str] = None unit_id: Optional[int] = None unit_name: Optional[str] = None parent_id: Optional[int] = None parent_name: Optional[str] = None type: Optional[int] = None private: Optional[bool] = None priority_level: Optional[int] = None status: Optional[bool] = None created_at: datetime.datetime updated_at: datetime.datetime class Config: from_attributes = True class ManageSystemResponse(BaseModel): message: str data: ManageSystem class ManageSystemListResponse(BaseModel): message: str data: List[ManageSystem] class ManageSystemImportRowError(BaseModel): row: int error: str class ManageSystemImportResponse(BaseModel): message: str total: int upserted: int failed: int errors: List[ManageSystemImportRowError] = [] class SystemImportRowError(BaseModel): row: int error: str class SystemImportResponse(BaseModel): message: str total: int inserted: int failed: int errors: List[SystemImportRowError] = [] # Dùng chung cho delete (nếu chưa có ở file, thêm vào) class DeleteResponse(BaseModel): message: str deleted: bool class LogSourceCreate(BaseModel): month_id: int unit_id: int name: str log_id: Optional[int] = None description: Optional[str] = None is_enabled: Optional[bool] = True source_type_id: Optional[int] = None status: Optional[int] = None # 1=OK, 2=ERROR, 3=DISABLE, 4=NOT AVAILABLE message: Optional[str] = None other: Optional[str] = None file_id: Optional[int] = None created_at: Optional[datetime.datetime] = None class LogSource(BaseModel): id: int month_id: int unit_id: int unit_name: Optional[str] = None log_id: Optional[int] = None name: str description: Optional[str] = None is_enabled: Optional[bool] = True source_type_id: Optional[int] = None status: Optional[int] = None # trả về mã số source_type_name: Optional[str] = None status_name: Optional[str] = None message: Optional[str] = None other: Optional[str] = None file_id: Optional[int] = None created_at: datetime.datetime class Config: from_attributes = True class LogSourceCommentBase(BaseModel): comment: str class LogSourceCommentCreate(LogSourceCommentBase): logsource_id: int class LogSourceComment(LogSourceCommentBase): id: int logsource_id: int user_id: int user_fullname: Optional[str] = None created_at: datetime.datetime class Config: from_attributes = True class LogSourceImportRowError(BaseModel): row: int error: str class LogSourceImportResponse(BaseModel): message: str total: int inserted: int failed: int errors: List[LogSourceImportRowError] = [] class SmartIRDetailCreate(BaseModel): month_id: int unit_id: Optional[int] = None vnpt_ma_nhan_vien: Optional[str] = None name: Optional[str] = None email: Optional[str] = None phong_ban: Optional[str] = None ip: Optional[str] = None pc_name: Optional[str] = None mac: Optional[str] = None agent_version: Optional[str] = None last_online: Optional[str] = None status: Optional[int] = None class SmartIRDetail(BaseModel): id: int month_id: int unit_id: Optional[int] = None unit_name: Optional[str] = None vnpt_ma_nhan_vien: Optional[str] = None name: Optional[str] = None email: Optional[str] = None phong_ban: Optional[str] = None ip: Optional[str] = None pc_name: Optional[str] = None mac: Optional[str] = None agent_version: Optional[str] = None last_online: Optional[str] = None status: Optional[int] = None created_at: datetime.datetime class Config: from_attributes = True class SmartIRDetailImportRowError(BaseModel): row: int error: str class SmartIRDetailImportResponse(BaseModel): message: str total: int inserted: int failed: int errors: List[SmartIRDetailImportRowError] = [] class NACDetailCreate(BaseModel): month_id: int unit_id: Optional[int] = None name: Optional[str] = None email: Optional[str] = None status: Optional[int] = None class NACDetail(BaseModel): id: int month_id: int unit_id: Optional[int] = None unit_name: Optional[str] = None name: Optional[str] = None email: Optional[str] = None status: Optional[int] = None created_at: datetime.datetime class Config: from_attributes = True class NACDetailImportRowError(BaseModel): row: int error: str class NACDetailImportResponse(BaseModel): message: str total: int inserted: int failed: int errors: List[NACDetailImportRowError] = [] class ComplianceDetailCreate(BaseModel): month_id: int unit_id: Optional[int] = None vnpt_ma_nhan_vien: Optional[str] = None name: Optional[str] = None email: Optional[str] = None phong_ban: Optional[str] = None windows_key: Optional[int] = None office: Optional[int] = None ms17010: Optional[int] = None firewall: Optional[int] = None uac: Optional[int] = None winrar: Optional[int] = None antivirus: Optional[int] = None update_win: Optional[int] = None status: Optional[int] = None class ComplianceDetail(BaseModel): id: int month_id: int unit_id: Optional[int] = None unit_name: Optional[str] = None vnpt_ma_nhan_vien: Optional[str] = None name: Optional[str] = None email: Optional[str] = None phong_ban: Optional[str] = None windows_key: Optional[int] = None office: Optional[int] = None ms17010: Optional[int] = None firewall: Optional[int] = None uac: Optional[int] = None winrar: Optional[int] = None antivirus: Optional[int] = None update_win: Optional[int] = None status: Optional[int] = None created_at: datetime.datetime class Config: from_attributes = True class ComplianceDetailImportRowError(BaseModel): row: int error: str class ComplianceDetailImportResponse(BaseModel): message: str total: int inserted: int failed: int errors: List[ComplianceDetailImportRowError] = [] class ComplianceSmartIRCreate(BaseModel): month_id: int unit_id: Optional[int] = None vnpt_ma_nhan_vien: str name: str email: str phong_ban: Optional[str] = None ten_may_tinh: Optional[str] = None mac: Optional[str] = None active_windows: Optional[int] = None bat_windows_firewall: Optional[int] = None bat_uac: Optional[int] = None cai_winrar_ban_moi: Optional[int] = None bat_av_fullscan: Optional[int] = None update_windows: Optional[int] = None status: int = 0 class ComplianceSmartIR(BaseModel): id: int month_id: int unit_id: Optional[int] = None unit_name: Optional[str] = None vnpt_ma_nhan_vien: str name: str email: str phong_ban: Optional[str] = None ten_may_tinh: Optional[str] = None mac: Optional[str] = None active_windows: Optional[int] = None bat_windows_firewall: Optional[int] = None bat_uac: Optional[int] = None cai_winrar_ban_moi: Optional[int] = None bat_av_fullscan: Optional[int] = None update_windows: Optional[int] = None status: int created_at: datetime.datetime updated_at: datetime.datetime class Config: from_attributes = True class ComplianceSmartIRImportError(BaseModel): row: int error: str class ComplianceSmartIRImportResponse(BaseModel): message: str total: int upserted: int failed: int errors: List[ComplianceSmartIRImportError] = [] class MMFintechCreate(BaseModel): month_id: int unit_id: Optional[int] = None file_path: str class MMFintech(BaseModel): id: int month_id: int unit_id: Optional[int] = None file_path: str created_at: datetime.datetime class Config: from_attributes = True class ConvertHardeningCreate(BaseModel): filename: str user_id: int class ConvertHardening(BaseModel): id: int filename: str user_id: int created_at: datetime.datetime class Config: from_attributes = True class EmailRoleBase(BaseModel): name: str description: Optional[str] = None class EmailRoleCreate(EmailRoleBase): pass class EmailRoleUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None class EmailRole(BaseModel): id: int name: str description: Optional[str] = None class Config: from_attributes = True class EmailUserBase(BaseModel): email: EmailStr name: Optional[str] = None role_id: int type: int class EmailUserCreate(EmailUserBase): pass class EmailUserUpdate(BaseModel): email: Optional[EmailStr] = None name: Optional[str] = None role_id: Optional[int] = None type: Optional[int] = None class EmailUser(BaseModel): id: int email: EmailStr name: Optional[str] = None role_id: int type: int class Config: from_attributes = True class DocumentBase(BaseModel): name: str number_doc: str sign_date: Optional[datetime.datetime] = None level: int validate_date: Optional[datetime.datetime] = None file_path: Optional[str] = None class DocumentCreate(DocumentBase): pass class DocumentUpdate(BaseModel): name: Optional[str] = None number_doc: Optional[str] = None sign_date: Optional[datetime.datetime] = None level: Optional[int] = None validate_date: Optional[datetime.datetime] = None file_path: Optional[str] = None class Document(BaseModel): id: int name: str number_doc: str sign_date: Optional[datetime.datetime] = None level: int validate_date: Optional[datetime.datetime] = None file_path: Optional[str] = None class Config: from_attributes = True