diff --git a/remnawave/controllers/users.py b/remnawave/controllers/users.py index 745e0f6..9b9c97b 100644 --- a/remnawave/controllers/users.py +++ b/remnawave/controllers/users.py @@ -5,58 +5,56 @@ from rapid_api_client.annotations import PydanticBody from remnawave.models import ( CreateUserRequestDto, + CreateUserResponseDto, DeleteUserResponseDto, - EmailUserResponseDto, + GetAllUsersResponseDto, + GetAllTagsResponseDto, + GetUserByIdResponseDto, + GetUserByShortUuidResponseDto, + GetUserByUsernameResponseDto, + GetUserByUuidResponseDto, GetUserAccessibleNodesResponseDto, + GetUserSubscriptionRequestHistoryResponseDto, TelegramUserResponseDto, - UpdateUserRequestDto, - UserResponseDto, - UsersResponseDto, - TagsResponseDto, + EmailUserResponseDto, TagUserResponseDto, + UpdateUserRequestDto, + UpdateUserResponseDto, RevokeUserRequestDto, - GetSubscriptionRequestsResponseDto ) from remnawave.rapid import BaseController, delete, get, patch, post class UsersController(BaseController): - @post("/users", response_class=UserResponseDto) + @post("/users", response_class=CreateUserResponseDto) async def create_user( self, body: Annotated[CreateUserRequestDto, PydanticBody()], - ) -> UserResponseDto: - """Create User""" + ) -> CreateUserResponseDto: + """Create a new user""" ... - @patch("/users", response_class=UserResponseDto) + @patch("/users", response_class=UpdateUserResponseDto) async def update_user( self, body: Annotated[UpdateUserRequestDto, PydanticBody()], - ) -> UserResponseDto: - """Update User""" + ) -> UpdateUserResponseDto: + """Update a user by UUID or username""" ... - @get("/users", response_class=UsersResponseDto) - async def get_all_users_v2( + @get("/users", response_class=GetAllUsersResponseDto) + async def get_all_users( self, start: Annotated[ - int, Query(default=0, ge=0, description="Index to start pagination from") - ], + Optional[int], + Query(default=None, description="Offset for pagination") + ] = None, size: Annotated[ - int, Query(default=25, ge=1, description="Number of users per page") - ], - ) -> UsersResponseDto: - """ - Get users page from the end. - - Args: - page (int): Page number from the end (1 = last page). - size (int): Number of users per page. - - Returns: - UsersResponseDto - """ + Optional[int], + Query(default=None, description="Page size for pagination") + ] = None, + ) -> GetAllUsersResponseDto: + """Get all users""" ... @delete("/users/{uuid}", response_class=DeleteUserResponseDto) @@ -64,75 +62,102 @@ class UsersController(BaseController): self, uuid: Annotated[str, Path(description="UUID of the user")], ) -> DeleteUserResponseDto: - """Delete User""" + """Delete user""" ... - @post("users/{uuid}/actions/revoke", response_class=UserResponseDto) + @post("/users/{uuid}/actions/revoke", response_class=UpdateUserResponseDto) async def revoke_user_subscription( self, uuid: Annotated[str, Path(description="UUID of the user")], body: Optional[Annotated[RevokeUserRequestDto, PydanticBody()]] = None, - ) -> UserResponseDto: + ) -> UpdateUserResponseDto: """Revoke User Subscription""" ... - @post("/users/{uuid}/actions/disable", response_class=UserResponseDto) + @post("/users/{uuid}/actions/disable", response_class=UpdateUserResponseDto) async def disable_user( self, uuid: Annotated[str, Path(description="UUID of the user")], - ) -> UserResponseDto: + ) -> UpdateUserResponseDto: """Disable User""" ... - @post("/users/{uuid}/actions/enable", response_class=UserResponseDto) + @post("/users/{uuid}/actions/enable", response_class=UpdateUserResponseDto) async def enable_user( self, uuid: Annotated[str, Path(description="UUID of the user")], - ) -> UserResponseDto: + ) -> UpdateUserResponseDto: """Enable User""" ... - @post("/users/{uuid}/actions/reset-traffic", response_class=UserResponseDto) + @post("/users/{uuid}/actions/reset-traffic", response_class=UpdateUserResponseDto) async def reset_user_traffic( self, uuid: Annotated[str, Path(description="UUID of the user")], - ) -> UserResponseDto: + ) -> UpdateUserResponseDto: """Reset User Traffic""" ... - @get("/users/by-short-uuid/{short_uuid}", response_class=UserResponseDto) - async def get_user_by_short_uuid( - self, - short_uuid: Annotated[str, Path(description="Short UUID of the user")], - ) -> UserResponseDto: - """Get User By Short UUID""" - ... - - @get( - "/users/by-subscription-uuid/{subscription_uuid}", - response_class=UserResponseDto, - ) - async def get_user_by_subscription_uuid( - self, - subscription_uuid: Annotated[str, Path(description="UUID of the subscription")], - ) -> UserResponseDto: - """Get User By Subscription UUID""" - ... - - @get("/users/{uuid}", response_class=UserResponseDto) + @get("/users/{uuid}", response_class=GetUserByUuidResponseDto) async def get_user_by_uuid( self, uuid: Annotated[str, Path(description="UUID of the user")], - ) -> UserResponseDto: - """Get User By UUID""" + ) -> GetUserByUuidResponseDto: + """Get user by UUID""" ... - @get("/users/by-username/{username}", response_class=UserResponseDto) + @get("/users/tags", response_class=GetAllTagsResponseDto) + async def get_all_tags( + self, + ) -> GetAllTagsResponseDto: + """Get all existing user tags""" + ... + + @get( + "/users/{uuid}/accessible-nodes", + response_class=GetUserAccessibleNodesResponseDto, + ) + async def get_user_accessible_nodes( + self, + uuid: Annotated[str, Path(description="UUID of the user")], + ) -> GetUserAccessibleNodesResponseDto: + """Get user accessible nodes""" + ... + + @get( + "/users/{uuid}/subscription-request-history", + response_class=GetUserSubscriptionRequestHistoryResponseDto + ) + async def get_user_subscription_request_history( + self, + uuid: Annotated[str, Path(description="UUID of the user")], + ) -> GetUserSubscriptionRequestHistoryResponseDto: + """Get user subscription request history, recent 24 records""" + ... + + @get("/users/by-short-uuid/{short_uuid}", response_class=GetUserByShortUuidResponseDto) + async def get_user_by_short_uuid( + self, + short_uuid: Annotated[str, Path(description="Short UUID of the user", alias="shortUuid")], + ) -> GetUserByShortUuidResponseDto: + """Get user by Short UUID""" + ... + + @get("/users/by-username/{username}", response_class=GetUserByUsernameResponseDto) async def get_user_by_username( self, username: Annotated[str, Path(description="Username of the user")], - ) -> UserResponseDto: - """Get User By Username""" + ) -> GetUserByUsernameResponseDto: + """Get user by username""" + ... + + # НОВЫЙ ЭНДПОИНТ + @get("/users/by-id/{id}", response_class=GetUserByIdResponseDto) + async def get_user_by_id( + self, + id: Annotated[str, Path(description="ID of the user")], + ) -> GetUserByIdResponseDto: + """Get user by ID""" ... @get( @@ -141,7 +166,7 @@ class UsersController(BaseController): ) async def get_users_by_telegram_id( self, - telegram_id: Annotated[str, Path(description="Telegram ID of the user")], + telegram_id: Annotated[str, Path(description="Telegram ID of the user", alias="telegramId")], ) -> TelegramUserResponseDto: """Get Users By Telegram ID""" ... @@ -160,30 +185,4 @@ class UsersController(BaseController): tag: Annotated[str, Path(description="Tag of the user")], ) -> TagUserResponseDto: """Get Users By Tag""" - ... - - @get("/users/tags", response_class=TagsResponseDto) - async def get_all_tags( - self, - ) -> TagsResponseDto: - """Get All Tags""" - ... - - @get( - "/users/{uuid}/accessible-nodes", - response_class=GetUserAccessibleNodesResponseDto, - ) - async def get_user_accessible_nodes( - self, - uuid: Annotated[str, Path(description="UUID of the user")], - ) -> GetUserAccessibleNodesResponseDto: - """Get User Accessible Nodes""" - ... - - @get("/users/{uuid}/subscription-request-history", response_class=GetSubscriptionRequestsResponseDto) - async def get_subscription_requests( - self, - uuid: Annotated[str, Path(description="UUID of the user")], - ) -> GetSubscriptionRequestsResponseDto: - """Get Subscription Requests History""" ... \ No newline at end of file diff --git a/remnawave/controllers/users_bulk_actions.py b/remnawave/controllers/users_bulk_actions.py index c4426fb..3b3b205 100644 --- a/remnawave/controllers/users_bulk_actions.py +++ b/remnawave/controllers/users_bulk_actions.py @@ -5,71 +5,95 @@ from rapid_api_client.annotations import PydanticBody from remnawave.enums import UserStatus from remnawave.models import ( + BulkAllExtendExpirationDateRequestDto, + BulkAllExtendExpirationDateResponseDto, BulkAllResetTrafficUsersResponseDto, BulkAllUpdateUsersRequestDto, BulkAllUpdateUsersResponseDto, - BulkResponseDto, - BulkUpdateUsersInternalSquadsRequestDto, - UpdateUserFields, + BulkDeleteUsersByStatusRequestDto, + BulkDeleteUsersByStatusResponseDto, + BulkDeleteUsersRequestDto, + BulkDeleteUsersResponseDto, + BulkExtendExpirationDateRequestDto, + BulkExtendExpirationDateResponseDto, + BulkResetTrafficUsersRequestDto, + BulkResetTrafficUsersResponseDto, + BulkRevokeUsersSubscriptionRequestDto, + BulkRevokeUsersSubscriptionResponseDto, + BulkUpdateUsersRequestDto, + BulkUpdateUsersResponseDto, + BulkUpdateUsersSquadsRequestDto, + BulkUpdateUsersSquadsResponseDto, ) -from remnawave.rapid import AttributeBody, BaseController, patch, post +from remnawave.rapid import BaseController, post class UsersBulkActionsController(BaseController): @post( "/users/bulk/delete-by-status", - response_class=BulkResponseDto, + response_class=BulkDeleteUsersByStatusResponseDto, ) async def bulk_delete_users_by_status( - self, status: Annotated[UserStatus, AttributeBody()] - ) -> BulkResponseDto: + self, + body: Annotated[BulkDeleteUsersByStatusRequestDto, PydanticBody()] + ) -> BulkDeleteUsersByStatusResponseDto: """Bulk Delete Users By Status""" ... - @post("/users/bulk/delete", response_class=BulkResponseDto) + @post("/users/bulk/delete", response_class=BulkDeleteUsersResponseDto) async def bulk_delete_users( self, - uuids: Annotated[List[UUID], AttributeBody()], - ) -> BulkResponseDto: + body: Annotated[BulkDeleteUsersRequestDto, PydanticBody()], + ) -> BulkDeleteUsersResponseDto: """Bulk Delete Users By UUIDs""" ... @post( "/users/bulk/revoke-subscription", - response_class=BulkResponseDto, + response_class=BulkRevokeUsersSubscriptionResponseDto, ) async def bulk_revoke_users_subscription( self, - uuids: Annotated[List[UUID], AttributeBody()], - ) -> BulkResponseDto: + body: Annotated[BulkRevokeUsersSubscriptionRequestDto, PydanticBody()], + ) -> BulkRevokeUsersSubscriptionResponseDto: """Bulk Revoke Users Subscription""" ... - @post("/users/bulk/reset-traffic", response_class=BulkResponseDto) + @post("/users/bulk/reset-traffic", response_class=BulkResetTrafficUsersResponseDto) async def bulk_reset_user_traffic( self, - uuids: Annotated[List[UUID], AttributeBody()], - ) -> BulkResponseDto: + body: Annotated[BulkResetTrafficUsersRequestDto, PydanticBody()], + ) -> BulkResetTrafficUsersResponseDto: """Bulk Reset User Traffic""" ... - @post("/users/bulk/update", response_class=BulkResponseDto) + @post("/users/bulk/update", response_class=BulkUpdateUsersResponseDto) async def bulk_update_users( self, - uuids: Annotated[List[UUID], AttributeBody()], - fields: Annotated[UpdateUserFields, AttributeBody()], - ) -> BulkResponseDto: + body: Annotated[BulkUpdateUsersRequestDto, PydanticBody()], + ) -> BulkUpdateUsersResponseDto: """Bulk Update Users""" ... - @post("/users/bulk/update-squads", response_class=BulkResponseDto) + @post("/users/bulk/update-squads", response_class=BulkUpdateUsersSquadsResponseDto) async def bulk_update_users_internal_squads( self, - body: Annotated[BulkUpdateUsersInternalSquadsRequestDto, PydanticBody()], - ) -> BulkResponseDto: + body: Annotated[BulkUpdateUsersSquadsRequestDto, PydanticBody()], + ) -> BulkUpdateUsersSquadsResponseDto: """Bulk Update Users Internal Squads""" ... + @post( + "/users/bulk/extend-expiration-date", + response_class=BulkExtendExpirationDateResponseDto, + ) + async def bulk_extend_expiration_date( + self, + body: Annotated[BulkExtendExpirationDateRequestDto, PydanticBody()], + ) -> BulkExtendExpirationDateResponseDto: + """Bulk Extend Users Expiration Date""" + ... + @post("/users/bulk/all/update", response_class=BulkAllUpdateUsersResponseDto) async def bulk_update_all_users( self, @@ -89,13 +113,12 @@ class UsersBulkActionsController(BaseController): ... @post( - "/users/bulk/update-squads", - response_class=BulkResponseDto, - ) - async def bulk_update_users_internal_squad( + "/users/bulk/all/extend-expiration-date", + response_class=BulkAllExtendExpirationDateResponseDto, + ) + async def bulk_all_extend_expiration_date( self, - uuids: Annotated[List[UUID], AttributeBody()], - active_internal_squads: Annotated[List[UUID], AttributeBody(serialization_alias="activeInternalSquads")], - ) -> BulkResponseDto: - """Bulk Update Users External Squad""" + body: Annotated[BulkAllExtendExpirationDateRequestDto, PydanticBody()], + ) -> BulkAllExtendExpirationDateResponseDto: + """Bulk Extend All Users Expiration Date""" ... \ No newline at end of file diff --git a/remnawave/models/__init__.py b/remnawave/models/__init__.py index a1ae60e..5933da9 100644 --- a/remnawave/models/__init__.py +++ b/remnawave/models/__init__.py @@ -242,27 +242,81 @@ from .system import ( EncryptHappCryptoLinkResponseDto, ) from .users import ( - ActiveInternalSquadDto, + # Request DTOs CreateUserRequestDto, - DeleteUserResponseDto, - EmailUserResponseDto, - TelegramUserResponseDto, UpdateUserRequestDto, - UserActiveInboundsDto, - UserLastConnectedNodeDto, + RevokeUserRequestDto, + + # Response DTOs - Single User + CreateUserResponseDto, + UpdateUserResponseDto, + GetUserByUuidResponseDto, + GetUserByShortUuidResponseDto, + GetUserByUsernameResponseDto, + GetUserByIdResponseDto, + DisableUserResponseDto, + EnableUserResponseDto, + ResetUserTrafficResponseDto, + RevokeUserSubscriptionResponseDto, + ActivateAllInboundsResponseDto, + + # Response DTOs - Collections + GetAllUsersResponseDto, + GetAllTagsResponseDto, + GetUserAccessibleNodesResponseDto, + GetUserSubscriptionRequestHistoryResponseDto, + + # Response DTOs - Arrays (RootModel) + TelegramUserResponseDto, + EmailUserResponseDto, + TagUserResponseDto, + + # Other Response DTOs + DeleteUserResponseDto, + + # Base DTOs UserResponseDto, UsersResponseDto, TagsResponseDto, - TagUserResponseDto, - RevokeUserRequestDto, + SubscriptionRequestsResponseData, + + # Data DTOs + UserTrafficDto, + ActiveInternalSquadDto, + SubscriptionRequestRecord, + HappCrypto, + UserActiveInboundsDto, + UserLastConnectedNodeDto, + + # Alias GetSubscriptionRequestsResponseDto, ) + from .users_bulk_actions import ( + BulkAllExtendExpirationDateRequestDto, + BulkAllExtendExpirationDateResponseDto, BulkAllResetTrafficUsersResponseDto, BulkAllUpdateUsersRequestDto, BulkAllUpdateUsersResponseDto, + BulkDeleteUsersByStatusRequestDto, + BulkDeleteUsersByStatusResponseDto, + BulkDeleteUsersRequestDto, + BulkDeleteUsersResponseDto, + BulkEventResponseData, + BulkExtendExpirationDateRequestDto, + BulkExtendExpirationDateResponseDto, + BulkResetTrafficUsersRequestDto, + BulkResetTrafficUsersResponseDto, + BulkResponseData, BulkResponseDto, + BulkRevokeUsersSubscriptionRequestDto, + BulkRevokeUsersSubscriptionResponseDto, BulkUpdateUsersInternalSquadsRequestDto, + BulkUpdateUsersRequestDto, + BulkUpdateUsersResponseDto, + BulkUpdateUsersSquadsRequestDto, + BulkUpdateUsersSquadsResponseDto, + TagStr, UpdateUserFields, ) from .users_stats import UserUsageByRange, UserUsageByRangeResponseDto @@ -520,40 +574,64 @@ __all__ = [ "SetPortToManyHostsResponseDto", "SetPortToManyHostsRequestDto", # Users models - "ActiveInternalSquadDto", "CreateUserRequestDto", - "CreateUserResponseDto", - "DeleteUserResponseDto", - "DisableUserResponseDto", - "EmailUserResponseDto", - "EnableUserResponseDto", - "GetAllUsersResponseDto", - "GetUserByEmailResponseDto", - "GetUserByShortUuidResponseDto", - "GetUserBySubscriptionUuidResponseDto", - "GetUserByTagResponseDto", - "GetUserByTelegramIdResponseDto", - "GetUserByUsernameResponseDto", - "GetUserByUuidResponseDto", - "ResetUserTrafficResponseDto", - "RevokeUserRequestDto", - "RevokeUserSubscriptionResponseDto", - "TagsResponseDto", - "TelegramUserResponseDto", "UpdateUserRequestDto", + "RevokeUserRequestDto", + "CreateUserResponseDto", "UpdateUserResponseDto", - "UserActiveInboundsDto", - "UserLastConnectedNodeDto", + "GetUserByUuidResponseDto", + "GetUserByShortUuidResponseDto", + "GetUserByUsernameResponseDto", + "GetUserByIdResponseDto", + "DisableUserResponseDto", + "EnableUserResponseDto", + "ResetUserTrafficResponseDto", + "RevokeUserSubscriptionResponseDto", + "ActivateAllInboundsResponseDto", + "GetAllUsersResponseDto", + "GetAllTagsResponseDto", + "GetUserAccessibleNodesResponseDto", + "GetUserSubscriptionRequestHistoryResponseDto", + "TelegramUserResponseDto", + "EmailUserResponseDto", + "TagUserResponseDto", + "DeleteUserResponseDto", "UserResponseDto", "UsersResponseDto", - "TagUserResponseDto", + "TagsResponseDto", + "SubscriptionRequestsResponseData", + "UserTrafficDto", + "ActiveInternalSquadDto", + "SubscriptionRequestRecord", + "HappCrypto", + "UserActiveInboundsDto", + "UserLastConnectedNodeDto", "GetSubscriptionRequestsResponseDto", # Users bulk actions models + "BulkAllExtendExpirationDateRequestDto", + "BulkAllExtendExpirationDateResponseDto", "BulkAllResetTrafficUsersResponseDto", "BulkAllUpdateUsersRequestDto", "BulkAllUpdateUsersResponseDto", + "BulkDeleteUsersByStatusRequestDto", + "BulkDeleteUsersByStatusResponseDto", + "BulkDeleteUsersRequestDto", + "BulkDeleteUsersResponseDto", + "BulkEventResponseData", + "BulkExtendExpirationDateRequestDto", + "BulkExtendExpirationDateResponseDto", + "BulkResetTrafficUsersRequestDto", + "BulkResetTrafficUsersResponseDto", + "BulkResponseData", "BulkResponseDto", + "BulkRevokeUsersSubscriptionRequestDto", + "BulkRevokeUsersSubscriptionResponseDto", "BulkUpdateUsersInternalSquadsRequestDto", + "BulkUpdateUsersRequestDto", + "BulkUpdateUsersResponseDto", + "BulkUpdateUsersSquadsRequestDto", + "BulkUpdateUsersSquadsResponseDto", + "TagStr", "UpdateUserFields", # Users stats models "UserUsageByRange", diff --git a/remnawave/models/hosts.py b/remnawave/models/hosts.py index 3a570b4..1ceb4d7 100644 --- a/remnawave/models/hosts.py +++ b/remnawave/models/hosts.py @@ -153,15 +153,14 @@ class UpdateHostResponseDto(CreateHostResponseDto): pass -class GetAllHostsResponseDto(BaseModel): - """Get all hosts response""" - response: List[HostResponseDto] - +class GetAllHostsResponseDto(RootModel[List[HostResponseDto]]): + root: List[HostResponseDto] + def __iter__(self): - return iter(self.response) - + return iter(self.root) + def __getitem__(self, item): - return self.response[item] + return self.root[item] class GetOneHostResponseDto(HostResponseDto): diff --git a/remnawave/models/remnawave_settings.py b/remnawave/models/remnawave_settings.py index c73cbf3..5f04010 100644 --- a/remnawave/models/remnawave_settings.py +++ b/remnawave/models/remnawave_settings.py @@ -69,9 +69,9 @@ class RemnawaveSettingsData(BaseModel): branding_settings: Optional[BrandingSettings] = Field(None, alias="brandingSettings") -class GetRemnawaveSettingsResponseDto(BaseModel): +class GetRemnawaveSettingsResponseDto(RemnawaveSettingsData): """Get Remnawave settings response""" - response: RemnawaveSettingsData + pass class UpdateRemnawaveSettingsRequestDto(BaseModel): @@ -83,6 +83,6 @@ class UpdateRemnawaveSettingsRequestDto(BaseModel): branding_settings: Optional[BrandingSettings] = Field(None, serialization_alias="brandingSettings") -class UpdateRemnawaveSettingsResponseDto(BaseModel): +class UpdateRemnawaveSettingsResponseDto(RemnawaveSettingsData): """Update Remnawave settings response""" - response: RemnawaveSettingsData \ No newline at end of file + pass \ No newline at end of file diff --git a/remnawave/models/users.py b/remnawave/models/users.py index 371c968..08692da 100644 --- a/remnawave/models/users.py +++ b/remnawave/models/users.py @@ -9,7 +9,6 @@ from pydantic import ( RootModel, StringConstraints, ) -from pydantic.alias_generators import to_camel from remnawave.enums import TrafficLimitStrategy, UserStatus from remnawave.utils.happ_crypt import create_happ_crypto_link @@ -34,8 +33,7 @@ class ActiveInternalSquadDto(BaseModel): class HappCrypto(BaseModel): - cryptoLink: str - + crypto_link: str = Field(alias="cryptoLink") class CreateUserRequestDto(BaseModel): @@ -108,6 +106,7 @@ class UpdateUserRequestDto(BaseModel): ) external_squad_uuid: Optional[UUID] = Field(None, serialization_alias="externalSquadUuid") + class UserTrafficDto(BaseModel): """User traffic information""" used_traffic_bytes: float = Field(alias="usedTrafficBytes") @@ -173,95 +172,19 @@ class UserResponseDto(BaseModel): """Backward compatibility property""" return self.user_traffic.last_connected_node_uuid - # generate happ link @property def happ(self) -> HappCrypto: """Generate Happ Crypto Link""" crypto_link = create_happ_crypto_link(self.subscription_url) return HappCrypto(cryptoLink=crypto_link) -class EmailUserResponseDto(RootModel[list[UserResponseDto]]): - def __iter__(self): - return iter(self.root) - - def __getitem__(self, item): - return self.root[item] - - -class TagUserResponseDto(RootModel[list[UserResponseDto]]): - def __iter__(self): - return iter(self.root) - - def __getitem__(self, item): - return self.root[item] - - -class TelegramUserResponseDto(RootModel[list[UserResponseDto]]): - def __iter__(self): - return iter(self.root) - - def __getitem__(self, item): - return self.root[item] - - -class UsersResponseDto(BaseModel): - users: list[UserResponseDto] - total: int - - -class DeleteUserResponseDto(BaseModel): - is_deleted: bool = Field(alias="isDeleted") - - -class TagsResponseDto(BaseModel): - tags: list[str] - - -class CreateUserResponseDto(UserResponseDto): - pass - - -class UpdateUserResponseDto(UserResponseDto): - pass - - -class DisableUserResponseDto(UserResponseDto): - pass - - -class EnableUserResponseDto(UserResponseDto): - pass - - -class ResetUserTrafficResponseDto(UserResponseDto): - pass - - -class RevokeUserSubscriptionResponseDto(UserResponseDto): - pass - - -class ActivateAllInboundsResponseDto(UserResponseDto): - pass - - -class GetUserByUuidResponseDto(UserResponseDto): - pass - - -class GetUserByShortUuidResponseDto(UserResponseDto): - pass - - -class GetUserByUsernameResponseDto(UserResponseDto): - pass - class RevokeUserRequestDto(BaseModel): - short_uuid: str | None = Field( + """Request DTO for revoking user subscription""" + short_uuid: Optional[str] = Field( None, serialization_alias="shortUuid", - description="Optional. If not provided, a new short UUID will be generated by Remnawave. Please note that it is strongly recommended to allow Remnawave to generate the short UUID.", + description="Optional. If not provided, a new short UUID will be generated by Remnawave.", min_length=6, max_length=48, pattern=r"^[a-zA-Z0-9_-]+$", @@ -269,6 +192,7 @@ class RevokeUserRequestDto(BaseModel): class SubscriptionRequestRecord(BaseModel): + """Subscription request history record""" id: int user_uuid: UUID = Field(alias="userUuid") request_at: datetime = Field(alias="requestAt") @@ -277,9 +201,133 @@ class SubscriptionRequestRecord(BaseModel): class SubscriptionRequestsResponseData(BaseModel): + """Subscription requests response data""" total: int records: List[SubscriptionRequestRecord] +class CreateUserResponseDto(UserResponseDto): + """Response for create user""" + pass + + +class UpdateUserResponseDto(UserResponseDto): + """Response for update user""" + pass + + +class GetUserByUuidResponseDto(UserResponseDto): + """Response for get user by UUID""" + pass + + +class GetUserByShortUuidResponseDto(UserResponseDto): + """Response for get user by short UUID""" + pass + + +class GetUserByUsernameResponseDto(UserResponseDto): + """Response for get user by username""" + pass + + +class GetUserByIdResponseDto(UserResponseDto): + """Response for get user by ID""" + pass + + +class DisableUserResponseDto(UserResponseDto): + """Response for disable user""" + pass + + +class EnableUserResponseDto(UserResponseDto): + """Response for enable user""" + pass + + +class ResetUserTrafficResponseDto(UserResponseDto): + """Response for reset user traffic""" + pass + + +class RevokeUserSubscriptionResponseDto(UserResponseDto): + """Response for revoke user subscription""" + pass + + +class ActivateAllInboundsResponseDto(UserResponseDto): + """Response for activate all inbounds""" + pass + + +class UsersResponseDto(BaseModel): + """Users collection response""" + users: list[UserResponseDto] + total: int + + +class GetAllUsersResponseDto(UsersResponseDto): + """Response for get all users""" + pass + + +class TagsResponseDto(BaseModel): + """Tags collection response""" + tags: list[str] + + +class GetAllTagsResponseDto(TagsResponseDto): + """Response for get all tags""" + pass + + class GetSubscriptionRequestsResponseDto(SubscriptionRequestsResponseData): - pass \ No newline at end of file + """Response for get subscription requests""" + pass + + +class GetUserSubscriptionRequestHistoryResponseDto(SubscriptionRequestsResponseData): + """Response for get user subscription request history""" + pass + + +class DeleteUserResponseDto(BaseModel): + """Response for delete user""" + is_deleted: bool = Field(alias="isDeleted") + + +class EmailUserResponseDto(RootModel[list[UserResponseDto]]): + """Response for get users by email""" + def __iter__(self): + return iter(self.root) + + def __getitem__(self, item): + return self.root[item] + + +class TagUserResponseDto(RootModel[list[UserResponseDto]]): + """Response for get users by tag""" + def __iter__(self): + return iter(self.root) + + def __getitem__(self, item): + return self.root[item] + + +class TelegramUserResponseDto(RootModel[list[UserResponseDto]]): + """Response for get users by telegram ID""" + def __iter__(self): + return iter(self.root) + + def __getitem__(self, item): + return self.root[item] + + +class GetUserAccessibleNodesResponseDto(RootModel[list[UUID]]): + """Response for get user accessible nodes""" + def __iter__(self): + return iter(self.root) + + def __getitem__(self, item): + return self.root[item] \ No newline at end of file diff --git a/remnawave/models/users_bulk_actions.py b/remnawave/models/users_bulk_actions.py index 30aad18..c5275c1 100644 --- a/remnawave/models/users_bulk_actions.py +++ b/remnawave/models/users_bulk_actions.py @@ -1,97 +1,203 @@ from datetime import datetime -from typing import Annotated, List, Literal, Optional +from typing import Annotated, List, Optional from uuid import UUID -from pydantic import BaseModel, EmailStr, Field, constr +from pydantic import BaseModel, Field -from remnawave.enums import UserStatus -from remnawave.enums.users import TrafficLimitStrategy +from remnawave.enums import UserStatus, TrafficLimitStrategy -class BulkUpdateUsersInternalSquadsRequestDto(BaseModel): - uuids: List[UUID] - active_internal_squads: List[UUID] = Field(serialization_alias="activeInternalSquads") - +# Type alias for tag validation TagStr = Annotated[ str, Field(min_length=1, max_length=16, pattern=r"^[A-Z0-9_]+$") ] + +# Request DTOs +class BulkDeleteUsersByStatusRequestDto(BaseModel): + """Request to delete users by status""" + status: UserStatus = Field(default=UserStatus.ACTIVE) + + +class BulkDeleteUsersRequestDto(BaseModel): + """Request to delete users by UUIDs""" + uuids: List[UUID] = Field(min_length=1, max_length=500) + + +class BulkRevokeUsersSubscriptionRequestDto(BaseModel): + """Request to revoke users subscription""" + uuids: List[UUID] = Field(min_length=1, max_length=500) + + +class BulkResetTrafficUsersRequestDto(BaseModel): + """Request to reset traffic for users""" + uuids: List[UUID] = Field(min_length=1, max_length=500) + + class UpdateUserFields(BaseModel): + """Fields to update for users""" status: Optional[UserStatus] = None traffic_limit_bytes: Optional[int] = Field( - None, serialization_alias="trafficLimitBytes", strict=True, ge=0 + None, + serialization_alias="trafficLimitBytes", + ge=0, + description="Traffic limit in bytes. 0 - unlimited" ) traffic_limit_strategy: Optional[TrafficLimitStrategy] = Field( - None, serialization_alias="trafficLimitStrategy" + None, + serialization_alias="trafficLimitStrategy", + description="Traffic limit reset strategy" + ) + expire_at: Optional[datetime] = Field( + None, + serialization_alias="expireAt", + description="Expiration date: 2025-01-17T15:38:45.065Z" ) - expire_at: Optional[datetime] = Field(None, serialization_alias="expireAt") description: Optional[str] = None + telegram_id: Optional[int] = Field(None, serialization_alias="telegramId") + email: Optional[str] = None tag: Optional[TagStr] = Field( None, description="Tag for user. Must be uppercase, alphanumeric, and can include underscores. Max length 16 characters." ) - telegram_id: Optional[int] = Field(None, serialization_alias="telegramId") - email: Optional[str] = None hwid_device_limit: Optional[int] = Field( - None, serialization_alias="hwidDeviceLimit", strict=True, ge=0 + None, + serialization_alias="hwidDeviceLimit", + ge=0 ) - telegram_id: Optional[int] = Field( - None, serialization_alias="telegramId" + external_squad_uuid: Optional[UUID] = Field( + None, + serialization_alias="externalSquadUuid", + description="Optional. External squad UUID." + ) + + +class BulkUpdateUsersRequestDto(BaseModel): + """Request to bulk update users""" + uuids: List[UUID] = Field(min_length=1, max_length=500) + fields: UpdateUserFields + + +class BulkUpdateUsersSquadsRequestDto(BaseModel): + """Request to update users internal squads""" + uuids: List[UUID] = Field(min_length=1, max_length=500) + active_internal_squads: List[UUID] = Field(serialization_alias="activeInternalSquads") + + +class BulkExtendExpirationDateRequestDto(BaseModel): + """Request to extend expiration date for selected users""" + uuids: List[UUID] = Field(min_length=1, max_length=500) + extend_days: int = Field( + serialization_alias="extendDays", + ge=1, + le=9999 ) - class BulkAllUpdateUsersRequestDto(BaseModel): - status: Optional[Literal["ACTIVE", "DISABLED", "LIMITED", "EXPIRED"]] = Field( - "ACTIVE" - ) - + """Request to update all users""" + status: Optional[UserStatus] = Field(default=UserStatus.ACTIVE) traffic_limit_bytes: Optional[int] = Field( None, serialization_alias="trafficLimitBytes", ge=0, description="Traffic limit in bytes. 0 - unlimited" ) - - traffic_limit_strategy: Optional[Literal["NO_RESET", "DAY", "WEEK", "MONTH"]] = Field( + traffic_limit_strategy: Optional[TrafficLimitStrategy] = Field( None, serialization_alias="trafficLimitStrategy", description="Traffic limit reset strategy" ) - expire_at: Optional[datetime] = Field( None, serialization_alias="expireAt", - description="Expiration date" + description="Expiration date: 2025-01-17T15:38:45.065Z" ) - description: Optional[str] = None - - telegram_id: Optional[int] = Field( - None, - serialization_alias="telegramId" - ) - - hwid_device_limit: Optional[int] = Field( - None, serialization_alias="hwidDeviceLimit", strict=True, ge=0 - ) - + telegram_id: Optional[int] = Field(None, serialization_alias="telegramId") email: Optional[str] = None - tag: Optional[TagStr] = Field( None, description="Tag for user. Must be uppercase, alphanumeric, and can include underscores. Max length 16 characters." ) + hwid_device_limit: Optional[int] = Field( + None, + serialization_alias="hwidDeviceLimit", + ge=0 + ) -class BulkResponseDto(BaseModel): +class BulkAllExtendExpirationDateRequestDto(BaseModel): + """Request to extend expiration date for all users""" + extend_days: int = Field( + serialization_alias="extendDays", + ge=1 + ) + + +# Base Response DTOs (без обертки response) +class BulkResponseData(BaseModel): + """Common bulk response with affected rows""" affected_rows: int = Field(alias="affectedRows") -class BulkAllResetTrafficUsersResponseDto(BaseModel): +class BulkEventResponseData(BaseModel): + """Common bulk response with event sent flag""" event_sent: bool = Field(alias="eventSent") -class BulkAllUpdateUsersResponseDto(BaseModel): - event_sent: bool = Field(alias="eventSent") +# Response DTOs - наследуются от базовых +class BulkDeleteUsersByStatusResponseDto(BulkResponseData): + """Response for bulk delete by status""" + pass + + +class BulkDeleteUsersResponseDto(BulkResponseData): + """Response for bulk delete users""" + pass + + +class BulkRevokeUsersSubscriptionResponseDto(BulkResponseData): + """Response for bulk revoke subscription""" + pass + + +class BulkResetTrafficUsersResponseDto(BulkResponseData): + """Response for bulk reset traffic""" + pass + + +class BulkUpdateUsersResponseDto(BulkResponseData): + """Response for bulk update users""" + pass + + +class BulkUpdateUsersSquadsResponseDto(BulkResponseData): + """Response for bulk update squads""" + pass + + +class BulkExtendExpirationDateResponseDto(BulkResponseData): + """Response for bulk extend expiration date""" + pass + + +class BulkAllUpdateUsersResponseDto(BulkEventResponseData): + """Response for bulk update all users""" + pass + + +class BulkAllResetTrafficUsersResponseDto(BulkEventResponseData): + """Response for bulk reset all users traffic""" + pass + + +class BulkAllExtendExpirationDateResponseDto(BulkEventResponseData): + """Response for bulk extend all users expiration date""" + pass + + +# Legacy compatibility +BulkResponseDto = BulkResponseData +BulkUpdateUsersInternalSquadsRequestDto = BulkUpdateUsersSquadsRequestDto \ No newline at end of file