gitscope
https://dev.kronoz.win/r/marzneshin.git
app/models/user.py · v0.3.0-alpha3
Source
1 12b255b Dawsh 2y ago import secrets
2 a47eb4c Dawsh 2y ago from datetime import datetime
3 a47eb4c Dawsh 2y ago from enum import Enum
4 857e289 Mrdn 2y ago from typing import List, Optional, Union, Annotated, Literal
5 a47eb4c Dawsh 2y ago
6 01cdf2e Dawsh 2y ago from pydantic import (
7 01cdf2e Dawsh 2y ago field_validator,
8 01cdf2e Dawsh 2y ago ConfigDict,
9 01cdf2e Dawsh 2y ago BaseModel,
10 01cdf2e Dawsh 2y ago Field,
11 01cdf2e Dawsh 2y ago computed_field,
12 01cdf2e Dawsh 2y ago StringConstraints,
13 857e289 Mrdn 2y ago model_validator,
14 01cdf2e Dawsh 2y ago )
15 a47eb4c Dawsh 2y ago
16 6a606d6 Dawsh 2y ago from config import XRAY_SUBSCRIPTION_URL_PREFIX
17 a47eb4c Dawsh 2y ago
18 f446990 Dawsh 2y ago USERNAME_REGEXP = r"^\w{3,32}$"
19 a47eb4c Dawsh 2y ago
20 a47eb4c Dawsh 2y ago
21 a47eb4c Dawsh 2y ago class ReminderType(str, Enum):
22 a47eb4c Dawsh 2y ago expiration_date = "expiration_date"
23 a47eb4c Dawsh 2y ago data_usage = "data_usage"
24 a47eb4c Dawsh 2y ago
25 a47eb4c Dawsh 2y ago
26 a47eb4c Dawsh 2y ago class UserStatus(str, Enum):
27 a47eb4c Dawsh 2y ago active = "active"
28 a47eb4c Dawsh 2y ago disabled = "disabled"
29 a47eb4c Dawsh 2y ago limited = "limited"
30 a47eb4c Dawsh 2y ago expired = "expired"
31 a47eb4c Dawsh 2y ago on_hold = "on_hold"
32 a47eb4c Dawsh 2y ago
33 12b255b Dawsh 2y ago
34 a47eb4c Dawsh 2y ago class UserStatusModify(str, Enum):
35 a47eb4c Dawsh 2y ago active = "active"
36 a47eb4c Dawsh 2y ago disabled = "disabled"
37 a47eb4c Dawsh 2y ago on_hold = "on_hold"
38 a47eb4c Dawsh 2y ago
39 a47eb4c Dawsh 2y ago
40 a47eb4c Dawsh 2y ago class UserStatusCreate(str, Enum):
41 a47eb4c Dawsh 2y ago active = "active"
42 a47eb4c Dawsh 2y ago on_hold = "on_hold"
43 a47eb4c Dawsh 2y ago
44 a47eb4c Dawsh 2y ago
45 a47eb4c Dawsh 2y ago class UserDataLimitResetStrategy(str, Enum):
46 a47eb4c Dawsh 2y ago no_reset = "no_reset"
47 a47eb4c Dawsh 2y ago day = "day"
48 a47eb4c Dawsh 2y ago week = "week"
49 a47eb4c Dawsh 2y ago month = "month"
50 a47eb4c Dawsh 2y ago year = "year"
51 a47eb4c Dawsh 2y ago
52 a47eb4c Dawsh 2y ago
53 a47eb4c Dawsh 2y ago class UserBase(BaseModel):
54 912788f Mardin 2y ago id: Optional[int] = None
55 f446990 Dawsh 2y ago username: Annotated[str, StringConstraints(to_lower=True, pattern=USERNAME_REGEXP)]
56 857e289 Mrdn 2y ago expire: Union[datetime, None, Literal[0]] = Field(None)
57 a47eb4c Dawsh 2y ago key: str = Field(default_factory=lambda: secrets.token_hex(16))
58 a47eb4c Dawsh 2y ago data_limit: Optional[int] = Field(
59 a47eb4c Dawsh 2y ago ge=0, default=None, description="data_limit can be 0 or greater"
60 a47eb4c Dawsh 2y ago )
61 a47eb4c Dawsh 2y ago data_limit_reset_strategy: UserDataLimitResetStrategy = (
62 a47eb4c Dawsh 2y ago UserDataLimitResetStrategy.no_reset
63 a47eb4c Dawsh 2y ago )
64 f446990 Dawsh 2y ago note: Optional[Annotated[str, Field(max_length=500)]] = None
65 857e289 Mrdn 2y ago sub_updated_at: Optional[datetime] = Field(None)
66 857e289 Mrdn 2y ago sub_last_user_agent: Optional[str] = Field(None)
67 857e289 Mrdn 2y ago online_at: Optional[datetime] = Field(None)
68 857e289 Mrdn 2y ago on_hold_expire_duration: Optional[int] = Field(None)
69 857e289 Mrdn 2y ago on_hold_timeout: Optional[datetime] = Field(None)
70 a47eb4c Dawsh 2y ago
71 a47eb4c Dawsh 2y ago model_config = ConfigDict(from_attributes=True)
72 a47eb4c Dawsh 2y ago
73 4d647dd Dawsh 2y ago
74 a47eb4c Dawsh 2y ago from app.models.service import ServiceBase
75 f6a32ea Dawsh 2y ago from app.models.proxy import InboundBase
76 a47eb4c Dawsh 2y ago
77 4d647dd Dawsh 2y ago
78 6457378 Dawsh 2y ago class User(UserBase):
79 a47eb4c Dawsh 2y ago pass
80 a47eb4c Dawsh 2y ago
81 a47eb4c Dawsh 2y ago
82 a47eb4c Dawsh 2y ago class UserCreate(User):
83 857e289 Mrdn 2y ago status: UserStatusCreate = Field(UserStatusCreate.active)
84 a47eb4c Dawsh 2y ago services: List[int] = Field([])
85 01cdf2e Dawsh 2y ago model_config = ConfigDict(
86 01cdf2e Dawsh 2y ago json_schema_extra={
87 01cdf2e Dawsh 2y ago "example": {
88 01cdf2e Dawsh 2y ago "username": "user1234",
89 01cdf2e Dawsh 2y ago "services": [1, 2, 3],
90 3b7feef Mrdn 2y ago "expire": "2023-11-03T20:30:00",
91 01cdf2e Dawsh 2y ago "data_limit": 0,
92 01cdf2e Dawsh 2y ago "data_limit_reset_strategy": "no_reset",
93 01cdf2e Dawsh 2y ago "status": "active",
94 01cdf2e Dawsh 2y ago "note": "",
95 01cdf2e Dawsh 2y ago "on_hold_timeout": "2023-11-03T20:30:00",
96 01cdf2e Dawsh 2y ago "on_hold_expire_duration": 0,
97 01cdf2e Dawsh 2y ago }
98 a47eb4c Dawsh 2y ago }
99 01cdf2e Dawsh 2y ago )
100 a47eb4c Dawsh 2y ago
101 857e289 Mrdn 2y ago @model_validator(mode="after")
102 857e289 Mrdn 2y ago def validate_expiry(self):
103 857e289 Mrdn 2y ago if self.status == UserStatusCreate.on_hold:
104 857e289 Mrdn 2y ago if not self.on_hold_expire_duration:
105 01cdf2e Dawsh 2y ago raise ValueError(
106 01cdf2e Dawsh 2y ago "User cannot be on hold without a valid on_hold_expire_duration."
107 01cdf2e Dawsh 2y ago )
108 857e289 Mrdn 2y ago if self.expire:
109 a47eb4c Dawsh 2y ago raise ValueError("User cannot be on hold with specified expire.")
110 857e289 Mrdn 2y ago elif self.status == UserStatusCreate.active:
111 857e289 Mrdn 2y ago if not self.expire:
112 857e289 Mrdn 2y ago raise ValueError("expire field should be set when status is active.")
113 857e289 Mrdn 2y ago if self.on_hold_expire_duration or self.on_hold_timeout:
114 857e289 Mrdn 2y ago raise ValueError(
115 857e289 Mrdn 2y ago "on hold parametrs set when user status isn't on_hold."
116 857e289 Mrdn 2y ago )
117 857e289 Mrdn 2y ago return self
118 857e289 Mrdn 2y ago
119 857e289 Mrdn 2y ago @field_validator("expire")
120 857e289 Mrdn 2y ago @classmethod
121 f446990 Dawsh 2y ago def validate_expire(cls, v: Union[datetime, None, Literal[0]]):
122 f446990 Dawsh 2y ago if isinstance(v, datetime) and (v.tzinfo is not None):
123 857e289 Mrdn 2y ago raise ValueError(
124 857e289 Mrdn 2y ago "Expire date should be offset naive, and preferably in utc timezone."
125 857e289 Mrdn 2y ago )
126 857e289 Mrdn 2y ago if isinstance(v, datetime) and v < datetime.utcnow():
127 857e289 Mrdn 2y ago raise ValueError("Expire date should be in the future, not in the past.")
128 857e289 Mrdn 2y ago return v
129 a47eb4c Dawsh 2y ago
130 a47eb4c Dawsh 2y ago
131 a47eb4c Dawsh 2y ago class UserModify(User):
132 9bfa731 Dawsh 2y ago status: Optional[UserStatusModify] = Field(None)
133 9bfa731 Dawsh 2y ago services: List[int] = Field([])
134 9bfa731 Dawsh 2y ago data_limit_reset_strategy: Optional[UserDataLimitResetStrategy] = Field(None)
135 01cdf2e Dawsh 2y ago model_config = ConfigDict(
136 01cdf2e Dawsh 2y ago json_schema_extra={
137 01cdf2e Dawsh 2y ago "example": {
138 01cdf2e Dawsh 2y ago "username": "mammad1234",
139 01cdf2e Dawsh 2y ago "services": [1, 2, 3],
140 01cdf2e Dawsh 2y ago "expire": 0,
141 01cdf2e Dawsh 2y ago "data_limit": 0,
142 01cdf2e Dawsh 2y ago "data_limit_reset_strategy": "no_reset",
143 01cdf2e Dawsh 2y ago "status": "active",
144 01cdf2e Dawsh 2y ago "note": "",
145 01cdf2e Dawsh 2y ago "on_hold_timeout": "2023-11-03T20:30:00",
146 01cdf2e Dawsh 2y ago "on_hold_expire_duration": 0,
147 01cdf2e Dawsh 2y ago }
148 a47eb4c Dawsh 2y ago }
149 01cdf2e Dawsh 2y ago )
150 a47eb4c Dawsh 2y ago
151 857e289 Mrdn 2y ago @model_validator(mode="after")
152 857e289 Mrdn 2y ago def validate_expiry(self):
153 857e289 Mrdn 2y ago if self.on_hold_expire_duration and self.expire:
154 857e289 Mrdn 2y ago raise ValueError(
155 857e289 Mrdn 2y ago "can't set both expire and on hold expire at the same time."
156 857e289 Mrdn 2y ago )
157 857e289 Mrdn 2y ago return self
158 857e289 Mrdn 2y ago
159 857e289 Mrdn 2y ago @field_validator("expire")
160 857e289 Mrdn 2y ago @classmethod
161 f446990 Dawsh 2y ago def validate_expire(cls, v: Union[datetime, None, Literal[0]]):
162 857e289 Mrdn 2y ago if isinstance(v, datetime) and v.tzinfo is not None:
163 857e289 Mrdn 2y ago raise ValueError(
164 857e289 Mrdn 2y ago "Expire date should be offset naive, and preferably in utc timezone."
165 857e289 Mrdn 2y ago )
166 857e289 Mrdn 2y ago if isinstance(v, datetime) and v < datetime.utcnow():
167 857e289 Mrdn 2y ago raise ValueError("Expire date should be in the future, not in the past.")
168 857e289 Mrdn 2y ago return v
169 a47eb4c Dawsh 2y ago
170 a47eb4c Dawsh 2y ago
171 a47eb4c Dawsh 2y ago class UserResponse(User):
172 a47eb4c Dawsh 2y ago id: int
173 a47eb4c Dawsh 2y ago status: UserStatus
174 a47eb4c Dawsh 2y ago used_traffic: int
175 a47eb4c Dawsh 2y ago lifetime_used_traffic: int = 0
176 a47eb4c Dawsh 2y ago created_at: datetime
177 a47eb4c Dawsh 2y ago inbounds: List[InboundBase] = Field([])
178 a47eb4c Dawsh 2y ago services: List[ServiceBase] = Field([])
179 f7af2b3 Dawsh 2y ago links: List[str] | None = [] # Field(None)
180 9bfa731 Dawsh 2y ago
181 a47eb4c Dawsh 2y ago # subscription_url: str = ""
182 a47eb4c Dawsh 2y ago model_config = ConfigDict(from_attributes=True)
183 9bfa731 Dawsh 2y ago
184 5f664c5 Dawsh 2y ago @computed_field
185 5f664c5 Dawsh 2y ago @property
186 5f664c5 Dawsh 2y ago def service_ids(self) -> List[int]:
187 5f664c5 Dawsh 2y ago return [s.id for s in self.services]
188 5f664c5 Dawsh 2y ago
189 a47eb4c Dawsh 2y ago @computed_field
190 a47eb4c Dawsh 2y ago @property
191 a47eb4c Dawsh 2y ago def subscription_url(self) -> str:
192 a47eb4c Dawsh 2y ago salt = secrets.token_hex(8)
193 857e289 Mrdn 2y ago url_prefix = XRAY_SUBSCRIPTION_URL_PREFIX.replace("*", salt)
194 6a606d6 Dawsh 2y ago return f"{url_prefix}/sub/{self.username}/{self.key}"
195 a47eb4c Dawsh 2y ago
196 a47eb4c Dawsh 2y ago
197 a47eb4c Dawsh 2y ago class UserUsageResponse(BaseModel):
198 a47eb4c Dawsh 2y ago node_id: Union[int, None] = None
199 a47eb4c Dawsh 2y ago node_name: str
200 a47eb4c Dawsh 2y ago used_traffic: int
201 a47eb4c Dawsh 2y ago
202 a47eb4c Dawsh 2y ago
203 a47eb4c Dawsh 2y ago class UserUsagesResponse(BaseModel):
204 a47eb4c Dawsh 2y ago username: str
205 a47eb4c Dawsh 2y ago usages: List[UserUsageResponse]