gitscope
https://dev.kronoz.win/r/marzneshin.git
app/routes/user.py · v0.3.0-alpha8
Source
1 f7af2b3 Dawsh 2y ago import asyncio
2 f7af2b3 Dawsh 2y ago import logging
3 f7af2b3 Dawsh 2y ago from datetime import datetime, timezone
4 d4ec019 Dawsh 2y ago from enum import Enum
5 f7af2b3 Dawsh 2y ago
6 f7af2b3 Dawsh 2y ago import sqlalchemy
7 f7af2b3 Dawsh 2y ago from fastapi import APIRouter
8 f7af2b3 Dawsh 2y ago from fastapi import HTTPException, Query
9 d4ec019 Dawsh 2y ago from fastapi_pagination.ext.sqlalchemy import paginate
10 d4ec019 Dawsh 2y ago from fastapi_pagination.links import Page
11 f7af2b3 Dawsh 2y ago
12 f7af2b3 Dawsh 2y ago from app import marznode
13 d4ec019 Dawsh 2y ago from app.db import crud, User
14 01cdf2e Dawsh 2y ago from app.dependencies import (
15 01cdf2e Dawsh 2y ago DBDep,
16 01cdf2e Dawsh 2y ago AdminDep,
17 01cdf2e Dawsh 2y ago SudoAdminDep,
18 01cdf2e Dawsh 2y ago UserDep,
19 01cdf2e Dawsh 2y ago StartDateDep,
20 01cdf2e Dawsh 2y ago EndDateDep,
21 01cdf2e Dawsh 2y ago )
22 01cdf2e Dawsh 2y ago from app.models.user import (
23 01cdf2e Dawsh 2y ago UserCreate,
24 01cdf2e Dawsh 2y ago UserModify,
25 01cdf2e Dawsh 2y ago UserResponse,
26 01cdf2e Dawsh 2y ago UserStatus,
27 01cdf2e Dawsh 2y ago UserUsagesResponse,
28 01cdf2e Dawsh 2y ago )
29 f7af2b3 Dawsh 2y ago from app.utils import report
30 f7af2b3 Dawsh 2y ago
31 f7af2b3 Dawsh 2y ago logger = logging.getLogger(__name__)
32 01cdf2e Dawsh 2y ago router = APIRouter(prefix="/users", tags=["User"])
33 f7af2b3 Dawsh 2y ago
34 f7af2b3 Dawsh 2y ago
35 d4ec019 Dawsh 2y ago class UsersSortingOptions(str, Enum):
36 d4ec019 Dawsh 2y ago username = "username"
37 d4ec019 Dawsh 2y ago used_traffic = "used_traffic"
38 d4ec019 Dawsh 2y ago data_limit = "data_limit"
39 d4ec019 Dawsh 2y ago expire = "expire"
40 d4ec019 Dawsh 2y ago created_at = "created_at"
41 d4ec019 Dawsh 2y ago
42 d4ec019 Dawsh 2y ago
43 d4ec019 Dawsh 2y ago @router.get("", response_model=Page[UserResponse])
44 01cdf2e Dawsh 2y ago def get_users(
45 01cdf2e Dawsh 2y ago db: DBDep,
46 01cdf2e Dawsh 2y ago admin: AdminDep,
47 01cdf2e Dawsh 2y ago username: list[str] = Query(None),
48 01cdf2e Dawsh 2y ago status: list[UserStatus] = Query(None),
49 01cdf2e Dawsh 2y ago order_by: UsersSortingOptions = Query(None),
50 01cdf2e Dawsh 2y ago descending: bool = Query(False),
51 01cdf2e Dawsh 2y ago ):
52 d4ec019 Dawsh 2y ago """
53 d4ec019 Dawsh 2y ago Filters users based on the options
54 d4ec019 Dawsh 2y ago """
55 d4ec019 Dawsh 2y ago dbadmin = crud.get_admin(db, admin.username)
56 d4ec019 Dawsh 2y ago
57 d4ec019 Dawsh 2y ago query = db.query(User)
58 d4ec019 Dawsh 2y ago
59 d4ec019 Dawsh 2y ago admin = dbadmin if not admin.is_sudo else None
60 d4ec019 Dawsh 2y ago
61 d4ec019 Dawsh 2y ago if username:
62 d4ec019 Dawsh 2y ago if len(username) > 1:
63 d4ec019 Dawsh 2y ago query = query.filter(User.username.in_(username))
64 d4ec019 Dawsh 2y ago else:
65 d4ec019 Dawsh 2y ago query = query.filter(User.username.ilike(f"%{username[0]}%"))
66 d4ec019 Dawsh 2y ago
67 d4ec019 Dawsh 2y ago if status:
68 d4ec019 Dawsh 2y ago query = query.filter(User.status.in_(status))
69 d4ec019 Dawsh 2y ago
70 d4ec019 Dawsh 2y ago if admin:
71 d4ec019 Dawsh 2y ago query = query.filter(User.admin == admin)
72 d4ec019 Dawsh 2y ago
73 d4ec019 Dawsh 2y ago if order_by:
74 d4ec019 Dawsh 2y ago order_column = getattr(User, order_by)
75 d4ec019 Dawsh 2y ago if descending:
76 d4ec019 Dawsh 2y ago order_column = order_column.desc()
77 d4ec019 Dawsh 2y ago query = query.order_by(order_column)
78 d4ec019 Dawsh 2y ago
79 d4ec019 Dawsh 2y ago return paginate(db, query)
80 d4ec019 Dawsh 2y ago
81 d4ec019 Dawsh 2y ago
82 5bb2712 Mardin 2y ago @router.post("", response_model=UserResponse)
83 01cdf2e Dawsh 2y ago async def add_user(new_user: UserCreate, db: DBDep, admin: AdminDep):
84 f7af2b3 Dawsh 2y ago """
85 f7af2b3 Dawsh 2y ago Add a new user
86 f7af2b3 Dawsh 2y ago
87 f7af2b3 Dawsh 2y ago - **username** must have 3 to 32 characters and is allowed to contain a-z, 0-9, and underscores in between
88 41ba8cb Dawsh 2y ago - **expire** must be a UTC timestamp
89 f7af2b3 Dawsh 2y ago - **data_limit** must be in Bytes, e.g. 1073741824B = 1GB
90 f7af2b3 Dawsh 2y ago - **services** list of service ids
91 f7af2b3 Dawsh 2y ago """
92 f7af2b3 Dawsh 2y ago # TODO expire should be datetime instead of timestamp
93 f7af2b3 Dawsh 2y ago
94 f7af2b3 Dawsh 2y ago try:
95 01cdf2e Dawsh 2y ago db_user = crud.create_user(
96 01cdf2e Dawsh 2y ago db, new_user, admin=crud.get_admin(db, admin.username)
97 01cdf2e Dawsh 2y ago )
98 f7af2b3 Dawsh 2y ago except sqlalchemy.exc.IntegrityError:
99 f7af2b3 Dawsh 2y ago db.rollback()
100 f7af2b3 Dawsh 2y ago raise HTTPException(status_code=409, detail="User already exists")
101 f7af2b3 Dawsh 2y ago
102 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
103 f6a32ea Dawsh 2y ago await marznode.operations.update_user(user=db_user)
104 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_created(user=user, user_id=db_user.id, by=admin))
105 f7af2b3 Dawsh 2y ago logger.info("New user `%s` added", db_user.username)
106 f7af2b3 Dawsh 2y ago return user
107 f7af2b3 Dawsh 2y ago
108 f7af2b3 Dawsh 2y ago
109 d4ec019 Dawsh 2y ago @router.post("/reset")
110 01cdf2e Dawsh 2y ago async def reset_users_data_usage(db: DBDep, admin: SudoAdminDep):
111 d4ec019 Dawsh 2y ago """
112 d4ec019 Dawsh 2y ago Reset all users data usage,
113 d4ec019 Dawsh 2y ago You will need to restart for this to take effect for now
114 d4ec019 Dawsh 2y ago """
115 d4ec019 Dawsh 2y ago dbadmin = crud.get_admin(db, admin.username)
116 d4ec019 Dawsh 2y ago crud.reset_all_users_data_usage(db=db, admin=dbadmin)
117 d4ec019 Dawsh 2y ago return {}
118 d4ec019 Dawsh 2y ago
119 d4ec019 Dawsh 2y ago
120 d4ec019 Dawsh 2y ago @router.delete("/expired")
121 01cdf2e Dawsh 2y ago def delete_expired(passed_time: int, db: DBDep, admin: AdminDep):
122 d4ec019 Dawsh 2y ago """
123 d4ec019 Dawsh 2y ago Delete expired users
124 d4ec019 Dawsh 2y ago - **passed_time** must be a timestamp
125 d4ec019 Dawsh 2y ago - This function will delete all expired users that meet the specified number of days passed and can't be undone.
126 d4ec019 Dawsh 2y ago """
127 d4ec019 Dawsh 2y ago
128 d4ec019 Dawsh 2y ago dbadmin = crud.get_admin(db, admin.username)
129 d4ec019 Dawsh 2y ago
130 01cdf2e Dawsh 2y ago db_users = crud.get_users(
131 01cdf2e Dawsh 2y ago db=db,
132 01cdf2e Dawsh 2y ago status=[UserStatus.expired, UserStatus.limited],
133 01cdf2e Dawsh 2y ago admin=dbadmin if not admin.is_sudo else None,
134 01cdf2e Dawsh 2y ago )
135 d4ec019 Dawsh 2y ago
136 d4ec019 Dawsh 2y ago current_time = int(datetime.now(timezone.utc).timestamp())
137 d4ec019 Dawsh 2y ago expiration_threshold = current_time - passed_time
138 d4ec019 Dawsh 2y ago expired_users = [
139 01cdf2e Dawsh 2y ago user
140 01cdf2e Dawsh 2y ago for user in db_users
141 01cdf2e Dawsh 2y ago if user.expire is not None and user.expire <= expiration_threshold
142 01cdf2e Dawsh 2y ago ]
143 d4ec019 Dawsh 2y ago if not expired_users:
144 d4ec019 Dawsh 2y ago raise HTTPException(status_code=404, detail="No expired user found.")
145 d4ec019 Dawsh 2y ago
146 d4ec019 Dawsh 2y ago for db_user in expired_users:
147 d4ec019 Dawsh 2y ago crud.remove_user(db, db_user)
148 d4ec019 Dawsh 2y ago
149 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_deleted(username=db_user.username, by=admin))
150 d4ec019 Dawsh 2y ago
151 d4ec019 Dawsh 2y ago logger.info("User `%s` removed", db_user.username)
152 d4ec019 Dawsh 2y ago
153 d4ec019 Dawsh 2y ago return {}
154 d4ec019 Dawsh 2y ago
155 d4ec019 Dawsh 2y ago
156 5bb2712 Mardin 2y ago @router.get("/{username}", response_model=UserResponse)
157 f7af2b3 Dawsh 2y ago def get_user(db_user: UserDep):
158 f7af2b3 Dawsh 2y ago """
159 f7af2b3 Dawsh 2y ago Get users information
160 f7af2b3 Dawsh 2y ago """
161 f7af2b3 Dawsh 2y ago return db_user
162 f7af2b3 Dawsh 2y ago
163 f7af2b3 Dawsh 2y ago
164 5bb2712 Mardin 2y ago @router.put("/{username}", response_model=UserResponse)
165 01cdf2e Dawsh 2y ago async def modify_user(
166 01cdf2e Dawsh 2y ago db_user: UserDep, modifications: UserModify, db: DBDep, admin: AdminDep
167 01cdf2e Dawsh 2y ago ):
168 f7af2b3 Dawsh 2y ago """
169 f7af2b3 Dawsh 2y ago Modify a user
170 f7af2b3 Dawsh 2y ago
171 f7af2b3 Dawsh 2y ago - set **expire** to 0 to make the user unlimited in time, null for no change
172 f7af2b3 Dawsh 2y ago - set **data_limit** to 0 to make the user unlimited in data, null for no change
173 f7af2b3 Dawsh 2y ago """
174 f7af2b3 Dawsh 2y ago old_status = db_user.status
175 01cdf2e Dawsh 2y ago status_change = bool(
176 01cdf2e Dawsh 2y ago modifications.status and modifications.status != db_user.status
177 01cdf2e Dawsh 2y ago )
178 f7af2b3 Dawsh 2y ago
179 f7af2b3 Dawsh 2y ago old_inbounds = {(i.node_id, i.protocol, i.tag) for i in db_user.inbounds}
180 f7af2b3 Dawsh 2y ago new_user = crud.update_user(db, db_user, modifications)
181 f7af2b3 Dawsh 2y ago new_inbounds = {(i.node_id, i.protocol, i.tag) for i in new_user.inbounds}
182 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
183 f7af2b3 Dawsh 2y ago
184 f7af2b3 Dawsh 2y ago inbound_change = old_inbounds != new_inbounds
185 cba9caa Dawsh 2y ago if (
186 cba9caa Dawsh 2y ago inbound_change
187 cba9caa Dawsh 2y ago and user.enabled
188 cba9caa Dawsh 2y ago and user.status in {UserStatus.active, UserStatus.on_hold}
189 cba9caa Dawsh 2y ago ):
190 d6e2aad Dawsh 2y ago await marznode.operations.update_user(new_user, old_inbounds)
191 f7af2b3 Dawsh 2y ago
192 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_updated(user=user, by=admin))
193 01cdf2e Dawsh 2y ago
194 f7af2b3 Dawsh 2y ago logger.info("User `%s` modified", user.username)
195 f7af2b3 Dawsh 2y ago
196 f7af2b3 Dawsh 2y ago if status_change:
197 01cdf2e Dawsh 2y ago asyncio.create_task(
198 01cdf2e Dawsh 2y ago report.status_change(
199 01cdf2e Dawsh 2y ago username=user.username, status=user.status, user=user, by=admin
200 01cdf2e Dawsh 2y ago )
201 01cdf2e Dawsh 2y ago )
202 01cdf2e Dawsh 2y ago
203 01cdf2e Dawsh 2y ago logger.info(
204 01cdf2e Dawsh 2y ago "User `%s` status changed from `%s` to `%s`",
205 01cdf2e Dawsh 2y ago user.username,
206 01cdf2e Dawsh 2y ago old_status,
207 01cdf2e Dawsh 2y ago user.status,
208 01cdf2e Dawsh 2y ago )
209 f7af2b3 Dawsh 2y ago
210 f7af2b3 Dawsh 2y ago return user
211 f7af2b3 Dawsh 2y ago
212 f7af2b3 Dawsh 2y ago
213 5bb2712 Mardin 2y ago @router.delete("/{username}")
214 01cdf2e Dawsh 2y ago async def remove_user(db_user: UserDep, db: DBDep, admin: AdminDep):
215 f7af2b3 Dawsh 2y ago """
216 f7af2b3 Dawsh 2y ago Remove a user
217 f7af2b3 Dawsh 2y ago """
218 d6e2aad Dawsh 2y ago await marznode.operations.remove_user(db_user)
219 f7af2b3 Dawsh 2y ago
220 f7af2b3 Dawsh 2y ago crud.remove_user(db, db_user)
221 f7af2b3 Dawsh 2y ago db.flush()
222 f7af2b3 Dawsh 2y ago
223 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_deleted(username=db_user.username, by=admin))
224 f7af2b3 Dawsh 2y ago logger.info("User %s deleted", db_user.username)
225 f7af2b3 Dawsh 2y ago return {}
226 f7af2b3 Dawsh 2y ago
227 f7af2b3 Dawsh 2y ago
228 5bb2712 Mardin 2y ago @router.post("/{username}/reset", response_model=UserResponse)
229 01cdf2e Dawsh 2y ago async def reset_user_data_usage(db_user: UserDep, db: DBDep, admin: AdminDep):
230 f7af2b3 Dawsh 2y ago """
231 f7af2b3 Dawsh 2y ago Reset user data usage
232 f7af2b3 Dawsh 2y ago """
233 f7af2b3 Dawsh 2y ago previous_status = db_user.status
234 f7af2b3 Dawsh 2y ago db_user = crud.reset_user_data_usage(db, db_user)
235 f7af2b3 Dawsh 2y ago
236 f7af2b3 Dawsh 2y ago if db_user.status == UserStatus.active and previous_status == UserStatus.limited:
237 d6e2aad Dawsh 2y ago await marznode.operations.update_user(db_user)
238 f7af2b3 Dawsh 2y ago
239 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
240 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_data_usage_reset(user=user, by=admin))
241 f7af2b3 Dawsh 2y ago
242 f7af2b3 Dawsh 2y ago logger.info("User `%s`'s usage was reset", db_user.username)
243 f7af2b3 Dawsh 2y ago
244 f7af2b3 Dawsh 2y ago return user
245 f7af2b3 Dawsh 2y ago
246 f7af2b3 Dawsh 2y ago
247 cba9caa Dawsh 2y ago @router.post("/{username}/enable", response_model=UserResponse)
248 cba9caa Dawsh 2y ago async def enable_user(db_user: UserDep, db: DBDep, admin: AdminDep):
249 cba9caa Dawsh 2y ago """
250 cba9caa Dawsh 2y ago Enables a user
251 cba9caa Dawsh 2y ago """
252 cba9caa Dawsh 2y ago if db_user.enabled:
253 cba9caa Dawsh 2y ago raise HTTPException(409, "User is already enabled")
254 cba9caa Dawsh 2y ago db_user.enabled = True
255 cba9caa Dawsh 2y ago db.commit()
256 cba9caa Dawsh 2y ago
257 cba9caa Dawsh 2y ago await marznode.operations.update_user(db_user)
258 cba9caa Dawsh 2y ago
259 cba9caa Dawsh 2y ago user = UserResponse.model_validate(db_user)
260 cba9caa Dawsh 2y ago
261 cba9caa Dawsh 2y ago logger.info("User `%s` has been enabled", db_user.username)
262 cba9caa Dawsh 2y ago
263 cba9caa Dawsh 2y ago return user
264 cba9caa Dawsh 2y ago
265 cba9caa Dawsh 2y ago
266 cba9caa Dawsh 2y ago @router.post("/{username}/disable", response_model=UserResponse)
267 cba9caa Dawsh 2y ago async def disable_user(db_user: UserDep, db: DBDep, admin: AdminDep):
268 cba9caa Dawsh 2y ago """
269 cba9caa Dawsh 2y ago Disables a user
270 cba9caa Dawsh 2y ago """
271 cba9caa Dawsh 2y ago if not db_user.enabled:
272 cba9caa Dawsh 2y ago raise HTTPException(409, "User is not enabled")
273 cba9caa Dawsh 2y ago db_user.enabled = False
274 cba9caa Dawsh 2y ago db.commit()
275 cba9caa Dawsh 2y ago
276 cba9caa Dawsh 2y ago await marznode.operations.remove_user(db_user)
277 cba9caa Dawsh 2y ago
278 cba9caa Dawsh 2y ago user = UserResponse.model_validate(db_user)
279 cba9caa Dawsh 2y ago
280 cba9caa Dawsh 2y ago logger.info("User `%s` has been disabled", db_user.username)
281 cba9caa Dawsh 2y ago
282 cba9caa Dawsh 2y ago return user
283 cba9caa Dawsh 2y ago
284 cba9caa Dawsh 2y ago
285 5bb2712 Mardin 2y ago @router.post("/{username}/revoke_sub", response_model=UserResponse)
286 01cdf2e Dawsh 2y ago async def revoke_user_subscription(db_user: UserDep, db: DBDep, admin: AdminDep):
287 f7af2b3 Dawsh 2y ago """
288 f7af2b3 Dawsh 2y ago Revoke users subscription (Subscription link and proxies)
289 f7af2b3 Dawsh 2y ago """
290 f7af2b3 Dawsh 2y ago db_user = crud.revoke_user_sub(db, db_user)
291 f7af2b3 Dawsh 2y ago
292 f7af2b3 Dawsh 2y ago if db_user.status in [UserStatus.active, UserStatus.on_hold]:
293 f7af2b3 Dawsh 2y ago await marznode.operations.remove_user(db_user)
294 d6e2aad Dawsh 2y ago await marznode.operations.update_user(db_user)
295 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
296 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_subscription_revoked(user=user, by=admin))
297 f7af2b3 Dawsh 2y ago
298 f7af2b3 Dawsh 2y ago logger.info("User %s subscription revoked", db_user.username)
299 f7af2b3 Dawsh 2y ago
300 f7af2b3 Dawsh 2y ago return user
301 f7af2b3 Dawsh 2y ago
302 f7af2b3 Dawsh 2y ago
303 5bb2712 Mardin 2y ago @router.get("/{username}/usage", response_model=UserUsagesResponse)
304 01cdf2e Dawsh 2y ago def get_user_usage(
305 01cdf2e Dawsh 2y ago db: DBDep, db_user: UserDep, start_date: StartDateDep, end_date: EndDateDep
306 01cdf2e Dawsh 2y ago ):
307 f7af2b3 Dawsh 2y ago """
308 f7af2b3 Dawsh 2y ago Get users usage
309 f7af2b3 Dawsh 2y ago """
310 f7af2b3 Dawsh 2y ago usages = crud.get_user_usages(db, db_user, start_date, end_date)
311 f7af2b3 Dawsh 2y ago
312 f7af2b3 Dawsh 2y ago return {"usages": usages, "username": db_user.username}
313 f7af2b3 Dawsh 2y ago
314 f7af2b3 Dawsh 2y ago
315 5bb2712 Mardin 2y ago @router.put("/{username}/set-owner", response_model=UserResponse)
316 01cdf2e Dawsh 2y ago def set_owner(username: str, admin_username: str, db: DBDep, admin: SudoAdminDep):
317 f7af2b3 Dawsh 2y ago db_user = crud.get_user(db, username)
318 f7af2b3 Dawsh 2y ago if not db_user:
319 f7af2b3 Dawsh 2y ago raise HTTPException(status_code=404, detail="User not found")
320 f7af2b3 Dawsh 2y ago
321 f7af2b3 Dawsh 2y ago new_admin = crud.get_admin(db, username=admin_username)
322 f7af2b3 Dawsh 2y ago if not new_admin:
323 f7af2b3 Dawsh 2y ago raise HTTPException(status_code=404, detail="Admin not found")
324 f7af2b3 Dawsh 2y ago
325 f7af2b3 Dawsh 2y ago db_user = crud.set_owner(db, db_user, new_admin)
326 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
327 f7af2b3 Dawsh 2y ago
328 f7af2b3 Dawsh 2y ago logger.info("`%s`'s owner successfully set to `%s`", user.username, admin.username)
329 f7af2b3 Dawsh 2y ago
330 f7af2b3 Dawsh 2y ago return user