diff --git a/app/marznode/operations.py b/app/marznode/operations.py index 667e66b..0a7fef9 100644 --- a/app/marznode/operations.py +++ b/app/marznode/operations.py @@ -29,7 +29,16 @@ async def update_user(user: "DBUser", old_inbounds: set | None = None): node_inbounds[inb[0]] for node_id, tags in node_inbounds.items(): - await marznode.nodes[node_id].update_user(user=user, inbounds=tags) + if marznode.nodes.get(node_id): + await marznode.nodes[node_id].update_user(user=user, inbounds=tags) + + +async def remove_user(user: "DBUser"): + node_ids = set(inb.id for inb in user.inbounds) + + for node_id in node_ids: + if marznode.nodes.get(node_id): + await marznode.nodes[node_id].update_user(user=user, inbounds=[]) async def remove_node(node_id: int): diff --git a/app/models/admin.py b/app/models/admin.py index 41e9982..9c99b0b 100644 --- a/app/models/admin.py +++ b/app/models/admin.py @@ -5,7 +5,7 @@ from passlib.context import CryptContext from pydantic import ConfigDict, BaseModel pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/admin/token") # Admin view url +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/admins/token") # Admin view url class Token(BaseModel): diff --git a/app/models/user.py b/app/models/user.py index 9e49c0b..fec35a5 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -67,7 +67,6 @@ class UserBase(BaseModel): on_hold_expire_duration: Optional[int] = Field(None, nullable=True) on_hold_timeout: Optional[Union[datetime, None]] = Field(None, nullable=True) - @field_validator("username") @classmethod def validate_username(cls, v: str): diff --git a/app/routes/service.py b/app/routes/service.py index bae709f..b3dd628 100644 --- a/app/routes/service.py +++ b/app/routes/service.py @@ -4,6 +4,7 @@ import sqlalchemy from fastapi import APIRouter, Depends from fastapi import HTTPException +from app import marznode from app.db import crud from app.dependencies import DBDep, sudo_admin from app.models.service import (ServiceCreate, ServiceModify, @@ -16,12 +17,10 @@ router = APIRouter(prefix="/services", dependencies=[Depends(sudo_admin)], tags= def add_service(new_service: ServiceCreate, db: DBDep): """ - Add a new user template + Add a new service - - **name** can be up to 64 characters - - **data_limit** must be in bytes and larger or equal to 0 - - **expire_duration** must be in seconds and larger or equal to 0 - - **inbounds** dictionary of protocol:inbound_tags, empty means all inbounds + - **name** service name + - **inbounds** list of inbound ids """ try: return crud.create_service(db, new_service) @@ -43,15 +42,13 @@ def get_service(id: int, db: DBDep): @router.put("/{id}", response_model=ServiceResponse) -def modify_service(id: int, +async def modify_service(id: int, modification: ServiceModify, db: DBDep): """ Modify Service - **name** can be up to 64 characters - - **data_limit** must be in bytes and larger or equal to 0 - - **expire_duration** must be in seconds and larger or equat to 0 - **inbounds** list of inbound ids. if not specified no change will be applied; in case of an empty list all inbounds would be removed. """ @@ -59,12 +56,16 @@ def modify_service(id: int, dbservice = crud.get_service(db, id) if not dbservice: raise HTTPException(status_code=404, detail="Service not found") - + old_inbounds = {(i.node_id, i.protocol, i.tag) for i in dbservice.inbounds} try: - return crud.update_service(db, dbservice, modification) + response = crud.update_service(db, dbservice, modification) except sqlalchemy.exc.IntegrityError: db.rollback() - raise HTTPException(status_code=409, detail="Service by this name already exists") + raise HTTPException(status_code=409, detail="problem updating the service") + else: + for user in response.users: + await marznode.operations.update_user(user, old_inbounds=old_inbounds) + return response @router.delete("/{id}") diff --git a/app/routes/user.py b/app/routes/user.py index 3ae7004..2d64498 100644 --- a/app/routes/user.py +++ b/app/routes/user.py @@ -82,14 +82,8 @@ async def modify_user(db_user: UserDep, user = UserResponse.model_validate(db_user) inbound_change = old_inbounds != new_inbounds - if (user.status in {UserStatus.active, UserStatus.on_hold} - and (not status_change or old_status == UserStatus.on_hold)): - if inbound_change: - await marznode.operations.update_user_inbounds( - user=new_user, new_inbounds=new_inbounds, old_inbounds=old_inbounds) - elif (user.status in {UserStatus.active, UserStatus.on_hold} and status_change and - old_status not in {UserStatus.active, UserStatus.on_hold}): - await marznode.operations.add_user(new_user) + if inbound_change and user.status in {UserStatus.active, UserStatus.on_hold}: + await marznode.operations.update_user(new_user, old_inbounds) asyncio.create_task(report.user_updated( user=user, @@ -119,8 +113,7 @@ async def remove_user(db_user: UserDep, """ Remove a user """ - db_user.inbounds = [] - await marznode.operations.update_user(db_user) + await marznode.operations.remove_user(db_user) crud.remove_user(db, db_user) db.flush() @@ -145,7 +138,7 @@ async def reset_user_data_usage(db_user: UserDep, db_user = crud.reset_user_data_usage(db, db_user) if db_user.status == UserStatus.active and previous_status == UserStatus.limited: - await marznode.operations.add_user(db_user) + await marznode.operations.update_user(db_user) user = UserResponse.model_validate(db_user) asyncio.create_task(report.user_data_usage_reset( @@ -168,7 +161,7 @@ async def revoke_user_subscription(db_user: UserDep, if db_user.status in [UserStatus.active, UserStatus.on_hold]: await marznode.operations.remove_user(db_user) - await marznode.operations.add_user(db_user) + await marznode.operations.update_user(db_user) user = UserResponse.model_validate(db_user) asyncio.create_task( report.user_subscription_revoked( @@ -220,15 +213,11 @@ def get_users(db: DBDep, async def reset_users_data_usage(db: DBDep, admin: SudoAdminDep): """ - Reset all users data usage + Reset all users data usage, + You will need to restart for this to take effect for now """ dbadmin = crud.get_admin(db, admin.username) crud.reset_all_users_data_usage(db=db, admin=dbadmin) - # startup_config = xray.config.include_db_users() - await xray.core.restart(xray.config.include_db_users(node_id=0)) - for node_id, node in list(xray.nodes.items()): - if node.connected: - xray.operations.restart_node(node_id, xray.config.include_db_users(node_id=node_id)) return {} diff --git a/app/tasks/reset_user_data_usage.py b/app/tasks/reset_user_data_usage.py index 196d66c..12c98fc 100644 --- a/app/tasks/reset_user_data_usage.py +++ b/app/tasks/reset_user_data_usage.py @@ -34,6 +34,6 @@ async def reset_user_data_usage(): crud.reset_user_data_usage(db, user) # make user active if limited on usage reset if user.status == UserStatus.active and old_status == UserStatus.limited: - await marznode.operations.add_user(user) + await marznode.operations.update_user(user) logger.info(f"User data usage reset for User \"{user.username}\"")