gitscope
https://dev.kronoz.win/r/marzneshin.git
app/routes/user.py · v0.3.0-alpha3
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 01cdf2e Dawsh 2y ago if status_change and (
179 01cdf2e Dawsh 2y ago modifications.status == UserStatus.disabled
180 01cdf2e Dawsh 2y ago and old_status in {UserStatus.active, UserStatus.on_hold}
181 01cdf2e Dawsh 2y ago ):
182 f7af2b3 Dawsh 2y ago await marznode.operations.remove_user(db_user)
183 f7af2b3 Dawsh 2y ago
184 f7af2b3 Dawsh 2y ago old_inbounds = {(i.node_id, i.protocol, i.tag) for i in db_user.inbounds}
185 f7af2b3 Dawsh 2y ago new_user = crud.update_user(db, db_user, modifications)
186 f7af2b3 Dawsh 2y ago new_inbounds = {(i.node_id, i.protocol, i.tag) for i in new_user.inbounds}
187 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
188 f7af2b3 Dawsh 2y ago
189 f7af2b3 Dawsh 2y ago inbound_change = old_inbounds != new_inbounds
190 d6e2aad Dawsh 2y ago if inbound_change and user.status in {UserStatus.active, UserStatus.on_hold}:
191 d6e2aad Dawsh 2y ago await marznode.operations.update_user(new_user, old_inbounds)
192 f7af2b3 Dawsh 2y ago
193 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_updated(user=user, by=admin))
194 01cdf2e Dawsh 2y ago
195 f7af2b3 Dawsh 2y ago logger.info("User `%s` modified", user.username)
196 f7af2b3 Dawsh 2y ago
197 f7af2b3 Dawsh 2y ago if status_change:
198 01cdf2e Dawsh 2y ago asyncio.create_task(
199 01cdf2e Dawsh 2y ago report.status_change(
200 01cdf2e Dawsh 2y ago username=user.username, status=user.status, user=user, by=admin
201 01cdf2e Dawsh 2y ago )
202 01cdf2e Dawsh 2y ago )
203 01cdf2e Dawsh 2y ago
204 01cdf2e Dawsh 2y ago logger.info(
205 01cdf2e Dawsh 2y ago "User `%s` status changed from `%s` to `%s`",
206 01cdf2e Dawsh 2y ago user.username,
207 01cdf2e Dawsh 2y ago old_status,
208 01cdf2e Dawsh 2y ago user.status,
209 01cdf2e Dawsh 2y ago )
210 f7af2b3 Dawsh 2y ago
211 f7af2b3 Dawsh 2y ago return user
212 f7af2b3 Dawsh 2y ago
213 f7af2b3 Dawsh 2y ago
214 5bb2712 Mardin 2y ago @router.delete("/{username}")
215 01cdf2e Dawsh 2y ago async def remove_user(db_user: UserDep, db: DBDep, admin: AdminDep):
216 f7af2b3 Dawsh 2y ago """
217 f7af2b3 Dawsh 2y ago Remove a user
218 f7af2b3 Dawsh 2y ago """
219 d6e2aad Dawsh 2y ago await marznode.operations.remove_user(db_user)
220 f7af2b3 Dawsh 2y ago
221 f7af2b3 Dawsh 2y ago crud.remove_user(db, db_user)
222 f7af2b3 Dawsh 2y ago db.flush()
223 f7af2b3 Dawsh 2y ago
224 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_deleted(username=db_user.username, by=admin))
225 f7af2b3 Dawsh 2y ago logger.info("User %s deleted", db_user.username)
226 f7af2b3 Dawsh 2y ago return {}
227 f7af2b3 Dawsh 2y ago
228 f7af2b3 Dawsh 2y ago
229 5bb2712 Mardin 2y ago @router.post("/{username}/reset", response_model=UserResponse)
230 01cdf2e Dawsh 2y ago async def reset_user_data_usage(db_user: UserDep, db: DBDep, admin: AdminDep):
231 f7af2b3 Dawsh 2y ago """
232 f7af2b3 Dawsh 2y ago Reset user data usage
233 f7af2b3 Dawsh 2y ago """
234 f7af2b3 Dawsh 2y ago previous_status = db_user.status
235 f7af2b3 Dawsh 2y ago db_user = crud.reset_user_data_usage(db, db_user)
236 f7af2b3 Dawsh 2y ago
237 f7af2b3 Dawsh 2y ago if db_user.status == UserStatus.active and previous_status == UserStatus.limited:
238 d6e2aad Dawsh 2y ago await marznode.operations.update_user(db_user)
239 f7af2b3 Dawsh 2y ago
240 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
241 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_data_usage_reset(user=user, by=admin))
242 f7af2b3 Dawsh 2y ago
243 f7af2b3 Dawsh 2y ago logger.info("User `%s`'s usage was reset", db_user.username)
244 f7af2b3 Dawsh 2y ago
245 f7af2b3 Dawsh 2y ago return user
246 f7af2b3 Dawsh 2y ago
247 f7af2b3 Dawsh 2y ago
248 5bb2712 Mardin 2y ago @router.post("/{username}/revoke_sub", response_model=UserResponse)
249 01cdf2e Dawsh 2y ago async def revoke_user_subscription(db_user: UserDep, db: DBDep, admin: AdminDep):
250 f7af2b3 Dawsh 2y ago """
251 f7af2b3 Dawsh 2y ago Revoke users subscription (Subscription link and proxies)
252 f7af2b3 Dawsh 2y ago """
253 f7af2b3 Dawsh 2y ago db_user = crud.revoke_user_sub(db, db_user)
254 f7af2b3 Dawsh 2y ago
255 f7af2b3 Dawsh 2y ago if db_user.status in [UserStatus.active, UserStatus.on_hold]:
256 f7af2b3 Dawsh 2y ago await marznode.operations.remove_user(db_user)
257 d6e2aad Dawsh 2y ago await marznode.operations.update_user(db_user)
258 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
259 01cdf2e Dawsh 2y ago asyncio.create_task(report.user_subscription_revoked(user=user, by=admin))
260 f7af2b3 Dawsh 2y ago
261 f7af2b3 Dawsh 2y ago logger.info("User %s subscription revoked", db_user.username)
262 f7af2b3 Dawsh 2y ago
263 f7af2b3 Dawsh 2y ago return user
264 f7af2b3 Dawsh 2y ago
265 f7af2b3 Dawsh 2y ago
266 5bb2712 Mardin 2y ago @router.get("/{username}/usage", response_model=UserUsagesResponse)
267 01cdf2e Dawsh 2y ago def get_user_usage(
268 01cdf2e Dawsh 2y ago db: DBDep, db_user: UserDep, start_date: StartDateDep, end_date: EndDateDep
269 01cdf2e Dawsh 2y ago ):
270 f7af2b3 Dawsh 2y ago """
271 f7af2b3 Dawsh 2y ago Get users usage
272 f7af2b3 Dawsh 2y ago """
273 f7af2b3 Dawsh 2y ago usages = crud.get_user_usages(db, db_user, start_date, end_date)
274 f7af2b3 Dawsh 2y ago
275 f7af2b3 Dawsh 2y ago return {"usages": usages, "username": db_user.username}
276 f7af2b3 Dawsh 2y ago
277 f7af2b3 Dawsh 2y ago
278 5bb2712 Mardin 2y ago @router.put("/{username}/set-owner", response_model=UserResponse)
279 01cdf2e Dawsh 2y ago def set_owner(username: str, admin_username: str, db: DBDep, admin: SudoAdminDep):
280 f7af2b3 Dawsh 2y ago db_user = crud.get_user(db, username)
281 f7af2b3 Dawsh 2y ago if not db_user:
282 f7af2b3 Dawsh 2y ago raise HTTPException(status_code=404, detail="User not found")
283 f7af2b3 Dawsh 2y ago
284 f7af2b3 Dawsh 2y ago new_admin = crud.get_admin(db, username=admin_username)
285 f7af2b3 Dawsh 2y ago if not new_admin:
286 f7af2b3 Dawsh 2y ago raise HTTPException(status_code=404, detail="Admin not found")
287 f7af2b3 Dawsh 2y ago
288 f7af2b3 Dawsh 2y ago db_user = crud.set_owner(db, db_user, new_admin)
289 f7af2b3 Dawsh 2y ago user = UserResponse.model_validate(db_user)
290 f7af2b3 Dawsh 2y ago
291 f7af2b3 Dawsh 2y ago logger.info("`%s`'s owner successfully set to `%s`", user.username, admin.username)
292 f7af2b3 Dawsh 2y ago
293 f7af2b3 Dawsh 2y ago return user