From a880da073fdc552643d7aae41c7c7b4d74eadb60 Mon Sep 17 00:00:00 2001 From: Artem Date: Wed, 11 Mar 2026 13:26:01 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BB=D0=B0=D0=B2=D0=B0=D1=8E=D1=89=D0=B8=D1=85?= =?UTF-8?q?=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20?= =?UTF-8?q?=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B8=D1=82=D1=8C=20=D0=B2=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- remnawave/controllers/subscription.py | 7 +- remnawave/models/auth.py | 13 ++- remnawave/models/config_profiles.py | 10 +-- remnawave/models/external_squads.py | 11 +-- remnawave/models/hosts.py | 10 ++- remnawave/models/hwid.py | 12 +-- remnawave/models/inbounds.py | 6 +- remnawave/models/infra_billing.py | 12 ++- remnawave/models/internal_squads.py | 14 +-- remnawave/models/nodes.py | 7 ++ remnawave/models/subscription.py | 13 ++- remnawave/models/subscription_page.py | 2 +- remnawave/models/subscriptions_template.py | 2 +- remnawave/models/system.py | 100 ++++++++++++++------- remnawave/models/users.py | 8 +- remnawave/models/users_bulk_actions.py | 2 +- 16 files changed, 150 insertions(+), 79 deletions(-) diff --git a/remnawave/controllers/subscription.py b/remnawave/controllers/subscription.py index a30423c..0bd1561 100644 --- a/remnawave/controllers/subscription.py +++ b/remnawave/controllers/subscription.py @@ -34,10 +34,10 @@ class SubscriptionController(BaseController): """None""" ... - @get("/sub/outline/{short_uuid}/{type}/{encoded_tag}", response_class=str) + @get("/sub/outline/{shortUuid}/{type}/{encodedTag}", response_class=str) async def get_subscription_with_type( self, - short_uuid: Annotated[str, Path(description="Short UUID of the user")], + short_uuid: Annotated[str, Path(description="Short UUID of the user", alias="shortUuid")], type: Annotated[ str, Path( @@ -47,7 +47,8 @@ class SubscriptionController(BaseController): encoded_tag: Annotated[ str, Path( - description="Base64 encoded tag for Outline config. This paramter is optional. It is required only when type=ss." + description="Base64 encoded tag for Outline config. This paramter is optional. It is required only when type=ss.", + alias="encodedTag", ), ] = "VGVzdGVy", ) -> str: diff --git a/remnawave/models/auth.py b/remnawave/models/auth.py index fdaab6b..348de8a 100644 --- a/remnawave/models/auth.py +++ b/remnawave/models/auth.py @@ -1,6 +1,6 @@ from typing import Annotated, Any, Dict, Optional -from pydantic import BaseModel, Field, StringConstraints +from pydantic import BaseModel, Field, StringConstraints, field_validator from remnawave.enums.auth import OAuth2Provider @@ -57,6 +57,17 @@ class RegisterRequestDto(BaseModel): username: str password: Annotated[str, StringConstraints(min_length=24)] + @field_validator("password") + @classmethod + def validate_password_complexity(cls, v: str) -> str: + if not any(c.isupper() for c in v): + raise ValueError("Password must contain at least one uppercase letter") + if not any(c.islower() for c in v): + raise ValueError("Password must contain at least one lowercase letter") + if not any(c.isdigit() for c in v): + raise ValueError("Password must contain at least one digit") + return v + class TelegramCallbackRequestDto(BaseModel): id: int diff --git a/remnawave/models/config_profiles.py b/remnawave/models/config_profiles.py index e094c62..f6069e8 100644 --- a/remnawave/models/config_profiles.py +++ b/remnawave/models/config_profiles.py @@ -1,8 +1,8 @@ from datetime import datetime -from typing import Any, Dict, List, Optional +from typing import Annotated, Any, Dict, List, Optional from uuid import UUID -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, StringConstraints class InboundDto(BaseModel): @@ -32,7 +32,7 @@ class ConfigProfileDto(BaseModel): class CreateConfigProfileRequestDto(BaseModel): - name: str + name: Annotated[str, StringConstraints(min_length=2, max_length=30, pattern=r"^[A-Za-z0-9_\s-]+$")] config: Dict[str, Any] @@ -42,7 +42,7 @@ class CreateConfigProfileResponseDto(ConfigProfileDto): class UpdateConfigProfileRequestDto(BaseModel): uuid: UUID - name: Optional[str] = Field(None, pattern=r"^[A-Za-z0-9_-]+$") + name: Optional[Annotated[str, StringConstraints(min_length=2, max_length=30, pattern=r"^[A-Za-z0-9_\s-]+$")]] = None config: Optional[Dict[str, Any]] = None @@ -51,7 +51,7 @@ class UpdateConfigProfileResponseDto(ConfigProfileDto): class GetAllConfigProfilesResponsePaginated(BaseModel): - total: int + total: float config_profiles: List[ConfigProfileDto] = Field(alias="configProfiles") diff --git a/remnawave/models/external_squads.py b/remnawave/models/external_squads.py index 165b7ad..d7def23 100644 --- a/remnawave/models/external_squads.py +++ b/remnawave/models/external_squads.py @@ -58,6 +58,7 @@ class ExternalSquadDto(BaseModel): response_headers: Optional[Dict[str, str]] = Field(None, alias="responseHeaders") hwid_settings: Optional[HwidSettingsDto] = Field(None, alias="hwidSettings") custom_remarks: Optional[CustomRemarksDto] = Field(None, alias="customRemarks") + subpage_config_uuid: Optional[UUID] = Field(None, alias="subpageConfigUuid") created_at: datetime = Field(alias="createdAt") updated_at: datetime = Field(alias="updatedAt") @@ -65,7 +66,7 @@ class ExternalSquadDto(BaseModel): # Request/Response models class GetExternalSquadsResponseDto(BaseModel): """Response with all external squads""" - total: int = Field(alias="total") + total: float = Field(alias="total") external_squads: List[ExternalSquadDto] = Field(alias="externalSquads") @@ -94,6 +95,7 @@ class UpdateExternalSquadRequestDto(BaseModel): hwid_settings: Optional[HwidSettingsDto] = Field(None, alias="hwidSettings") custom_remarks: Optional[CustomRemarksDto] = Field(None, alias="customRemarks") response_headers: Optional[Dict[str, str]] = Field(None, serialization_alias="responseHeaders") + subpage_config_uuid: Optional[UUID] = Field(None, serialization_alias="subpageConfigUuid") class UpdateExternalSquadResponseDto(ExternalSquadDto): @@ -117,15 +119,10 @@ class ReorderExternalSquadsRequestDto(BaseModel): class ReorderExternalSquadsResponseDto(BaseModel): """Response after reordering external squads""" - total: int = Field(alias="total") + total: float = Field(alias="total") external_squads: List[ExternalSquadDto] = Field(alias="externalSquads") -class DeleteExternalSquadResponseDto(BaseModel): - """Response after deleting external squad""" - is_deleted: bool = Field(alias="isDeleted") - - class AddUsersToExternalSquadResponseDto(BaseModel): """Response after adding users to external squad""" event_sent: bool = Field(alias="eventSent") diff --git a/remnawave/models/hosts.py b/remnawave/models/hosts.py index d123445..c732985 100644 --- a/remnawave/models/hosts.py +++ b/remnawave/models/hosts.py @@ -43,7 +43,7 @@ class UpdateHostRequestDto(BaseModel): tag: Optional[Annotated[str, StringConstraints(max_length=32, pattern=r"^[A-Z0-9_:]+$")]] = None is_hidden: Optional[bool] = Field(None, serialization_alias="isHidden") override_sni_from_address: Optional[bool] = Field(None, serialization_alias="overrideSniFromAddress") - keep_blank_sni: Optional[bool] = Field(None, serialization_alias="keepBlankSni") + keep_blank_sni: Optional[bool] = Field(None, serialization_alias="keepSniBlank") vless_route_id: Optional[int] = Field(None, serialization_alias="vlessRouteId", ge=0, le=65535) shuffle_host: Optional[bool] = Field(None, serialization_alias="shuffleHost") mihomo_x25519: Optional[bool] = Field(None, serialization_alias="mihomoX25519") @@ -89,7 +89,7 @@ class HostResponseDto(BaseModel): security_layer: SecurityLayer = Field(SecurityLayer.DEFAULT, alias="securityLayer") is_hidden: bool = Field(False, alias="isHidden") override_sni_from_address: bool = Field(False, alias="overrideSniFromAddress") - keep_blank_sni: bool = Field(False, alias="keepBlankSni") + keep_blank_sni: bool = Field(False, alias="keepSniBlank") allow_insecure: bool = Field(False, alias="allowInsecure") xray_json_template_uuid: UUID | None = Field(alias="xrayJsonTemplateUuid") excluded_internal_squads: List[UUID] = Field(default_factory=list, alias="excludedInternalSquads") @@ -128,7 +128,7 @@ class CreateHostRequestDto(BaseModel): security_layer: SecurityLayer = Field(SecurityLayer.DEFAULT, serialization_alias="securityLayer") is_hidden: bool = Field(False, serialization_alias="isHidden") override_sni_from_address: bool = Field(False, serialization_alias="overrideSniFromAddress") - keep_blank_sni: bool = Field(False, serialization_alias="keepBlankSni") + keep_blank_sni: bool = Field(False, serialization_alias="keepSniBlank") xray_json_template_uuid: Optional[UUID] = Field(None, serialization_alias="xrayJsonTemplateUuid") excluded_internal_squads: List[UUID] = Field(default_factory=list, serialization_alias="excludedInternalSquads") exclude_from_subscription_types: List[SubscriptionType] = Field( @@ -147,6 +147,10 @@ class CreateHostRequestDto(BaseModel): config_profile_uuid: Optional[UUID] = None, **data, ): + # Backward-compatible support for misspelled helper argument used in old tests/examples + if config_profile_uuid is None and "config_profile_inbound_uuid" in data: + config_profile_uuid = data.pop("config_profile_inbound_uuid") + if inbound_uuid is not None and "inbound" not in data: data["inbound"] = CreateHostInboundData( config_profile_uuid=config_profile_uuid diff --git a/remnawave/models/hwid.py b/remnawave/models/hwid.py index 8cf80a7..1db9551 100644 --- a/remnawave/models/hwid.py +++ b/remnawave/models/hwid.py @@ -31,22 +31,22 @@ class HwidDeviceDto(BaseModel): class HwidDevicesData(BaseModel): - total: int + total: float devices: List[HwidDeviceDto] class CreateUserHwidDeviceResponseDto(BaseModel): - total: int + total: float devices: List[HwidDeviceDto] class DeleteUserHwidDeviceResponseDto(BaseModel): - total: int + total: float devices: List[HwidDeviceDto] class GetUserHwidDevicesResponseDto(BaseModel): - total: int + total: float devices: List[HwidDeviceDto] class PlatformStatItem(BaseModel): @@ -82,13 +82,13 @@ class TopUserByHwidDevicesDto(BaseModel): user_uuid: UUID = Field(alias="userUuid") id: int username: str - devices_count: int = Field(alias="devicesCount") + devices_count: float = Field(alias="devicesCount") class TopUsersByHwidDevicesData(BaseModel): """Top users by HWID devices data""" users: list[TopUserByHwidDevicesDto] - total: int + total: float class GetTopUsersByHwidDevicesResponseDto(TopUsersByHwidDevicesData): diff --git a/remnawave/models/inbounds.py b/remnawave/models/inbounds.py index c74ed3c..1af28db 100644 --- a/remnawave/models/inbounds.py +++ b/remnawave/models/inbounds.py @@ -13,11 +13,11 @@ class InboundResponseDto(BaseModel): security: Optional[str] = None port: Optional[float] = None raw_inbound: Optional[Any] = Field(None, alias="rawInbound") - active_squads: Optional[list[UUID]] = Field(None, alias="activeSquads") + active_squads: List[UUID] = Field(default_factory=list, alias="activeSquads") class AllInboundsData(BaseModel): - total: int + total: float inbounds: List[InboundResponseDto] @@ -26,7 +26,7 @@ class GetAllInboundsResponseDto(AllInboundsData): class InboundsByProfileData(BaseModel): - total: int + total: float inbounds: List[InboundResponseDto] diff --git a/remnawave/models/infra_billing.py b/remnawave/models/infra_billing.py index b1ddd0b..ab622c2 100644 --- a/remnawave/models/infra_billing.py +++ b/remnawave/models/infra_billing.py @@ -102,7 +102,7 @@ class UpdateInfraProviderResponseDto(InfraProviderDto): class AllInfraProvidersData(BaseModel): - total: int = Field(alias="total") + total: float = Field(alias="total") providers: List[InfraProviderDto] @@ -135,7 +135,7 @@ class CreateInfraBillingHistoryRecordResponseDto(InfraBillingHistoryDto): class InfraBillingHistoryData(BaseModel): records: List[InfraBillingHistoryDto] - total: int + total: float class GetInfraBillingHistoryRecordsResponseDto(InfraBillingHistoryData): @@ -167,8 +167,12 @@ class UpdateInfraBillingNodeRequestDto(BaseModel): next_billing_at: datetime = Field(serialization_alias="nextBillingAt") -class UpdateInfraBillingNodeResponseDto(InfraBillingNodeDto): - pass +class UpdateInfraBillingNodeResponseDto(BaseModel): + total_billing_nodes: float = Field(alias="totalBillingNodes") + billing_nodes: List[InfraBillingNodeDto] = Field(alias="billingNodes") + available_billing_nodes: List[AvailableBillingNodeDto] = Field(alias="availableBillingNodes") + total_available_billing_nodes: float = Field(alias="totalAvailableBillingNodes") + stats: BillingStatsDto class InfraBillingNodesData(BaseModel): diff --git a/remnawave/models/internal_squads.py b/remnawave/models/internal_squads.py index a00a440..3f704d2 100644 --- a/remnawave/models/internal_squads.py +++ b/remnawave/models/internal_squads.py @@ -1,8 +1,8 @@ from datetime import datetime -from typing import List, Optional +from typing import Annotated, List, Optional from uuid import UUID -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, StringConstraints class InboundsDto(BaseModel): @@ -17,8 +17,8 @@ class InboundsDto(BaseModel): class InfoDto(BaseModel): - members_count: int = Field(alias="membersCount") - inbounds_count: int = Field(alias="inboundsCount") + members_count: float = Field(alias="membersCount") + inbounds_count: float = Field(alias="inboundsCount") class InternalSquadDto(BaseModel): @@ -32,7 +32,7 @@ class InternalSquadDto(BaseModel): class CreateInternalSquadRequestDto(BaseModel): - name: str + name: Annotated[str, StringConstraints(min_length=2, max_length=30, pattern=r"^[A-Za-z0-9_\s-]+$")] inbounds: List[UUID] = Field(default_factory=list) @@ -43,7 +43,7 @@ class CreateInternalSquadResponseDto(InternalSquadDto): class UpdateInternalSquadRequestDto(BaseModel): uuid: UUID inbounds: List[UUID] = Field(default_factory=list) - name: Optional[str] = Field(None, pattern=r"^[A-Za-z0-9_-]+$") + name: Optional[Annotated[str, StringConstraints(min_length=2, max_length=30, pattern=r"^[A-Za-z0-9_\s-]+$")]] = None class UpdateInternalSquadResponseDto(InternalSquadDto): @@ -51,7 +51,7 @@ class UpdateInternalSquadResponseDto(InternalSquadDto): class GetAllInternalSquadsResponse(BaseModel): - total: int + total: float internal_squads: List[InternalSquadDto] = Field(alias="internalSquads") diff --git a/remnawave/models/nodes.py b/remnawave/models/nodes.py index 9eb09ff..9ed1acc 100644 --- a/remnawave/models/nodes.py +++ b/remnawave/models/nodes.py @@ -89,6 +89,9 @@ class CreateNodeRequestDto(BaseModel): serialization_alias="tags", max_length=10 ) + active_plugin_uuid: Optional[UUID] = Field( + None, serialization_alias="activePluginUuid" + ) class UpdateNodeRequestDto(BaseModel): @@ -126,6 +129,9 @@ class UpdateNodeRequestDto(BaseModel): serialization_alias="tags", max_length=10 ) + active_plugin_uuid: Optional[UUID] = Field( + None, serialization_alias="activePluginUuid" + ) class ReorderNodeRequestDto(BaseModel): @@ -163,6 +169,7 @@ class NodeResponseDto(BaseModel): provider_uuid: Optional[UUID] = Field(None, alias="providerUuid") provider: Optional[NodeProviderDto] = None tags: List[str] = Field(default_factory=list, alias="tags") + active_plugin_uuid: Optional[UUID] = Field(None, alias="activePluginUuid") class CreateNodeResponseDto(NodeResponseDto): diff --git a/remnawave/models/subscription.py b/remnawave/models/subscription.py index ed6885a..0ec8a6f 100644 --- a/remnawave/models/subscription.py +++ b/remnawave/models/subscription.py @@ -176,7 +176,7 @@ class GetRawSubscriptionByShortUuidResponseDto(RawSubscriptionResponse): class UserSubscription(BaseModel): short_uuid: str = Field(alias="shortUuid") username: str - days_left: int = Field(alias="daysLeft") + days_left: float = Field(alias="daysLeft") traffic_used: str = Field(alias="trafficUsed") traffic_limit: str = Field(alias="trafficLimit") lifetime_traffic_used: str = Field(alias="lifetimeTrafficUsed") @@ -222,7 +222,7 @@ class SubscriptionWithoutHapp(BaseModel): class GetAllSubscriptionsResponseDto(BaseModel): subscriptions: List[SubscriptionWithoutHapp] - total: int + total: float class GetSubscriptionByUsernameResponseDto(BaseModel): @@ -242,7 +242,14 @@ class GetSubscriptionByUUIDResponseDto(GetSubscriptionByUsernameResponseDto): class GetConnectionKeysByUuidResponseDto(BaseModel): - connection_keys: List[str] = Field(alias="connectionKeys") + enabled_keys: List[str] = Field(alias="enabledKeys") + hidden_keys: List[str] = Field(alias="hiddenKeys") + disabled_keys: List[str] = Field(alias="disabledKeys") + + @property + def connection_keys(self) -> List[str]: + """Backward compatibility: historically SDK exposed a flat list of keys.""" + return self.enabled_keys # Legacy alias for backward compatibility diff --git a/remnawave/models/subscription_page.py b/remnawave/models/subscription_page.py index 5958abc..1fab4cb 100644 --- a/remnawave/models/subscription_page.py +++ b/remnawave/models/subscription_page.py @@ -16,7 +16,7 @@ class SubscriptionPageConfigDto(BaseModel): class GetSubscriptionPageConfigsData(BaseModel): """Data for getting all subscription page configs""" - total: int + total: float configs: List[SubscriptionPageConfigDto] diff --git a/remnawave/models/subscriptions_template.py b/remnawave/models/subscriptions_template.py index eaf43aa..439c068 100644 --- a/remnawave/models/subscriptions_template.py +++ b/remnawave/models/subscriptions_template.py @@ -29,7 +29,7 @@ class GetTemplateResponseDto(TemplateResponseDto): pass class GetTemplatesData(BaseModel): - total: int + total: float templates: List[TemplateInfoDto] class GetTemplatesResponseDto(GetTemplatesData): diff --git a/remnawave/models/system.py b/remnawave/models/system.py index 323fcd2..2a39ed1 100644 --- a/remnawave/models/system.py +++ b/remnawave/models/system.py @@ -10,7 +10,7 @@ from remnawave.models.subscriptions_settings import ResponseRule, ResponseRules class NodeStatistic(BaseModel): node_name: str = Field(alias="nodeName") date: datetime.date - total_bytes: int = Field(alias="totalBytes") + total_bytes: str = Field(alias="totalBytes") class NodesStatisticResponseDto(BaseModel): @@ -32,16 +32,16 @@ class BandwidthStatisticResponseDto(BaseModel): class CPUStatistic(BaseModel): - cores: int - physical_cores: int = Field(alias="physicalCores") + cores: float + physical_cores: float = Field(alias="physicalCores") class MemoryStatistic(BaseModel): - total: int - free: int - used: int - active: int - available: int + total: float + free: float + used: float + active: float + available: float class StatusCounts(BaseModel): @@ -59,18 +59,18 @@ class StatusCounts(BaseModel): class UsersStatistic(BaseModel): status_counts: StatusCounts = Field(alias="statusCounts") - total_users: int = Field(alias="totalUsers") + total_users: float = Field(alias="totalUsers") class OnlineStatistic(BaseModel): - last_day: int = Field(alias="lastDay") - last_week: int = Field(alias="lastWeek") - never_online: int = Field(alias="neverOnline") - online_now: int = Field(alias="onlineNow") + last_day: float = Field(alias="lastDay") + last_week: float = Field(alias="lastWeek") + never_online: float = Field(alias="neverOnline") + online_now: float = Field(alias="onlineNow") class NodesStatistic(BaseModel): - total_online: int = Field(alias="totalOnline") + total_online: float = Field(alias="totalOnline") total_bytes_lifetime: str = Field(alias="totalBytesLifetime") @@ -79,7 +79,7 @@ class StatisticResponseDto(BaseModel): cpu: CPUStatistic memory: MemoryStatistic uptime: float - timestamp: int + timestamp: float users: UsersStatistic online_stats: OnlineStatistic = Field(alias="onlineStats") nodes: NodesStatistic @@ -116,21 +116,57 @@ class GetRemnawaveHealthResponseDto(BaseModel): pm2_stats: List[PM2Stat] = Field(alias="pm2Stats") +class TrafficStatDto(BaseModel): + tag: str + upload: str + download: str + + class NodeMetric(BaseModel): - """Node metric data""" - uuid: str = Field(alias="nodeUuid") - name: Optional[str] = None - address: Optional[str] = None - is_online: Optional[bool] = Field(None, alias="isOnline") - cpu_usage: Optional[float] = Field(None, alias="cpuUsage") - memory_usage: Optional[float] = Field(None, alias="memoryUsage") - network_upload: Optional[int] = Field(None, alias="networkUpload") - network_download: Optional[int] = Field(None, alias="networkDownload") - uptime: Optional[int] = None - last_seen: Optional[datetime.datetime] = Field(None, alias="lastSeen") - connected_users: Optional[int] = Field(None, alias="connectedUsers") - upload: Optional[str] = None - download: Optional[str] = None + """Node metric data (API v1.10)""" + node_uuid: str = Field(alias="nodeUuid") + node_name: str = Field(alias="nodeName") + country_emoji: str = Field(alias="countryEmoji") + provider_name: str = Field(alias="providerName") + users_online: float = Field(alias="usersOnline") + inbounds_stats: List[TrafficStatDto] = Field(alias="inboundsStats") + outbounds_stats: List[TrafficStatDto] = Field(alias="outboundsStats") + + @property + def uuid(self) -> str: + return self.node_uuid + + @property + def name(self) -> str: + return self.node_name + + @property + def connected_users(self) -> float: + return self.users_online + + @property + def cpu_usage(self) -> None: + return None + + @property + def memory_usage(self) -> None: + return None + + @property + def network_upload(self) -> None: + return None + + @property + def network_download(self) -> None: + return None + + @property + def uptime(self) -> None: + return None + + @property + def last_seen(self) -> None: + return None class GetNodesMetricsResponseDto(BaseModel): @@ -143,7 +179,11 @@ class X25519KeyPair(BaseModel): class GetX25519KeyPairResponseDto(BaseModel): - key_pairs: List[X25519KeyPair] = Field(alias="keyPairs") + key_pairs: List[X25519KeyPair] = Field(alias="keypairs") + + +# OpenAPI v1.10 schema name +GenerateX25519ResponseDto = GetX25519KeyPairResponseDto class EncryptHappCryptoLinkRequestDto(BaseModel): diff --git a/remnawave/models/users.py b/remnawave/models/users.py index 6be6cf7..aec4b99 100644 --- a/remnawave/models/users.py +++ b/remnawave/models/users.py @@ -109,8 +109,8 @@ class UpdateUserRequestDto(BaseModel): class UserTrafficDto(BaseModel): """User traffic information""" - used_traffic_bytes: int = Field(alias="usedTrafficBytes") - lifetime_used_traffic_bytes: int = Field(alias="lifetimeUsedTrafficBytes") + used_traffic_bytes: float = Field(alias="usedTrafficBytes") + lifetime_used_traffic_bytes: float = 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") @@ -149,12 +149,12 @@ class UserResponseDto(BaseModel): user_traffic: UserTrafficDto = Field(alias="userTraffic") @property - def used_traffic_bytes(self) -> int: + def used_traffic_bytes(self) -> float: """Backward compatibility property""" return self.user_traffic.used_traffic_bytes @property - def lifetime_used_traffic_bytes(self) -> int: + def lifetime_used_traffic_bytes(self) -> float: """Backward compatibility property""" return self.user_traffic.lifetime_used_traffic_bytes diff --git a/remnawave/models/users_bulk_actions.py b/remnawave/models/users_bulk_actions.py index c5275c1..e46587e 100644 --- a/remnawave/models/users_bulk_actions.py +++ b/remnawave/models/users_bulk_actions.py @@ -139,7 +139,7 @@ class BulkAllExtendExpirationDateRequestDto(BaseModel): # Base Response DTOs (без обертки response) class BulkResponseData(BaseModel): """Common bulk response with affected rows""" - affected_rows: int = Field(alias="affectedRows") + affected_rows: float = Field(alias="affectedRows") class BulkEventResponseData(BaseModel):