Remnawave_python-sdk/remnawave/models/users.py
anatoliy 0b7d386149 feat!: change traffic bytes from float to int
Used float for byte counters was incorrect - bytes are always integers.
2026-01-17 04:29:14 +03:00

355 lines
No EOL
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import datetime
from typing import Annotated, List, Literal, Optional
from uuid import UUID
from pydantic import (
BaseModel,
EmailStr,
Field,
RootModel,
StringConstraints,
)
from remnawave.enums import TrafficLimitStrategy, UserStatus
from remnawave.utils.happ_crypt import create_happ_crypto_link
class UserActiveInboundsDto(BaseModel):
uuid: UUID
tag: str
type: str
network: str | None = None
security: str | None = None
class UserLastConnectedNodeDto(BaseModel):
connected_at: datetime = Field(alias="connectedAt")
node_name: str = Field(alias="nodeName")
class ActiveInternalSquadDto(BaseModel):
uuid: UUID
name: str
class HappCrypto(BaseModel):
crypto_link: str = Field(alias="cryptoLink")
class CreateUserRequestDto(BaseModel):
"""Request DTO for creating a user"""
# Required fields
username: Annotated[
str,
StringConstraints(pattern=r"^[a-zA-Z0-9_-]+$", min_length=3, max_length=36)
] = Field(..., description="Unique username for the user")
expire_at: datetime = Field(..., serialization_alias="expireAt", description="Account expiration date")
# Optional fields with defaults
status: UserStatus = Field(default=UserStatus.ACTIVE, description="User account status")
traffic_limit_strategy: TrafficLimitStrategy = Field(
default=TrafficLimitStrategy.NO_RESET,
serialization_alias="trafficLimitStrategy",
description="Traffic reset strategy"
)
# Optional fields
short_uuid: Optional[str] = Field(None, serialization_alias="shortUuid")
trojan_password: Optional[Annotated[str, StringConstraints(min_length=8, max_length=32)]] = Field(
None, serialization_alias="trojanPassword"
)
vless_uuid: Optional[UUID] = Field(None, serialization_alias="vlessUuid")
ss_password: Optional[Annotated[str, StringConstraints(min_length=8, max_length=32)]] = Field(
None, serialization_alias="ssPassword"
)
traffic_limit_bytes: Optional[int] = Field(
None, serialization_alias="trafficLimitBytes", ge=0
)
created_at: Optional[datetime] = Field(None, serialization_alias="createdAt")
last_traffic_reset_at: Optional[datetime] = Field(None, serialization_alias="lastTrafficResetAt")
description: Optional[str] = None
tag: Optional[Annotated[str, StringConstraints(max_length=16, pattern=r"^[A-Z0-9_]+$")]] = None
telegram_id: Optional[int] = Field(None, serialization_alias="telegramId")
email: Optional[EmailStr] = None
hwid_device_limit: Optional[int] = Field(None, serialization_alias="hwidDeviceLimit", ge=0)
active_internal_squads: Optional[List[UUID]] = Field(None, serialization_alias="activeInternalSquads")
uuid: Optional[UUID] = Field(
None,
description="Optional. Pass UUID to create user with specific UUID, otherwise it will be generated automatically."
)
external_squad_uuid: Optional[UUID] = Field(None, serialization_alias="externalSquadUuid")
class UpdateUserRequestDto(BaseModel):
"""Request DTO for updating a user"""
# Either username or uuid must be provided, uuid has priority
username: Optional[str] = Field(None, description="Username of the user")
uuid: Optional[UUID] = Field(
None,
description="UUID of the user. UUID has higher priority than username"
)
# Optional update fields
status: Optional[UserStatus] = None
description: Optional[str] = None
email: Optional[EmailStr] = None
expire_at: Optional[datetime] = Field(None, serialization_alias="expireAt")
hwid_device_limit: Optional[int] = Field(None, serialization_alias="hwidDeviceLimit", ge=0)
tag: Optional[Annotated[str, StringConstraints(max_length=16, pattern=r"^[A-Z0-9_]+$")]] = None
telegram_id: Optional[int] = Field(None, serialization_alias="telegramId")
traffic_limit_bytes: Optional[int] = Field(None, serialization_alias="trafficLimitBytes", ge=0)
traffic_limit_strategy: Optional[TrafficLimitStrategy] = Field(
None, serialization_alias="trafficLimitStrategy"
)
active_internal_squads: Optional[List[UUID]] = Field(
None, serialization_alias="activeInternalSquads"
)
external_squad_uuid: Optional[UUID] = Field(None, serialization_alias="externalSquadUuid")
class UserTrafficDto(BaseModel):
"""User traffic information"""
used_traffic_bytes: int = Field(alias="usedTrafficBytes")
lifetime_used_traffic_bytes: int = Field(alias="lifetimeUsedTrafficBytes")
online_at: Optional[datetime] = Field(None, alias="onlineAt")
first_connected_at: Optional[datetime] = Field(None, alias="firstConnectedAt")
last_connected_node_uuid: Optional[UUID] = Field(None, alias="lastConnectedNodeUuid")
class UserResponseDto(BaseModel):
"""User response DTO - обновленная структура с userTraffic"""
uuid: UUID
short_uuid: str = Field(alias="shortUuid")
username: str
status: UserStatus = Field(default=UserStatus.ACTIVE)
traffic_limit_bytes: int = Field(0, alias="trafficLimitBytes")
traffic_limit_strategy: TrafficLimitStrategy = Field(
TrafficLimitStrategy.NO_RESET, alias="trafficLimitStrategy"
)
expire_at: datetime = Field(alias="expireAt")
telegram_id: Optional[int] = Field(None, alias="telegramId")
email: Optional[str] = None
description: Optional[str] = None
tag: Optional[str] = None
hwid_device_limit: Optional[int] = Field(None, alias="hwidDeviceLimit")
external_squad_uuid: Optional[UUID] = Field(None, alias="externalSquadUuid")
trojan_password: str = Field(alias="trojanPassword")
vless_uuid: UUID = Field(alias="vlessUuid")
ss_password: str = Field(alias="ssPassword")
last_trigger_threshold: int = Field(0, alias="lastTriggeredThreshold")
sub_revoked_at: Optional[datetime] = Field(None, alias="subRevokedAt")
sub_last_user_agent: Optional[str] = Field(None, alias="subLastUserAgent")
sub_last_opened_at: Optional[datetime] = Field(None, alias="subLastOpenedAt")
last_traffic_reset_at: Optional[datetime] = Field(None, alias="lastTrafficResetAt")
created_at: datetime = Field(alias="createdAt")
updated_at: datetime = Field(alias="updatedAt")
subscription_url: str = Field(alias="subscriptionUrl")
active_internal_squads: list[ActiveInternalSquadDto] = Field(alias="activeInternalSquads")
user_traffic: UserTrafficDto = Field(alias="userTraffic")
@property
def used_traffic_bytes(self) -> int:
"""Backward compatibility property"""
return self.user_traffic.used_traffic_bytes
@property
def lifetime_used_traffic_bytes(self) -> int:
"""Backward compatibility property"""
return self.user_traffic.lifetime_used_traffic_bytes
@property
def online_at(self) -> Optional[datetime]:
"""Backward compatibility property"""
return self.user_traffic.online_at
@property
def first_connected(self) -> Optional[datetime]:
"""Backward compatibility property"""
return self.user_traffic.first_connected_at
@property
def last_connected_node_uuid(self) -> Optional[UUID]:
"""Backward compatibility property"""
return self.user_traffic.last_connected_node_uuid
@property
def happ(self) -> HappCrypto:
"""Generate Happ Crypto Link"""
crypto_link = create_happ_crypto_link(self.subscription_url)
return HappCrypto(cryptoLink=crypto_link)
def happ_with_version(self, version: Literal["v3", "v4"] = "v4") -> HappCrypto:
return self._generate_happ(version=version)
def _generate_happ(self, version):
crypto_link = create_happ_crypto_link(content=self.subscription_url, method=version)
return HappCrypto(cryptoLink=crypto_link)
class RevokeUserRequestDto(BaseModel):
"""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.",
min_length=6,
max_length=48,
pattern=r"^[a-zA-Z0-9_-]+$",
)
class SubscriptionRequestRecord(BaseModel):
"""Subscription request history record"""
id: int
user_uuid: UUID = Field(alias="userUuid")
request_at: datetime = Field(alias="requestAt")
request_ip: Optional[str] = Field(alias="requestIp")
user_agent: Optional[str] = Field(alias="userAgent")
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):
"""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]
def __bool__(self):
"""Return True if list is not empty"""
return bool(self.root)
def __len__(self):
"""Return length of list"""
return len(self.root)
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]
def __bool__(self):
"""Return True if list is not empty"""
return bool(self.root)
def __len__(self):
"""Return length of list"""
return len(self.root)
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]
def __bool__(self):
"""Return True if list is not empty"""
return bool(self.root)
def __len__(self):
"""Return length of list"""
return len(self.root)