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