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