gitscope
https://dev.kronoz.win/r/marzneshin.git
app/models/user.py · v0.1.0
Source
1 a47eb4c Dawsh 2y ago import re
2 12b255b Dawsh 2y ago import secrets
3 a47eb4c Dawsh 2y ago from datetime import datetime
4 a47eb4c Dawsh 2y ago from enum import Enum
5 6457378 Dawsh 2y ago from typing import List, Optional, Union, Annotated
6 a47eb4c Dawsh 2y ago
7 6457378 Dawsh 2y ago from pydantic import field_validator, ConfigDict, BaseModel, Field, validator, computed_field, ValidationInfo, constr, \
8 6457378 Dawsh 2y ago StringConstraints
9 a47eb4c Dawsh 2y ago
10 6a606d6 Dawsh 2y ago from config import XRAY_SUBSCRIPTION_URL_PREFIX
11 a47eb4c Dawsh 2y ago
12 8b9d507 Dawsh 2y ago USERNAME_REGEXP = re.compile(r"^\w{3,32}$")
13 a47eb4c Dawsh 2y ago
14 a47eb4c Dawsh 2y ago
15 a47eb4c Dawsh 2y ago class ReminderType(str, Enum):
16 a47eb4c Dawsh 2y ago expiration_date = "expiration_date"
17 a47eb4c Dawsh 2y ago data_usage = "data_usage"
18 a47eb4c Dawsh 2y ago
19 a47eb4c Dawsh 2y ago
20 a47eb4c Dawsh 2y ago class UserStatus(str, Enum):
21 a47eb4c Dawsh 2y ago active = "active"
22 a47eb4c Dawsh 2y ago disabled = "disabled"
23 a47eb4c Dawsh 2y ago limited = "limited"
24 a47eb4c Dawsh 2y ago expired = "expired"
25 a47eb4c Dawsh 2y ago on_hold = "on_hold"
26 a47eb4c Dawsh 2y ago
27 12b255b Dawsh 2y ago
28 f7af2b3 Dawsh 2y ago # from app.utils.share import generate_v2ray_links
29 a47eb4c Dawsh 2y ago
30 12b255b Dawsh 2y ago
31 a47eb4c Dawsh 2y ago class UserStatusModify(str, Enum):
32 a47eb4c Dawsh 2y ago active = "active"
33 a47eb4c Dawsh 2y ago disabled = "disabled"
34 a47eb4c Dawsh 2y ago on_hold = "on_hold"
35 a47eb4c Dawsh 2y ago
36 a47eb4c Dawsh 2y ago
37 a47eb4c Dawsh 2y ago class UserStatusCreate(str, Enum):
38 a47eb4c Dawsh 2y ago active = "active"
39 a47eb4c Dawsh 2y ago on_hold = "on_hold"
40 a47eb4c Dawsh 2y ago
41 a47eb4c Dawsh 2y ago
42 a47eb4c Dawsh 2y ago class UserDataLimitResetStrategy(str, Enum):
43 a47eb4c Dawsh 2y ago no_reset = "no_reset"
44 a47eb4c Dawsh 2y ago day = "day"
45 a47eb4c Dawsh 2y ago week = "week"
46 a47eb4c Dawsh 2y ago month = "month"
47 a47eb4c Dawsh 2y ago year = "year"
48 a47eb4c Dawsh 2y ago
49 a47eb4c Dawsh 2y ago
50 a47eb4c Dawsh 2y ago class UserBase(BaseModel):
51 a47eb4c Dawsh 2y ago # proxies: Dict[ProxyTypes, ProxySettings] = {}
52 912788f Mardin 2y ago id: Optional[int] = None
53 6457378 Dawsh 2y ago username: Annotated[str, StringConstraints(to_lower=True)]
54 a47eb4c Dawsh 2y ago expire: Optional[int] = Field(None, nullable=True)
55 a47eb4c Dawsh 2y ago key: str = Field(default_factory=lambda: secrets.token_hex(16))
56 a47eb4c Dawsh 2y ago data_limit: Optional[int] = Field(
57 a47eb4c Dawsh 2y ago ge=0, default=None, description="data_limit can be 0 or greater"
58 a47eb4c Dawsh 2y ago )
59 a47eb4c Dawsh 2y ago data_limit_reset_strategy: UserDataLimitResetStrategy = (
60 a47eb4c Dawsh 2y ago UserDataLimitResetStrategy.no_reset
61 a47eb4c Dawsh 2y ago )
62 a47eb4c Dawsh 2y ago # inbounds: Dict[ProxyTypes, List[str]] = {}
63 a47eb4c Dawsh 2y ago note: Optional[str] = Field(None, nullable=True)
64 a47eb4c Dawsh 2y ago sub_updated_at: Optional[datetime] = Field(None, nullable=True)
65 a47eb4c Dawsh 2y ago sub_last_user_agent: Optional[str] = Field(None, nullable=True)
66 a47eb4c Dawsh 2y ago online_at: Optional[datetime] = Field(None, nullable=True)
67 a47eb4c Dawsh 2y ago on_hold_expire_duration: Optional[int] = Field(None, nullable=True)
68 a47eb4c Dawsh 2y ago on_hold_timeout: Optional[Union[datetime, None]] = Field(None, nullable=True)
69 a47eb4c Dawsh 2y ago
70 a47eb4c Dawsh 2y ago @field_validator("username")
71 a47eb4c Dawsh 2y ago @classmethod
72 a47eb4c Dawsh 2y ago def validate_username(cls, v: str):
73 a47eb4c Dawsh 2y ago if not USERNAME_REGEXP.match(v):
74 a47eb4c Dawsh 2y ago raise ValueError(
75 8b9d507 Dawsh 2y ago "Username should be 3 to 32 characters containing A-Z, a-z, 0-9 and underscores in between."
76 a47eb4c Dawsh 2y ago )
77 a47eb4c Dawsh 2y ago return v
78 a47eb4c Dawsh 2y ago
79 a47eb4c Dawsh 2y ago @field_validator("note")
80 a47eb4c Dawsh 2y ago @classmethod
81 a47eb4c Dawsh 2y ago def validate_note(cls, v: str):
82 a47eb4c Dawsh 2y ago if v and len(v) > 500:
83 a47eb4c Dawsh 2y ago raise ValueError("User's note can be a maximum of 500 character")
84 a47eb4c Dawsh 2y ago return v
85 a47eb4c Dawsh 2y ago
86 a47eb4c Dawsh 2y ago @field_validator("on_hold_expire_duration", "on_hold_timeout", check_fields=False)
87 a47eb4c Dawsh 2y ago @classmethod
88 a47eb4c Dawsh 2y ago def validate_timeout(cls, v: Optional[int]):
89 a47eb4c Dawsh 2y ago # Check if expire is 0 or None and timeout is not 0 or None
90 a47eb4c Dawsh 2y ago if (v in (0, None)):
91 a47eb4c Dawsh 2y ago return None
92 a47eb4c Dawsh 2y ago return v
93 a47eb4c Dawsh 2y ago model_config = ConfigDict(from_attributes=True)
94 a47eb4c Dawsh 2y ago
95 4d647dd Dawsh 2y ago
96 a47eb4c Dawsh 2y ago from app.models.service import ServiceBase
97 f6a32ea Dawsh 2y ago from app.models.proxy import InboundBase
98 a47eb4c Dawsh 2y ago
99 4d647dd Dawsh 2y ago
100 6457378 Dawsh 2y ago class User(UserBase):
101 a47eb4c Dawsh 2y ago pass
102 a47eb4c Dawsh 2y ago
103 a47eb4c Dawsh 2y ago
104 a47eb4c Dawsh 2y ago class UserCreate(User):
105 a47eb4c Dawsh 2y ago status: UserStatusCreate = Field(None, validate_default=True)
106 a47eb4c Dawsh 2y ago services: List[int] = Field([])
107 a47eb4c Dawsh 2y ago model_config = ConfigDict(json_schema_extra={
108 a47eb4c Dawsh 2y ago "example": {
109 a47eb4c Dawsh 2y ago "username": "user1234",
110 a47eb4c Dawsh 2y ago "services": [1, 2, 3],
111 a47eb4c Dawsh 2y ago "expire": 0,
112 a47eb4c Dawsh 2y ago "data_limit": 0,
113 a47eb4c Dawsh 2y ago "data_limit_reset_strategy": "no_reset",
114 a47eb4c Dawsh 2y ago "status": "active",
115 a47eb4c Dawsh 2y ago "note": "",
116 a47eb4c Dawsh 2y ago "on_hold_timeout": "2023-11-03T20:30:00",
117 a47eb4c Dawsh 2y ago "on_hold_expire_duration": 0,
118 a47eb4c Dawsh 2y ago }
119 a47eb4c Dawsh 2y ago })
120 a47eb4c Dawsh 2y ago
121 a47eb4c Dawsh 2y ago # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
122 a47eb4c Dawsh 2y ago # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
123 a47eb4c Dawsh 2y ago #@validator("status", pre=True, always=True)
124 a47eb4c Dawsh 2y ago #def validate_status(cls, value):
125 a47eb4c Dawsh 2y ago
126 a47eb4c Dawsh 2y ago # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
127 a47eb4c Dawsh 2y ago # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
128 a47eb4c Dawsh 2y ago @field_validator("status", mode="before")
129 a47eb4c Dawsh 2y ago def validate_status(cls, status: str, info: ValidationInfo) -> str:
130 a47eb4c Dawsh 2y ago if not status or status not in UserStatusCreate.__members__:
131 a47eb4c Dawsh 2y ago return UserStatusCreate.active # Set to the default if not valid
132 a47eb4c Dawsh 2y ago on_hold_expire = info.data.get("on_hold_expire_duration")
133 a47eb4c Dawsh 2y ago expire = info.data.get("expire")
134 a47eb4c Dawsh 2y ago if status == UserStatusCreate.on_hold:
135 4d647dd Dawsh 2y ago if on_hold_expire == 0 or on_hold_expire is None:
136 a47eb4c Dawsh 2y ago raise ValueError("User cannot be on hold without a valid on_hold_expire_duration.")
137 a47eb4c Dawsh 2y ago if expire:
138 a47eb4c Dawsh 2y ago raise ValueError("User cannot be on hold with specified expire.")
139 a47eb4c Dawsh 2y ago return status
140 a47eb4c Dawsh 2y ago
141 a47eb4c Dawsh 2y ago
142 a47eb4c Dawsh 2y ago class UserModify(User):
143 9bfa731 Dawsh 2y ago status: Optional[UserStatusModify] = Field(None)
144 9bfa731 Dawsh 2y ago services: List[int] = Field([])
145 9bfa731 Dawsh 2y ago data_limit_reset_strategy: Optional[UserDataLimitResetStrategy] = Field(None)
146 a47eb4c Dawsh 2y ago model_config = ConfigDict(json_schema_extra={
147 a47eb4c Dawsh 2y ago "example": {
148 a47eb4c Dawsh 2y ago "username": "mammad1234",
149 a47eb4c Dawsh 2y ago "services": [1, 2, 3],
150 a47eb4c Dawsh 2y ago "expire": 0,
151 a47eb4c Dawsh 2y ago "data_limit": 0,
152 a47eb4c Dawsh 2y ago "data_limit_reset_strategy": "no_reset",
153 a47eb4c Dawsh 2y ago "status": "active",
154 a47eb4c Dawsh 2y ago "note": "",
155 a47eb4c Dawsh 2y ago "on_hold_timeout": "2023-11-03T20:30:00",
156 a47eb4c Dawsh 2y ago "on_hold_expire_duration": 0,
157 a47eb4c Dawsh 2y ago }
158 a47eb4c Dawsh 2y ago })
159 a47eb4c Dawsh 2y ago
160 a47eb4c Dawsh 2y ago
161 a47eb4c Dawsh 2y ago # TODO[pydantic]: We couldn't refactor the `validator`, please replace it by `field_validator` manually.
162 a47eb4c Dawsh 2y ago # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-validators for more information.
163 a47eb4c Dawsh 2y ago @validator("status", pre=True, always=True)
164 a47eb4c Dawsh 2y ago def validate_status(cls, status, values):
165 a47eb4c Dawsh 2y ago on_hold_expire = values.get("on_hold_expire_duration")
166 a47eb4c Dawsh 2y ago expire = values.get("expire")
167 a47eb4c Dawsh 2y ago if status == UserStatusCreate.on_hold:
168 a47eb4c Dawsh 2y ago if not on_hold_expire:
169 a47eb4c Dawsh 2y ago raise ValueError("User cannot be on hold without a valid on_hold_expire_duration.")
170 a47eb4c Dawsh 2y ago if expire:
171 a47eb4c Dawsh 2y ago raise ValueError("User cannot be on hold with specified expire.")
172 a47eb4c Dawsh 2y ago return status
173 a47eb4c Dawsh 2y ago
174 a47eb4c Dawsh 2y ago
175 a47eb4c Dawsh 2y ago class UserResponse(User):
176 a47eb4c Dawsh 2y ago id: int
177 a47eb4c Dawsh 2y ago status: UserStatus
178 a47eb4c Dawsh 2y ago used_traffic: int
179 a47eb4c Dawsh 2y ago lifetime_used_traffic: int = 0
180 a47eb4c Dawsh 2y ago created_at: datetime
181 a47eb4c Dawsh 2y ago inbounds: List[InboundBase] = Field([])
182 a47eb4c Dawsh 2y ago services: List[ServiceBase] = Field([])
183 f7af2b3 Dawsh 2y ago links: List[str] | None = [] # Field(None)
184 9bfa731 Dawsh 2y ago
185 a47eb4c Dawsh 2y ago # subscription_url: str = ""
186 a47eb4c Dawsh 2y ago model_config = ConfigDict(from_attributes=True)
187 9bfa731 Dawsh 2y ago
188 5f664c5 Dawsh 2y ago @computed_field
189 5f664c5 Dawsh 2y ago @property
190 5f664c5 Dawsh 2y ago def service_ids(self) -> List[int]:
191 5f664c5 Dawsh 2y ago return [s.id for s in self.services]
192 5f664c5 Dawsh 2y ago
193 a47eb4c Dawsh 2y ago @computed_field
194 a47eb4c Dawsh 2y ago @property
195 a47eb4c Dawsh 2y ago def subscription_url(self) -> str:
196 a47eb4c Dawsh 2y ago salt = secrets.token_hex(8)
197 a47eb4c Dawsh 2y ago url_prefix = (XRAY_SUBSCRIPTION_URL_PREFIX).replace('*', salt)
198 6a606d6 Dawsh 2y ago return f"{url_prefix}/sub/{self.username}/{self.key}"
199 a47eb4c Dawsh 2y ago
200 a47eb4c Dawsh 2y ago
201 a47eb4c Dawsh 2y ago class UsersResponse(BaseModel):
202 a47eb4c Dawsh 2y ago total: int
203 a47eb4c Dawsh 2y ago users: List[UserResponse]
204 a47eb4c Dawsh 2y ago
205 a47eb4c Dawsh 2y ago
206 a47eb4c Dawsh 2y ago class UserUsageResponse(BaseModel):
207 a47eb4c Dawsh 2y ago node_id: Union[int, None] = None
208 a47eb4c Dawsh 2y ago node_name: str
209 a47eb4c Dawsh 2y ago used_traffic: int
210 a47eb4c Dawsh 2y ago
211 a47eb4c Dawsh 2y ago
212 a47eb4c Dawsh 2y ago class UserUsagesResponse(BaseModel):
213 a47eb4c Dawsh 2y ago username: str
214 a47eb4c Dawsh 2y ago usages: List[UserUsageResponse]