Coverage for modules\authentication\schemas.py: 90%

100 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-10-17 16:50 +0800

1import re 

2from typing import Optional, List 

3from pydantic import BaseModel, ConfigDict, field_validator 

4from uuid import UUID 

5from datetime import datetime 

6 

7 

8def validate_email_field(value: str) -> str: 

9 if value is None: 

10 raise ValueError("Email is required.") 

11 

12 email = str(value).strip() 

13 if not email: 

14 raise ValueError("Email is required.") 

15 

16 if "@" not in email: 

17 raise ValueError("Enter a valid email address.") 

18 

19 local_part, domain = email.rsplit("@", 1) 

20 if not local_part or not domain: 

21 raise ValueError("Enter a valid email address.") 

22 

23 if domain.startswith(".") or domain.endswith("."): 

24 raise ValueError("Enter a valid email address.") 

25 

26 if not re.fullmatch(r"^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9.-]+\.[A-Za-z0-9-]+$", email): 

27 raise ValueError("Enter a valid email address.") 

28 

29 return email.lower() 

30 

31 

32class RegisterIn(BaseModel): 

33 email: str 

34 password: str 

35 first_name: str 

36 last_name: str 

37 organization_name: str 

38 

39 @field_validator("email") 

40 @classmethod 

41 def validate_email(cls, value: str) -> str: 

42 return validate_email_field(value) 

43 

44 

45class LoginIn(BaseModel): 

46 email: str 

47 password: str 

48 

49 @field_validator("email") 

50 @classmethod 

51 def validate_email(cls, value: str) -> str: 

52 return validate_email_field(value) 

53 

54class RefreshIn(BaseModel): 

55 refresh_token: str 

56 

57class PasswordResetRequestIn(BaseModel): 

58 email: str 

59 

60 @field_validator("email") 

61 @classmethod 

62 def validate_email(cls, value: str) -> str: 

63 return validate_email_field(value) 

64 

65class PasswordResetConfirmIn(BaseModel): 

66 token: str 

67 password: str 

68 

69 @field_validator("password") 

70 @classmethod 

71 def validate_password(cls, value: str) -> str: 

72 if len(value) < 8: 

73 raise ValueError("Password must be at least 8 characters.") 

74 return value 

75 

76class SwitchOrgIn(BaseModel): 

77 organization_id: UUID 

78 

79class UserUpdateIn(BaseModel): 

80 first_name: Optional[str] = None 

81 last_name: Optional[str] = None 

82 phone_number: Optional[str] = None 

83 avatar_url: Optional[str] = None 

84 

85class OrganizationOut(BaseModel): 

86 id: UUID 

87 name: str 

88 slug: str 

89 logo_url: Optional[str] = None 

90 currency: str 

91 fiscal_year_start: int 

92 tax_id: Optional[str] = None 

93 website: Optional[str] = None 

94 address: Optional[str] = None 

95 created_at: datetime 

96 updated_at: datetime 

97 

98 model_config = ConfigDict(from_attributes=True) 

99 

100class UserOut(BaseModel): 

101 id: UUID 

102 email: str 

103 first_name: str 

104 last_name: str 

105 full_name: str 

106 avatar_url: Optional[str] = None 

107 phone_number: Optional[str] = None 

108 is_active: bool = True 

109 is_staff: bool = False 

110 is_superuser: bool = False 

111 current_organization_id: Optional[UUID] = None 

112 role: Optional[str] = None 

113 created_at: datetime 

114 updated_at: datetime 

115 

116 model_config = ConfigDict(from_attributes=True) 

117 

118class TokenOut(BaseModel): 

119 access_token: str 

120 refresh_token: str 

121 token_type: str = "Bearer" 

122 expires_in: int 

123 

124class AuthResponse(BaseModel): 

125 user: UserOut 

126 organization: Optional[OrganizationOut] = None 

127 tokens: TokenOut