mirror of
https://github.com/remnawave/python-sdk.git
synced 2026-08-04 14:37:36 +00:00
feat: Enhance subscription and external squad management
- Updated NodeUsageDto to include node_uuid and changed date to datetime. - Changed total fields in subscription and subscription request history models from float to int. - Introduced response rules and conditions for subscription settings, including new models for response modifications. - Added external squads management with CRUD operations and associated models. - Implemented passkey management with registration and verification endpoints. - Enhanced snippets management with full CRUD operations and validation. - Added OAuth2 provider enum for better authentication handling.
This commit is contained in:
parent
31a189ca42
commit
9e2822774a
39 changed files with 1355 additions and 181 deletions
|
|
@ -63,7 +63,8 @@ pip install git+https://github.com/remnawave/python-sdk.git@development
|
|||
|
||||
| Contract Version | Remnawave Panel Version |
|
||||
| ---------------- | ----------------------- |
|
||||
| 2.1.19 | >=2.1.19 |
|
||||
| 2.2.13 | >=2.2.0 |
|
||||
| 2.1.19 | >=2.1.19, <2.2.0 |
|
||||
| 2.1.18 | >=2.1.18 |
|
||||
| 2.1.17 | >=2.1.16, <=2.1.17 |
|
||||
| 2.1.16 | >=2.1.16 |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[project]
|
||||
name = "remnawave"
|
||||
version = "2.1.19.post.1"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.1.19."
|
||||
version = "2.2.3"
|
||||
description = "A Python SDK for interacting with the Remnawave API v2.2.3."
|
||||
authors = [
|
||||
{name = "Artem",email = "dev@forestsnet.com"}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -29,7 +29,11 @@ from remnawave.controllers import (
|
|||
UsersStatsController,
|
||||
WebhookUtility,
|
||||
XrayConfigController,
|
||||
SubscriptionRequestHistoryController
|
||||
SubscriptionRequestHistoryController,
|
||||
PasskeysController,
|
||||
ExternalSquadsController,
|
||||
SnippetsController,
|
||||
RemnawaveSettingsController,
|
||||
# WebhookUtility is not a controller, but it's included in the controllers module for convenience
|
||||
)
|
||||
|
||||
|
|
@ -93,6 +97,10 @@ class RemnawaveSDK:
|
|||
self.users_stats = UsersStatsController(self._client)
|
||||
self.webhook_utility = WebhookUtility()
|
||||
self.xray_config = XrayConfigController(self._client)
|
||||
self.passkeys = PasskeysController(self._client)
|
||||
self.external_squads = ExternalSquadsController(self._client)
|
||||
self.snippets = SnippetsController(self._client)
|
||||
self.remnawave_settings = RemnawaveSettingsController(self._client)
|
||||
|
||||
def _validate_params(self) -> None:
|
||||
if self._client is None:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ from .users_stats import UsersStatsController
|
|||
from .webhooks import WebhookUtility
|
||||
from .xray_config import XrayConfigController
|
||||
from .subscriptions_request import SubscriptionRequestHistoryController
|
||||
from .passkeys import PasskeysController
|
||||
from .external_squads import ExternalSquadsController
|
||||
from .snippets import SnippetsController
|
||||
from .remnawave_settings import RemnawaveSettingsController
|
||||
|
||||
|
||||
__all__ = [
|
||||
"APITokensManagementController",
|
||||
|
|
@ -51,4 +56,8 @@ __all__ = [
|
|||
"WebhookUtility",
|
||||
"XrayConfigController",
|
||||
"SubscriptionRequestHistoryController",
|
||||
"PasskeysController",
|
||||
"ExternalSquadsController",
|
||||
"SnippetsController",
|
||||
"RemnawaveSettingsController",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ from remnawave.models import (
|
|||
GetStatusResponseDto,
|
||||
LoginRequestDto,
|
||||
LoginResponseDto,
|
||||
OAuth2AuthorizeRequestDto,
|
||||
OAuth2AuthorizeResponseDto,
|
||||
OAuth2CallbackRequestDto,
|
||||
OAuth2CallbackResponseDto,
|
||||
VerifyPasskeyAuthenticationRequestDto,
|
||||
VerifyPasskeyAuthenticationResponseDto,
|
||||
GetPasskeyAuthenticationOptionsResponseDto,
|
||||
RegisterRequestDto,
|
||||
RegisterResponseDto,
|
||||
TelegramCallbackRequestDto,
|
||||
|
|
@ -44,4 +51,35 @@ class AuthController(BaseController):
|
|||
body: Annotated[TelegramCallbackRequestDto, PydanticBody()],
|
||||
) -> TelegramCallbackResponseDto:
|
||||
"""OAuth2 Telegram callback"""
|
||||
...
|
||||
|
||||
@post("/auth/oauth2/authorize", response_class=OAuth2AuthorizeResponseDto)
|
||||
async def oauth2_authorize(
|
||||
self,
|
||||
body: Annotated[OAuth2AuthorizeRequestDto, PydanticBody()],
|
||||
) -> OAuth2AuthorizeResponseDto:
|
||||
"""Initiate OAuth2 authorization"""
|
||||
...
|
||||
|
||||
@post("/auth/oauth2/callback", response_class=OAuth2CallbackResponseDto)
|
||||
async def oauth2_callback(
|
||||
self,
|
||||
body: Annotated[OAuth2CallbackRequestDto, PydanticBody()],
|
||||
) -> OAuth2CallbackResponseDto:
|
||||
"""Callback from OAuth2"""
|
||||
...
|
||||
|
||||
@get("/auth/passkey/authentication/options", response_class=GetPasskeyAuthenticationOptionsResponseDto)
|
||||
async def passkey_authentication_options(
|
||||
self,
|
||||
) -> GetPasskeyAuthenticationOptionsResponseDto:
|
||||
"""Get the authentication options for passkey"""
|
||||
...
|
||||
|
||||
@post("/auth/passkey/authentication/verify", response_class=VerifyPasskeyAuthenticationResponseDto)
|
||||
async def passkey_authentication_verify(
|
||||
self,
|
||||
body: Annotated[VerifyPasskeyAuthenticationRequestDto, PydanticBody()],
|
||||
) -> VerifyPasskeyAuthenticationResponseDto:
|
||||
"""Verify the authentication for passkey"""
|
||||
...
|
||||
|
|
@ -67,3 +67,12 @@ class ConfigProfilesController(BaseController):
|
|||
) -> DeleteConfigProfileResponseDto:
|
||||
"""Delete config profile"""
|
||||
...
|
||||
|
||||
# Get computed config profile by uuid
|
||||
@get("/config-profiles/{uuid}/computed-config", response_class=GetConfigProfileByUuidResponseDto)
|
||||
async def get_computed_config_profile_by_uuid(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="UUID of the config profile")],
|
||||
) -> GetConfigProfileByUuidResponseDto:
|
||||
"""Get computed config profile by uuid"""
|
||||
...
|
||||
73
remnawave/controllers/external_squads.py
Normal file
73
remnawave/controllers/external_squads.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
from typing import Annotated
|
||||
|
||||
from rapid_api_client.annotations import PydanticBody
|
||||
|
||||
from remnawave.models import (
|
||||
AddUsersToExternalSquadResponseDto,
|
||||
CreateExternalSquadRequestDto,
|
||||
CreateExternalSquadResponseDto,
|
||||
DeleteExternalSquadResponseDto,
|
||||
GetExternalSquadByUuidResponseDto,
|
||||
GetExternalSquadsResponseDto,
|
||||
RemoveUsersFromExternalSquadResponseDto,
|
||||
UpdateExternalSquadRequestDto,
|
||||
UpdateExternalSquadResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, delete, get, patch, post
|
||||
|
||||
|
||||
class ExternalSquadsController(BaseController):
|
||||
@get("/external-squads", response_class=GetExternalSquadsResponseDto)
|
||||
async def get_external_squads(
|
||||
self,
|
||||
) -> GetExternalSquadsResponseDto:
|
||||
"""Get all external squads"""
|
||||
...
|
||||
|
||||
@post("/external-squads", response_class=CreateExternalSquadResponseDto)
|
||||
async def create_external_squad(
|
||||
self,
|
||||
body: Annotated[CreateExternalSquadRequestDto, PydanticBody()],
|
||||
) -> CreateExternalSquadResponseDto:
|
||||
"""Create external squad"""
|
||||
...
|
||||
|
||||
@patch("/external-squads", response_class=UpdateExternalSquadResponseDto)
|
||||
async def update_external_squad(
|
||||
self,
|
||||
body: Annotated[UpdateExternalSquadRequestDto, PydanticBody()],
|
||||
) -> UpdateExternalSquadResponseDto:
|
||||
"""Update external squad"""
|
||||
...
|
||||
|
||||
@get("/external-squads/{uuid}", response_class=GetExternalSquadByUuidResponseDto)
|
||||
async def get_external_squad_by_uuid(
|
||||
self,
|
||||
uuid: str,
|
||||
) -> GetExternalSquadByUuidResponseDto:
|
||||
"""Get external squad by uuid"""
|
||||
...
|
||||
|
||||
@delete("/external-squads/{uuid}", response_class=DeleteExternalSquadResponseDto)
|
||||
async def delete_external_squad(
|
||||
self,
|
||||
uuid: str,
|
||||
) -> DeleteExternalSquadResponseDto:
|
||||
"""Delete external squad"""
|
||||
...
|
||||
|
||||
@post("/external-squads/{uuid}/bulk-actions/add-users", response_class=AddUsersToExternalSquadResponseDto)
|
||||
async def add_users_to_external_squad(
|
||||
self,
|
||||
uuid: str,
|
||||
) -> AddUsersToExternalSquadResponseDto:
|
||||
"""Add all users to external squad"""
|
||||
...
|
||||
|
||||
@delete("/external-squads/{uuid}/bulk-actions/remove-users", response_class=RemoveUsersFromExternalSquadResponseDto)
|
||||
async def remove_users_from_external_squad(
|
||||
self,
|
||||
uuid: str,
|
||||
) -> RemoveUsersFromExternalSquadResponseDto:
|
||||
"""Delete users from external squad"""
|
||||
...
|
||||
|
|
@ -17,7 +17,7 @@ from remnawave.models import (
|
|||
RestartNodeResponseDto,
|
||||
UpdateNodeRequestDto,
|
||||
UpdateNodeResponseDto,
|
||||
RestartAllNodesRequestDto,
|
||||
RestartAllNodesRequestBodyDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, delete, get, patch, post
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ class NodesController(BaseController):
|
|||
@post("/nodes/actions/restart-all", response_class=RestartAllNodesResponseDto)
|
||||
async def restart_all_nodes(
|
||||
self,
|
||||
body: Annotated[RestartAllNodesRequestDto, PydanticBody()],
|
||||
body: Annotated[RestartAllNodesRequestBodyDto, PydanticBody()],
|
||||
) -> RestartAllNodesResponseDto:
|
||||
"""Restart All Nodes"""
|
||||
...
|
||||
|
|
@ -100,4 +100,4 @@ class NodesController(BaseController):
|
|||
body: Annotated[ReorderNodeRequestDto, PydanticBody()],
|
||||
) -> ReorderNodeResponseDto:
|
||||
"""Reorder Nodes"""
|
||||
...
|
||||
...
|
||||
45
remnawave/controllers/passkeys.py
Normal file
45
remnawave/controllers/passkeys.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from typing import Annotated
|
||||
|
||||
from rapid_api_client.annotations import PydanticBody
|
||||
|
||||
from remnawave.models import (
|
||||
DeletePasskeyRequestDto,
|
||||
DeletePasskeyResponseDto,
|
||||
GetAllPasskeysResponseDto,
|
||||
GetPasskeyRegistrationOptionsResponseDto,
|
||||
VerifyPasskeyRegistrationRequestDto,
|
||||
VerifyPasskeyRegistrationResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, delete, get, post
|
||||
|
||||
|
||||
class PasskeysController(BaseController):
|
||||
@get("/passkeys/registration/options", response_class=GetPasskeyRegistrationOptionsResponseDto)
|
||||
async def passkey_registration_options(
|
||||
self,
|
||||
) -> GetPasskeyRegistrationOptionsResponseDto:
|
||||
"""Get registration options for passkey"""
|
||||
...
|
||||
|
||||
@post("/passkeys/registration/verify", response_class=VerifyPasskeyRegistrationResponseDto)
|
||||
async def passkey_registration_verify(
|
||||
self,
|
||||
body: Annotated[VerifyPasskeyRegistrationRequestDto, PydanticBody()],
|
||||
) -> VerifyPasskeyRegistrationResponseDto:
|
||||
"""Verify registration for passkey"""
|
||||
...
|
||||
|
||||
@get("/passkeys", response_class=GetAllPasskeysResponseDto)
|
||||
async def get_active_passkeys(
|
||||
self,
|
||||
) -> GetAllPasskeysResponseDto:
|
||||
"""Get all passkeys"""
|
||||
...
|
||||
|
||||
@delete("/passkeys", response_class=DeletePasskeyResponseDto)
|
||||
async def delete_passkey(
|
||||
self,
|
||||
body: Annotated[DeletePasskeyRequestDto, PydanticBody()],
|
||||
) -> DeletePasskeyResponseDto:
|
||||
"""Delete a passkey by ID"""
|
||||
...
|
||||
25
remnawave/controllers/remnawave_settings.py
Normal file
25
remnawave/controllers/remnawave_settings.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from typing import Annotated
|
||||
|
||||
from rapid_api_client.annotations import PydanticBody
|
||||
|
||||
from remnawave.models import (
|
||||
GetRemnawaveSettingsResponseDto,
|
||||
UpdateRemnawaveSettingsRequestDto,
|
||||
UpdateRemnawaveSettingsResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, get, patch
|
||||
|
||||
|
||||
class RemnawaveSettingsController(BaseController):
|
||||
@get("/remnawave-settings", response_class=GetRemnawaveSettingsResponseDto)
|
||||
async def get_settings(self) -> GetRemnawaveSettingsResponseDto:
|
||||
"""Get Remnawave settings"""
|
||||
...
|
||||
|
||||
@patch("/remnawave-settings", response_class=UpdateRemnawaveSettingsResponseDto)
|
||||
async def update_settings(
|
||||
self,
|
||||
body: Annotated[UpdateRemnawaveSettingsRequestDto, PydanticBody()],
|
||||
) -> UpdateRemnawaveSettingsResponseDto:
|
||||
"""Update Remnawave settings"""
|
||||
...
|
||||
45
remnawave/controllers/snippets.py
Normal file
45
remnawave/controllers/snippets.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
from typing import Annotated
|
||||
|
||||
from rapid_api_client.annotations import PydanticBody
|
||||
|
||||
from remnawave.models import (
|
||||
CreateSnippetRequestDto,
|
||||
CreateSnippetResponseDto,
|
||||
DeleteSnippetRequestDto,
|
||||
DeleteSnippetResponseDto,
|
||||
GetSnippetsResponseDto,
|
||||
UpdateSnippetRequestDto,
|
||||
UpdateSnippetResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, delete, get, post, patch
|
||||
|
||||
|
||||
class SnippetsController(BaseController):
|
||||
@get("/snippets", response_class=GetSnippetsResponseDto)
|
||||
async def get_snippets(self) -> GetSnippetsResponseDto:
|
||||
"""Get snippets"""
|
||||
...
|
||||
|
||||
@post("/snippets", response_class=CreateSnippetResponseDto)
|
||||
async def create_snippet(
|
||||
self,
|
||||
body: Annotated[CreateSnippetRequestDto, PydanticBody()],
|
||||
) -> CreateSnippetResponseDto:
|
||||
"""Create snippet"""
|
||||
...
|
||||
|
||||
@patch("/snippets", response_class=UpdateSnippetResponseDto)
|
||||
async def update_snippet(
|
||||
self,
|
||||
body: Annotated[UpdateSnippetRequestDto, PydanticBody()],
|
||||
) -> UpdateSnippetResponseDto:
|
||||
"""Update snippet"""
|
||||
...
|
||||
|
||||
@delete("/snippets", response_class=DeleteSnippetResponseDto)
|
||||
async def delete_snippet_by_name(
|
||||
self,
|
||||
body: Annotated[DeleteSnippetRequestDto, PydanticBody()],
|
||||
) -> DeleteSnippetResponseDto:
|
||||
"""Delete snippet"""
|
||||
...
|
||||
|
|
@ -3,29 +3,52 @@ from typing import Annotated
|
|||
from rapid_api_client.annotations import Path, PydanticBody
|
||||
|
||||
from remnawave.enums import TemplateType
|
||||
from remnawave.models import GetTemplateResponseDto, UpdateTemplateRequestDto, UpdateTemplateResponseDto
|
||||
from remnawave.rapid import BaseController, get, put
|
||||
from remnawave.models import (
|
||||
CreateSubscriptionTemplateRequestDto,
|
||||
CreateSubscriptionTemplateResponseDto,
|
||||
DeleteSubscriptionTemplateResponseDto,
|
||||
GetTemplateResponseDto,
|
||||
GetTemplatesResponseDto,
|
||||
UpdateTemplateRequestDto,
|
||||
UpdateTemplateResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, delete, get, patch, post
|
||||
|
||||
|
||||
class SubscriptionsTemplateController(BaseController):
|
||||
@get(
|
||||
"/subscription-templates/{template_type}",
|
||||
response_class=GetTemplateResponseDto,
|
||||
)
|
||||
async def get_template(
|
||||
self,
|
||||
template_type: Annotated[TemplateType, Path(description="Template type")],
|
||||
) -> GetTemplateResponseDto:
|
||||
"""Get Template"""
|
||||
@get("/subscription-templates", response_class=GetTemplatesResponseDto)
|
||||
async def get_all_templates(self) -> GetTemplatesResponseDto:
|
||||
"""Get all subscription templates (without content)"""
|
||||
...
|
||||
|
||||
@put(
|
||||
"/subscription-templates",
|
||||
response_class=UpdateTemplateResponseDto,
|
||||
)
|
||||
@post("/subscription-templates", response_class=CreateSubscriptionTemplateResponseDto)
|
||||
async def create_template(
|
||||
self,
|
||||
body: Annotated[CreateSubscriptionTemplateRequestDto, PydanticBody()],
|
||||
) -> CreateSubscriptionTemplateResponseDto:
|
||||
"""Create subscription template"""
|
||||
...
|
||||
|
||||
@patch("/subscription-templates", response_class=UpdateTemplateResponseDto)
|
||||
async def update_template(
|
||||
self,
|
||||
body: Annotated[UpdateTemplateRequestDto, PydanticBody()],
|
||||
) -> UpdateTemplateResponseDto:
|
||||
"""Update Template"""
|
||||
"""Update subscription template"""
|
||||
...
|
||||
|
||||
@get("/subscription-templates/{uuid}", response_class=GetTemplateResponseDto)
|
||||
async def get_template_by_uuid(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="Template UUID")],
|
||||
) -> GetTemplateResponseDto:
|
||||
"""Get subscription template by uuid"""
|
||||
...
|
||||
|
||||
@delete("/subscription-templates/{uuid}", response_class=DeleteSubscriptionTemplateResponseDto)
|
||||
async def delete_template(
|
||||
self,
|
||||
uuid: Annotated[str, Path(description="Template UUID")],
|
||||
) -> DeleteSubscriptionTemplateResponseDto:
|
||||
"""Delete subscription template"""
|
||||
...
|
||||
|
|
@ -1,12 +1,18 @@
|
|||
from typing import Annotated
|
||||
from rapid_api_client import PydanticBody
|
||||
from remnawave.models import (
|
||||
GetBandwidthStatsResponseDto,
|
||||
GetNodesStatisticsResponseDto,
|
||||
GetStatsResponseDto,
|
||||
GetNodesMetricsResponseDto,
|
||||
GetRemnawaveHealthResponseDto,
|
||||
GetX25519KeyPairResponseDto
|
||||
GetX25519KeyPairResponseDto,
|
||||
EncryptHappCryptoLinkRequestDto,
|
||||
EncryptHappCryptoLinkResponseDto,
|
||||
DebugSrrMatcherRequestDto,
|
||||
DebugSrrMatcherResponseDto,
|
||||
)
|
||||
from remnawave.rapid import BaseController, get
|
||||
from remnawave.rapid import BaseController, get, post
|
||||
|
||||
|
||||
class SystemController(BaseController):
|
||||
|
|
@ -50,4 +56,20 @@ class SystemController(BaseController):
|
|||
self,
|
||||
) -> GetX25519KeyPairResponseDto:
|
||||
"""Get X25519 Key Pair"""
|
||||
...
|
||||
|
||||
@post("/system/tools/happ/encrypt", response_class=EncryptHappCryptoLinkResponseDto)
|
||||
async def encrypt_happ_crypto_link(
|
||||
self,
|
||||
body: Annotated[EncryptHappCryptoLinkRequestDto, PydanticBody()],
|
||||
) -> EncryptHappCryptoLinkResponseDto:
|
||||
"""Encrypt Happ Crypto Link"""
|
||||
...
|
||||
|
||||
@post("/system/testers/srr-matcher", response_class=DebugSrrMatcherResponseDto)
|
||||
async def debug_srr_matcher(
|
||||
self,
|
||||
body: Annotated[DebugSrrMatcherRequestDto, PydanticBody()],
|
||||
) -> DebugSrrMatcherResponseDto:
|
||||
"""Test SRR Matcher"""
|
||||
...
|
||||
|
|
@ -87,3 +87,15 @@ class UsersBulkActionsController(BaseController):
|
|||
) -> BulkAllResetTrafficUsersResponseDto:
|
||||
"""Bulk Reset All Users Traffic"""
|
||||
...
|
||||
|
||||
@post(
|
||||
"/users/bulk/update-squads",
|
||||
response_class=BulkResponseDto,
|
||||
)
|
||||
async def bulk_update_users_internal_squad(
|
||||
self,
|
||||
uuids: Annotated[List[UUID], AttributeBody()],
|
||||
active_internal_squads: Annotated[List[UUID], AttributeBody(serialization_alias="activeInternalSquads")],
|
||||
) -> BulkResponseDto:
|
||||
"""Bulk Update Users External Squad"""
|
||||
...
|
||||
|
|
@ -8,7 +8,16 @@ from .users import TrafficLimitStrategy, UserStatus
|
|||
from .webhook import (
|
||||
TCRMEvents, TErrorsEvents, TNodeEvents, TResetPeriods, TServiceEvents, TUserEvents, TUserHwidDevicesEvents, TUsersStatus
|
||||
)
|
||||
from .auth import OAuth2Provider
|
||||
from .subscriptions_settings import (
|
||||
ResponseRuleConditionOperator,
|
||||
ResponseRuleOperator,
|
||||
ResponseRuleVersion,
|
||||
ResponseType,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"OAuth2Provider",
|
||||
"TrafficLimitStrategy",
|
||||
"UserStatus",
|
||||
"ErrorCode",
|
||||
|
|
@ -17,6 +26,10 @@ __all__ = [
|
|||
"Fingerprint",
|
||||
"SecurityLayer",
|
||||
"TemplateType",
|
||||
"ResponseRuleConditionOperator",
|
||||
"ResponseRuleOperator",
|
||||
"ResponseRuleVersion",
|
||||
"ResponseType",
|
||||
# Webhook enums
|
||||
"TNodeEvents",
|
||||
"TUserEvents",
|
||||
|
|
|
|||
7
remnawave/enums/auth.py
Normal file
7
remnawave/enums/auth.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from enum import StrEnum
|
||||
|
||||
class OAuth2Provider(StrEnum):
|
||||
"""OAuth2 Provider enum"""
|
||||
GITHUB = "github"
|
||||
POCKETID = "pocketid"
|
||||
YANDEX = "yandex"
|
||||
40
remnawave/enums/subscriptions_settings.py
Normal file
40
remnawave/enums/subscriptions_settings.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from enum import StrEnum
|
||||
|
||||
class ResponseRuleOperator(StrEnum):
|
||||
"""Response rule logical operators"""
|
||||
AND = "AND"
|
||||
OR = "OR"
|
||||
|
||||
|
||||
class ResponseRuleConditionOperator(StrEnum):
|
||||
"""Response rule condition operators"""
|
||||
EQUALS = "EQUALS"
|
||||
NOT_EQUALS = "NOT_EQUALS"
|
||||
CONTAINS = "CONTAINS"
|
||||
NOT_CONTAINS = "NOT_CONTAINS"
|
||||
STARTS_WITH = "STARTS_WITH"
|
||||
NOT_STARTS_WITH = "NOT_STARTS_WITH"
|
||||
ENDS_WITH = "ENDS_WITH"
|
||||
NOT_ENDS_WITH = "NOT_ENDS_WITH"
|
||||
REGEX = "REGEX"
|
||||
NOT_REGEX = "NOT_REGEX"
|
||||
|
||||
|
||||
class ResponseType(StrEnum):
|
||||
"""Response types for subscription rules"""
|
||||
XRAY_JSON = "XRAY_JSON"
|
||||
XRAY_BASE64 = "XRAY_BASE64"
|
||||
MIHOMO = "MIHOMO"
|
||||
STASH = "STASH"
|
||||
CLASH = "CLASH"
|
||||
SINGBOX = "SINGBOX"
|
||||
BROWSER = "BROWSER"
|
||||
BLOCK = "BLOCK"
|
||||
STATUS_CODE_404 = "STATUS_CODE_404"
|
||||
STATUS_CODE_451 = "STATUS_CODE_451"
|
||||
SOCKET_DROP = "SOCKET_DROP"
|
||||
|
||||
|
||||
class ResponseRuleVersion(StrEnum):
|
||||
"""Response rules config version"""
|
||||
V1 = "1"
|
||||
|
|
@ -14,6 +14,13 @@ from .auth import (
|
|||
TelegramCallbackRequestDto,
|
||||
TelegramCallbackResponseDto,
|
||||
LoginTelegramRequestDto, # Legacy alias
|
||||
OAuth2AuthorizeRequestDto,
|
||||
OAuth2AuthorizeResponseDto,
|
||||
OAuth2CallbackRequestDto,
|
||||
OAuth2CallbackResponseDto,
|
||||
VerifyPasskeyAuthenticationRequestDto,
|
||||
VerifyPasskeyAuthenticationResponseDto,
|
||||
GetPasskeyAuthenticationOptionsResponseDto,
|
||||
)
|
||||
from .bandwidthstats import (
|
||||
GetNodeUserUsageByRangeResponseDto,
|
||||
|
|
@ -36,6 +43,7 @@ from .config_profiles import (
|
|||
GetConfigProfileByUuidResponseDto,
|
||||
GetInboundsByProfileUuidResponseDto,
|
||||
InboundDto,
|
||||
NodesProfileDto,
|
||||
UpdateConfigProfileRequestDto,
|
||||
UpdateConfigProfileResponseDto,
|
||||
)
|
||||
|
|
@ -162,7 +170,8 @@ from .nodes import (
|
|||
RestartNodeResponseDto,
|
||||
UpdateNodeRequestDto,
|
||||
UpdateNodeResponseDto,
|
||||
RestartAllNodesRequestDto,
|
||||
RestartAllNodesRequestDto, # Legacy alias,
|
||||
RestartAllNodesRequestBodyDto,
|
||||
)
|
||||
from .nodes_usage_history import (
|
||||
GetNodeUserUsageByRangeResponseDto,
|
||||
|
|
@ -184,11 +193,21 @@ from .subscription import (
|
|||
)
|
||||
from .subscriptions_settings import (
|
||||
GetSubscriptionSettingsResponseDto,
|
||||
ResponseModificationHeader,
|
||||
ResponseModifications,
|
||||
ResponseRule,
|
||||
ResponseRuleCondition,
|
||||
ResponseRules,
|
||||
SubscriptionSettingsResponseDto,
|
||||
UpdateSubscriptionSettingsRequestDto,
|
||||
UpdateSubscriptionSettingsResponseDto,
|
||||
)
|
||||
from .subscriptions_template import (
|
||||
CreateSubscriptionTemplateRequestDto,
|
||||
CreateSubscriptionTemplateResponseDto,
|
||||
DeleteSubscriptionTemplateResponseDto,
|
||||
GetTemplatesResponseDto,
|
||||
TemplateInfoDto,
|
||||
GetTemplateResponseDto,
|
||||
TemplateResponseDto,
|
||||
UpdateTemplateRequestDto,
|
||||
|
|
@ -212,6 +231,10 @@ from .system import (
|
|||
GetNodesMetricsResponseDto,
|
||||
GetX25519KeyPairResponseDto,
|
||||
X25519KeyPair,
|
||||
DebugSrrMatcherRequestDto,
|
||||
DebugSrrMatcherResponseDto,
|
||||
EncryptHappCryptoLinkRequestDto,
|
||||
EncryptHappCryptoLinkResponseDto,
|
||||
)
|
||||
from .users import (
|
||||
ActiveInternalSquadDto,
|
||||
|
|
@ -271,6 +294,56 @@ from .webhook import (
|
|||
CrmEventDto,
|
||||
WebhookPayloadDto,
|
||||
)
|
||||
from .passkeys import (
|
||||
DeletePasskeyRequestDto,
|
||||
DeletePasskeyResponseDto,
|
||||
GetAllPasskeysResponseDto,
|
||||
GetPasskeyRegistrationOptionsResponseDto,
|
||||
PasskeyDto,
|
||||
VerifyPasskeyRegistrationRequestDto,
|
||||
VerifyPasskeyRegistrationResponseDto,
|
||||
)
|
||||
from .external_squads import (
|
||||
AddUsersToExternalSquadResponseDto,
|
||||
CreateExternalSquadRequestDto,
|
||||
CreateExternalSquadResponseDto,
|
||||
DeleteExternalSquadResponseDto,
|
||||
ExternalSquadDto,
|
||||
ExternalSquadInfoDto,
|
||||
ExternalSquadSubscriptionSettingsDto,
|
||||
ExternalSquadTemplateDto,
|
||||
GetExternalSquadByUuidResponseDto,
|
||||
GetExternalSquadsResponseDto,
|
||||
RemoveUsersFromExternalSquadResponseDto,
|
||||
TemplateType,
|
||||
UpdateExternalSquadRequestDto,
|
||||
UpdateExternalSquadResponseDto,
|
||||
)
|
||||
from .snippets import (
|
||||
CreateSnippetRequestDto,
|
||||
CreateSnippetResponseDto,
|
||||
DeleteSnippetRequestDto,
|
||||
DeleteSnippetResponseDto,
|
||||
GetSnippetsResponseDto,
|
||||
SnippetItem,
|
||||
SnippetsData,
|
||||
UpdateSnippetRequestDto,
|
||||
UpdateSnippetResponseDto,
|
||||
)
|
||||
from .remnawave_settings import (
|
||||
BrandingSettings,
|
||||
GetRemnawaveSettingsResponseDto,
|
||||
GitHubOAuth2Settings,
|
||||
OAuth2Settings,
|
||||
PasskeySettings,
|
||||
PasswordSettings,
|
||||
PocketIdOAuth2Settings,
|
||||
RemnawaveSettingsData,
|
||||
TelegramAuthSettings,
|
||||
UpdateRemnawaveSettingsRequestDto,
|
||||
UpdateRemnawaveSettingsResponseDto,
|
||||
YandexOAuth2Settings,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Auth models
|
||||
|
|
@ -283,6 +356,13 @@ __all__ = [
|
|||
"TelegramCallbackRequestDto",
|
||||
"TelegramCallbackResponseDto",
|
||||
"LoginTelegramRequestDto", # Legacy alias
|
||||
"OAuth2AuthorizeRequestDto",
|
||||
"OAuth2AuthorizeResponseDto",
|
||||
"OAuth2CallbackRequestDto",
|
||||
"OAuth2CallbackResponseDto",
|
||||
"VerifyPasskeyAuthenticationRequestDto",
|
||||
"VerifyPasskeyAuthenticationResponseDto",
|
||||
"GetPasskeyAuthenticationOptionsResponseDto",
|
||||
# Nodes models
|
||||
"CreateNodeRequestDto",
|
||||
"CreateNodeResponseDto",
|
||||
|
|
@ -302,7 +382,8 @@ __all__ = [
|
|||
"UpdateNodeResponseDto",
|
||||
"NodeConfigProfileDto",
|
||||
"NodeConfigProfileRequestDto",
|
||||
"RestartAllNodesRequestDto",
|
||||
"RestartAllNodesRequestDto", # Legacy alias
|
||||
"RestartAllNodesRequestBodyDto",
|
||||
# Hosts models
|
||||
"CreateHostRequestDto",
|
||||
"CreateHostResponseDto",
|
||||
|
|
@ -347,11 +428,21 @@ __all__ = [
|
|||
"SubscriptionSettingsResponseDto",
|
||||
"UpdateSubscriptionSettingsRequestDto",
|
||||
"UpdateSubscriptionSettingsResponseDto",
|
||||
"ResponseModificationHeader",
|
||||
"ResponseModifications",
|
||||
"ResponseRule",
|
||||
"ResponseRuleCondition",
|
||||
"ResponseRules",
|
||||
# Subscription template models
|
||||
"GetTemplateResponseDto",
|
||||
"TemplateResponseDto",
|
||||
"UpdateTemplateRequestDto",
|
||||
"UpdateTemplateResponseDto",
|
||||
"CreateSubscriptionTemplateRequestDto",
|
||||
"CreateSubscriptionTemplateResponseDto",
|
||||
"DeleteSubscriptionTemplateResponseDto",
|
||||
"GetTemplatesResponseDto",
|
||||
"TemplateInfoDto",
|
||||
# System models
|
||||
"BandwidthStatistic",
|
||||
"BandwidthStatisticResponseDto",
|
||||
|
|
@ -370,6 +461,10 @@ __all__ = [
|
|||
"GetNodesMetricsResponseDto",
|
||||
"GetX25519KeyPairResponseDto",
|
||||
"X25519KeyPair",
|
||||
"DebugSrrMatcherRequestDto",
|
||||
"DebugSrrMatcherResponseDto",
|
||||
"EncryptHappCryptoLinkRequestDto",
|
||||
"EncryptHappCryptoLinkResponseDto",
|
||||
# XRay config models
|
||||
"ConfigResponseDto", # Legacy alias
|
||||
"GetConfigResponseDto",
|
||||
|
|
@ -463,6 +558,7 @@ __all__ = [
|
|||
"GetConfigProfileByUuidResponseDto",
|
||||
"GetInboundsByProfileUuidResponseDto",
|
||||
"InboundDto",
|
||||
"NodesProfileDto",
|
||||
"UpdateConfigProfileRequestDto",
|
||||
"UpdateConfigProfileResponseDto",
|
||||
"GetAllConfigProfilesResponsePaginated",
|
||||
|
|
@ -553,4 +649,56 @@ __all__ = [
|
|||
|
||||
# WEBHOOK PAYLOAD
|
||||
"WebhookPayloadDto",
|
||||
|
||||
# Passkeys models
|
||||
"DeletePasskeyRequestDto",
|
||||
"DeletePasskeyResponseDto",
|
||||
"GetAllPasskeysResponseDto",
|
||||
"GetPasskeyRegistrationOptionsResponseDto",
|
||||
"PasskeyDto",
|
||||
"VerifyPasskeyRegistrationRequestDto",
|
||||
"VerifyPasskeyRegistrationResponseDto",
|
||||
|
||||
# External squads models
|
||||
"AddUsersToExternalSquadResponseDto",
|
||||
"CreateExternalSquadRequestDto",
|
||||
"CreateExternalSquadResponseDto",
|
||||
"DeleteExternalSquadResponseDto",
|
||||
"ExternalSquadDto",
|
||||
"ExternalSquadInfoDto",
|
||||
"ExternalSquadSubscriptionSettingsDto",
|
||||
"ExternalSquadTemplateDto",
|
||||
"GetExternalSquadByUuidResponseDto",
|
||||
"GetExternalSquadsResponseDto",
|
||||
"RemoveUsersFromExternalSquadResponseDto",
|
||||
"TemplateType",
|
||||
"UpdateExternalSquadRequestDto",
|
||||
"UpdateExternalSquadResponseDto",
|
||||
|
||||
# Snippets models
|
||||
|
||||
"CreateSnippetRequestDto",
|
||||
"CreateSnippetResponseDto",
|
||||
"DeleteSnippetRequestDto",
|
||||
"DeleteSnippetResponseDto",
|
||||
"GetSnippetsResponseDto",
|
||||
"SnippetItem",
|
||||
"SnippetsData",
|
||||
"UpdateSnippetRequestDto",
|
||||
"UpdateSnippetResponseDto",
|
||||
|
||||
# Remnawave settings models
|
||||
|
||||
"BrandingSettings",
|
||||
"GetRemnawaveSettingsResponseDto",
|
||||
"GitHubOAuth2Settings",
|
||||
"OAuth2Settings",
|
||||
"PasskeySettings",
|
||||
"PasswordSettings",
|
||||
"PocketIdOAuth2Settings",
|
||||
"RemnawaveSettingsData",
|
||||
"TelegramAuthSettings",
|
||||
"UpdateRemnawaveSettingsRequestDto",
|
||||
"UpdateRemnawaveSettingsResponseDto",
|
||||
"YandexOAuth2Settings",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
from typing import Annotated, Optional
|
||||
from typing import Annotated, Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, StringConstraints
|
||||
|
||||
from remnawave.enums.auth import OAuth2Provider
|
||||
|
||||
|
||||
class AuthTokenResponseData(BaseModel):
|
||||
access_token: str = Field(alias="accessToken")
|
||||
|
|
@ -53,6 +55,47 @@ class TelegramCallbackResponseDto(AuthTokenResponseData):
|
|||
pass
|
||||
|
||||
|
||||
# OAuth2 Authorization models
|
||||
class OAuth2AuthorizeRequestDto(BaseModel):
|
||||
"""Request to initiate OAuth2 authorization"""
|
||||
provider: OAuth2Provider
|
||||
|
||||
|
||||
class OAuth2AuthorizeResponseDto(BaseModel):
|
||||
"""Response with OAuth2 authorization URL"""
|
||||
authorization_url: Optional[str] = Field(alias="authorizationUrl")
|
||||
|
||||
|
||||
# OAuth2 Callback models
|
||||
class OAuth2CallbackRequestDto(BaseModel):
|
||||
"""Request for OAuth2 callback"""
|
||||
provider: OAuth2Provider
|
||||
code: str
|
||||
state: str
|
||||
|
||||
|
||||
class OAuth2CallbackResponseDto(BaseModel):
|
||||
"""Response with access token from OAuth2 callback"""
|
||||
access_token: str = Field(alias="accessToken")
|
||||
|
||||
|
||||
# Passkey Authentication models
|
||||
class GetPasskeyAuthenticationOptionsResponseDto(BaseModel):
|
||||
"""Response with passkey authentication options"""
|
||||
# Passkey options are complex WebAuthn objects, using Any for flexibility
|
||||
response: Dict[str, Any]
|
||||
|
||||
|
||||
class VerifyPasskeyAuthenticationRequestDto(BaseModel):
|
||||
"""Request to verify passkey authentication"""
|
||||
# Passkey authentication response is complex WebAuthn object
|
||||
response: Dict[str, Any]
|
||||
|
||||
|
||||
class VerifyPasskeyAuthenticationResponseDto(BaseModel):
|
||||
"""Response with access token after successful passkey authentication"""
|
||||
access_token: str = Field(alias="accessToken")
|
||||
|
||||
# Legacy alias for backward compatibility
|
||||
StatusResponseDto = GetStatusResponseDto
|
||||
LoginTelegramRequestDto = TelegramCallbackRequestDto
|
||||
LoginTelegramRequestDto = TelegramCallbackRequestDto
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from pydantic import BaseModel, Field, RootModel
|
|||
class NodeUsageResponseDto(BaseModel):
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
node_name: str = Field(alias="nodeName")
|
||||
total: float
|
||||
total: int
|
||||
total_download: float = Field(alias="totalDownload")
|
||||
total_upload: float = Field(alias="totalUpload")
|
||||
human_readable_total: str = Field(alias="humanReadableTotal")
|
||||
|
|
@ -65,7 +65,7 @@ class UserUsageByRangeItem(BaseModel):
|
|||
user_uuid: UUID = Field(alias="userUuid")
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
node_name: str = Field(alias="nodeName")
|
||||
total: float
|
||||
total: int
|
||||
date: str
|
||||
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ class NodeUserUsageItem(BaseModel):
|
|||
user_uuid: UUID = Field(alias="userUuid")
|
||||
username: str
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
total: float
|
||||
total: int
|
||||
date: str
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,13 +15,17 @@ class InboundDto(BaseModel):
|
|||
port: Optional[int] = None
|
||||
raw_inbound: Optional[Any] = Field(None, alias="rawInbound")
|
||||
|
||||
class NodesProfileDto(BaseModel):
|
||||
uuid: UUID
|
||||
name: str
|
||||
country_code: str = Field(alias="countryCode")
|
||||
|
||||
class ConfigProfileDto(BaseModel):
|
||||
uuid: UUID
|
||||
name: str
|
||||
config: Dict[str, Any]
|
||||
inbounds: List[InboundDto]
|
||||
nodes: List[Any] = [] # Can be empty list
|
||||
nodes: List[NodesProfileDto] = []
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
|
|
|||
102
remnawave/models/external_squads.py
Normal file
102
remnawave/models/external_squads.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
from datetime import datetime
|
||||
from enum import StrEnum
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TemplateType(StrEnum):
|
||||
"""Template type enum"""
|
||||
XRAY_JSON = "XRAY_JSON"
|
||||
XRAY_BASE64 = "XRAY_BASE64"
|
||||
MIHOMO = "MIHOMO"
|
||||
STASH = "STASH"
|
||||
CLASH = "CLASH"
|
||||
SINGBOX = "SINGBOX"
|
||||
|
||||
|
||||
class ExternalSquadInfoDto(BaseModel):
|
||||
"""External squad info"""
|
||||
members_count: float = Field(alias="membersCount")
|
||||
|
||||
|
||||
class ExternalSquadTemplateDto(BaseModel):
|
||||
"""External squad template"""
|
||||
template_uuid: UUID = Field(alias="templateUuid")
|
||||
template_type: TemplateType = Field(alias="templateType")
|
||||
|
||||
|
||||
class ExternalSquadSubscriptionSettingsDto(BaseModel):
|
||||
"""External squad subscription settings"""
|
||||
profile_title: str = Field(alias="profileTitle")
|
||||
support_link: str = Field(alias="supportLink")
|
||||
profile_update_interval: int = Field(alias="profileUpdateInterval", ge=1)
|
||||
is_profile_webpage_url_enabled: bool = Field(alias="isProfileWebpageUrlEnabled")
|
||||
serve_json_at_base_subscription: bool = Field(alias="serveJsonAtBaseSubscription")
|
||||
add_username_to_base_subscription: bool = Field(alias="addUsernameToBaseSubscription")
|
||||
is_show_custom_remarks: bool = Field(alias="isShowCustomRemarks")
|
||||
happ_announce: Optional[str] = Field(None, alias="happAnnounce")
|
||||
happ_routing: Optional[str] = Field(None, alias="happRouting")
|
||||
randomize_hosts: bool = Field(alias="randomizeHosts")
|
||||
|
||||
|
||||
class ExternalSquadDto(BaseModel):
|
||||
"""External squad data model"""
|
||||
uuid: UUID
|
||||
name: str
|
||||
info: ExternalSquadInfoDto
|
||||
templates: List[ExternalSquadTemplateDto]
|
||||
subscription_settings: Optional[ExternalSquadSubscriptionSettingsDto] = Field(alias="subscriptionSettings")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
||||
# Request/Response models
|
||||
class GetExternalSquadsResponseDto(BaseModel):
|
||||
"""Response with all external squads"""
|
||||
total: float
|
||||
external_squads: List[ExternalSquadDto] = Field(alias="externalSquads")
|
||||
|
||||
|
||||
class GetExternalSquadByUuidResponseDto(ExternalSquadDto):
|
||||
"""Response with external squad by UUID"""
|
||||
pass
|
||||
|
||||
|
||||
class CreateExternalSquadRequestDto(BaseModel):
|
||||
"""Request to create external squad"""
|
||||
name: str = Field(min_length=2, max_length=30, pattern=r"^[A-Za-z0-9_\s-]+$")
|
||||
|
||||
|
||||
class CreateExternalSquadResponseDto(ExternalSquadDto):
|
||||
"""Response after creating external squad"""
|
||||
pass
|
||||
|
||||
|
||||
class UpdateExternalSquadRequestDto(BaseModel):
|
||||
"""Request to update external squad"""
|
||||
uuid: UUID
|
||||
name: Optional[str] = Field(None, min_length=2, max_length=30, pattern=r"^[A-Za-z0-9_\s-]+$")
|
||||
templates: Optional[List[ExternalSquadTemplateDto]] = None
|
||||
subscription_settings: Optional[ExternalSquadSubscriptionSettingsDto] = Field(None, serialization_alias="subscriptionSettings")
|
||||
|
||||
|
||||
class UpdateExternalSquadResponseDto(ExternalSquadDto):
|
||||
"""Response after updating external squad"""
|
||||
pass
|
||||
|
||||
|
||||
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")
|
||||
|
||||
|
||||
class RemoveUsersFromExternalSquadResponseDto(BaseModel):
|
||||
"""Response after removing users from external squad"""
|
||||
event_sent: bool = Field(alias="eventSent")
|
||||
|
|
@ -23,9 +23,19 @@ class ReorderHostRequestDto(BaseModel):
|
|||
hosts: List[ReorderHostItem]
|
||||
|
||||
|
||||
class HostInboundData(BaseModel):
|
||||
config_profile_uuid: Optional[UUID] = Field(alias="configProfileUuid")
|
||||
config_profile_inbound_uuid: Optional[UUID] = Field(alias="configProfileInboundUuid")
|
||||
|
||||
|
||||
class CreateHostInboundData(BaseModel):
|
||||
config_profile_uuid: UUID = Field(serialization_alias="configProfileUuid")
|
||||
config_profile_inbound_uuid: UUID = Field(serialization_alias="configProfileInboundUuid")
|
||||
|
||||
|
||||
class UpdateHostRequestDto(BaseModel):
|
||||
uuid: UUID
|
||||
inbound_uuid: Optional[UUID] = Field(None, serialization_alias="inboundUuid")
|
||||
inbound: Optional[CreateHostInboundData] = None
|
||||
remark: Annotated[Optional[str], StringConstraints(max_length=40)] = None
|
||||
address: Optional[str] = None
|
||||
port: Optional[int] = None
|
||||
|
|
@ -42,17 +52,7 @@ class UpdateHostRequestDto(BaseModel):
|
|||
server_description: Optional[str] = Field(
|
||||
None, serialization_alias="serverDescription", max_length=30
|
||||
)
|
||||
mux_params: Optional[str] = Field(
|
||||
None,
|
||||
serialization_alias="muxParams",
|
||||
)
|
||||
sockopt_params: Optional[str] = Field(
|
||||
None,
|
||||
serialization_alias="sockoptParams",
|
||||
)
|
||||
tag: Optional[Annotated[str, StringConstraints(max_length=32)]] = Field(
|
||||
None, serialization_alias="tag"
|
||||
)
|
||||
tag: Optional[Annotated[str, StringConstraints(max_length=32, pattern=r"^[A-Z0-9_:]+$")]] = None
|
||||
is_hidden: Optional[bool] = Field(
|
||||
None,
|
||||
serialization_alias="isHidden",
|
||||
|
|
@ -79,12 +79,20 @@ class UpdateHostRequestDto(BaseModel):
|
|||
None,
|
||||
serialization_alias="xHttpExtraParams",
|
||||
)
|
||||
nodes: Optional[List[str]] = None
|
||||
mux_params: Optional[str] = Field(
|
||||
None,
|
||||
serialization_alias="muxParams",
|
||||
)
|
||||
sockopt_params: Optional[str] = Field(
|
||||
None,
|
||||
serialization_alias="sockoptParams",
|
||||
)
|
||||
nodes: Optional[List[UUID]] = None
|
||||
|
||||
|
||||
class HostInboundData(BaseModel):
|
||||
config_profile_uuid: Optional[UUID] = Field(alias="configProfileUuid")
|
||||
config_profile_inbound_uuid: Optional[UUID] = Field(alias="configProfileInboundUuid")
|
||||
# Legacy compatibility properties
|
||||
@property
|
||||
def inbound_uuid(self) -> Optional[UUID]:
|
||||
return self.inbound.config_profile_inbound_uuid if self.inbound else None
|
||||
|
||||
|
||||
class HostResponseDto(BaseModel):
|
||||
|
|
@ -121,7 +129,7 @@ class HostResponseDto(BaseModel):
|
|||
)
|
||||
shuffle_host: bool = Field(alias="shuffleHost")
|
||||
mihomo_x25519: bool = Field(alias="mihomoX25519")
|
||||
nodes: List[str]
|
||||
nodes: List[UUID]
|
||||
is_disabled: bool = Field(
|
||||
default=False,
|
||||
alias="isDisabled",
|
||||
|
|
@ -149,54 +157,9 @@ class HostResponseDto(BaseModel):
|
|||
return self.inbound.config_profile_inbound_uuid
|
||||
|
||||
|
||||
class HostsResponseDto(List[HostResponseDto]):
|
||||
pass
|
||||
|
||||
|
||||
class CreateHostResponseDto(HostResponseDto):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateHostResponseDto(HostResponseDto):
|
||||
pass
|
||||
|
||||
|
||||
class GetAllHostTagsResponseDto(BaseModel):
|
||||
tags: list[str] = None
|
||||
|
||||
|
||||
class GetAllHostsResponseDto(RootModel[List[HostResponseDto]]):
|
||||
root: List[HostResponseDto]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.root)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
|
||||
class GetOneHostResponseDto(HostResponseDto):
|
||||
pass
|
||||
|
||||
|
||||
class ReorderHostResponseDto(BaseModel):
|
||||
is_updated: bool = Field(alias="isUpdated", default=True)
|
||||
|
||||
|
||||
class DeleteHostResponseDto(BaseModel):
|
||||
is_deleted: bool = Field(alias="isDeleted")
|
||||
|
||||
|
||||
class CreateHostInboundData(BaseModel):
|
||||
config_profile_uuid: Optional[UUID] = Field(serialization_alias="configProfileUuid")
|
||||
config_profile_inbound_uuid: Optional[UUID] = Field(
|
||||
serialization_alias="configProfileInboundUuid"
|
||||
)
|
||||
|
||||
|
||||
class CreateHostRequestDto(BaseModel):
|
||||
inbound: CreateHostInboundData
|
||||
remark: Annotated[str, StringConstraints(max_length=40)]
|
||||
remark: Annotated[str, StringConstraints(min_length=1, max_length=40)]
|
||||
address: str
|
||||
port: int
|
||||
path: Optional[str] = None
|
||||
|
|
@ -219,9 +182,7 @@ class CreateHostRequestDto(BaseModel):
|
|||
server_description: Optional[str] = Field(
|
||||
None, serialization_alias="serverDescription", max_length=30
|
||||
)
|
||||
tag: Optional[Annotated[str, StringConstraints(max_length=32)]] = Field(
|
||||
None, serialization_alias="tag"
|
||||
)
|
||||
tag: Optional[Annotated[str, StringConstraints(max_length=32, pattern=r"^[A-Z0-9_:]+$")]] = None
|
||||
vless_route_id: Optional[int] = Field(
|
||||
None,
|
||||
serialization_alias="vlessRouteId",
|
||||
|
|
@ -236,7 +197,7 @@ class CreateHostRequestDto(BaseModel):
|
|||
False,
|
||||
serialization_alias="mihomoX25519",
|
||||
)
|
||||
nodes: List[str] = Field(default_factory=list)
|
||||
nodes: List[UUID] = Field(default_factory=list)
|
||||
allow_insecure: bool = Field(
|
||||
False,
|
||||
serialization_alias="allowInsecure",
|
||||
|
|
@ -278,4 +239,42 @@ class CreateHostRequestDto(BaseModel):
|
|||
or UUID("107541f1-ae1a-4e2d-9dec-7297557b5125"),
|
||||
config_profile_inbound_uuid=inbound_uuid,
|
||||
)
|
||||
super().__init__(**data)
|
||||
super().__init__(**data)
|
||||
|
||||
|
||||
class GetAllHostTagsResponseDto(BaseModel):
|
||||
tags: List[str]
|
||||
|
||||
|
||||
class CreateHostResponseDto(HostResponseDto):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateHostResponseDto(HostResponseDto):
|
||||
pass
|
||||
|
||||
|
||||
class GetAllHostsResponseDto(RootModel[List[HostResponseDto]]):
|
||||
root: List[HostResponseDto]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.root)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
|
||||
|
||||
class GetOneHostResponseDto(HostResponseDto):
|
||||
pass
|
||||
|
||||
|
||||
class ReorderHostResponseDto(BaseModel):
|
||||
is_updated: bool = Field(alias="isUpdated", default=True)
|
||||
|
||||
|
||||
class DeleteHostResponseDto(BaseModel):
|
||||
is_deleted: bool = Field(alias="isDeleted")
|
||||
|
||||
|
||||
# Legacy compatibility
|
||||
HostsResponseDto = List[HostResponseDto]
|
||||
|
|
@ -17,7 +17,7 @@ class InboundResponseDto(BaseModel):
|
|||
|
||||
|
||||
class AllInboundsData(BaseModel):
|
||||
total: float
|
||||
total: int
|
||||
inbounds: List[InboundResponseDto]
|
||||
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ class GetAllInboundsResponseDto(AllInboundsData):
|
|||
|
||||
|
||||
class InboundsByProfileData(BaseModel):
|
||||
total: float
|
||||
total: int
|
||||
inbounds: List[InboundResponseDto]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ class UpdateInfraProviderResponseDto(InfraProviderDto):
|
|||
|
||||
|
||||
class AllInfraProvidersData(BaseModel):
|
||||
total: float = Field(alias="total")
|
||||
total: int = Field(alias="total")
|
||||
providers: List[InfraProviderDto]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,18 @@ class ReorderNodeItem(BaseModel):
|
|||
uuid: UUID
|
||||
|
||||
|
||||
class NodeProviderDto(BaseModel):
|
||||
"""Node provider information"""
|
||||
uuid: UUID
|
||||
name: str
|
||||
favicon_link: Optional[str] = Field(None, alias="faviconLink")
|
||||
login_url: Optional[str] = Field(None, alias="loginUrl")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
||||
class NodeConfigProfileDto(BaseModel):
|
||||
active_config_profile_uuid: UUID = Field(alias="activeConfigProfileUuid")
|
||||
active_config_profile_uuid: Optional[UUID] = Field(alias="activeConfigProfileUuid")
|
||||
active_inbounds: List[InboundsDto] = Field(alias="activeInbounds")
|
||||
|
||||
|
||||
|
|
@ -39,30 +49,31 @@ class NodeConfigProfileRequestDto(BaseModel):
|
|||
|
||||
|
||||
class CreateNodeRequestDto(BaseModel):
|
||||
name: Annotated[str, StringConstraints(min_length=3)]
|
||||
name: Annotated[str, StringConstraints(min_length=3, max_length=30)]
|
||||
address: Annotated[str, StringConstraints(min_length=2)]
|
||||
port: Optional[int] = Field(None, strict=True, ge=1)
|
||||
port: Optional[int] = Field(None, ge=1, le=65535)
|
||||
is_traffic_tracking_active: Optional[bool] = Field(
|
||||
None,
|
||||
False,
|
||||
serialization_alias="isTrafficTrackingActive",
|
||||
)
|
||||
traffic_limit_bytes: Optional[int] = Field(
|
||||
None, serialization_alias="trafficLimitBytes", strict=True, ge=0
|
||||
None, serialization_alias="trafficLimitBytes", ge=0
|
||||
)
|
||||
notify_percent: Optional[int] = Field(
|
||||
None, serialization_alias="notifyPercent", strict=True, ge=0
|
||||
None, serialization_alias="notifyPercent", ge=0, le=100
|
||||
)
|
||||
traffic_reset_day: Optional[int] = Field(
|
||||
None, serialization_alias="trafficResetDay", strict=True, ge=1
|
||||
None, serialization_alias="trafficResetDay", ge=1, le=31
|
||||
)
|
||||
excluded_inbounds: Optional[List[UUID]] = Field(
|
||||
None, serialization_alias="excludedInbounds"
|
||||
)
|
||||
country_code: Annotated[Optional[str], StringConstraints(max_length=2)] = Field(
|
||||
None, serialization_alias="countryCode"
|
||||
"XX",
|
||||
serialization_alias="countryCode"
|
||||
)
|
||||
consumption_multiplier: Optional[float] = Field(
|
||||
None, serialization_alias="consumptionMultiplier"
|
||||
None, serialization_alias="consumptionMultiplier", ge=0.1
|
||||
)
|
||||
config_profile: NodeConfigProfileRequestDto = Field(
|
||||
serialization_alias="configProfile"
|
||||
|
|
@ -72,18 +83,20 @@ class CreateNodeRequestDto(BaseModel):
|
|||
|
||||
class UpdateNodeRequestDto(BaseModel):
|
||||
uuid: UUID
|
||||
name: Annotated[Optional[str], StringConstraints(min_length=3)] = None
|
||||
name: Annotated[Optional[str], StringConstraints(min_length=3, max_length=30)] = None
|
||||
address: Annotated[Optional[str], StringConstraints(min_length=2)] = None
|
||||
port: Optional[int] = None
|
||||
port: Optional[float] = Field(None, ge=1, le=65535) # ИСПРАВЛЕН тип на float
|
||||
is_traffic_tracking_active: Optional[bool] = Field(
|
||||
None, serialization_alias="isTrafficTrackingActive"
|
||||
)
|
||||
traffic_limit_bytes: Optional[float] = Field(
|
||||
None, serialization_alias="trafficLimitBytes"
|
||||
None, serialization_alias="trafficLimitBytes", ge=0
|
||||
)
|
||||
notify_percent: Optional[float] = Field(
|
||||
None, serialization_alias="notifyPercent", ge=0, le=100
|
||||
)
|
||||
notify_percent: Optional[float] = Field(None, serialization_alias="notifyPercent")
|
||||
traffic_reset_day: Optional[float] = Field(
|
||||
None, serialization_alias="trafficResetDay"
|
||||
None, serialization_alias="trafficResetDay", ge=1, le=31
|
||||
)
|
||||
excluded_inbounds: Optional[List[UUID]] = Field(
|
||||
None, serialization_alias="excludedInbounds"
|
||||
|
|
@ -92,8 +105,12 @@ class UpdateNodeRequestDto(BaseModel):
|
|||
None, serialization_alias="countryCode"
|
||||
)
|
||||
consumption_multiplier: Optional[float] = Field(
|
||||
None, serialization_alias="consumptionMultiplier"
|
||||
None, serialization_alias="consumptionMultiplier", ge=0.1
|
||||
)
|
||||
config_profile: Optional[NodeConfigProfileRequestDto] = Field(
|
||||
None, serialization_alias="configProfile"
|
||||
)
|
||||
provider_uuid: Optional[UUID] = Field(None, serialization_alias="providerUuid")
|
||||
|
||||
|
||||
class ReorderNodeRequestDto(BaseModel):
|
||||
|
|
@ -113,6 +130,7 @@ class NodeResponseDto(BaseModel):
|
|||
last_status_change: Optional[datetime] = Field(None, alias="lastStatusChange")
|
||||
last_status_message: Optional[str] = Field(None, alias="lastStatusMessage")
|
||||
xray_version: Optional[str] = Field(None, alias="xrayVersion")
|
||||
node_version: Optional[str] = Field(None, alias="nodeVersion")
|
||||
xray_uptime: str = Field(alias="xrayUptime")
|
||||
is_traffic_tracking_active: bool = Field(alias="isTrafficTrackingActive")
|
||||
traffic_reset_day: Optional[int] = Field(None, alias="trafficResetDay")
|
||||
|
|
@ -129,16 +147,8 @@ class NodeResponseDto(BaseModel):
|
|||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
config_profile: NodeConfigProfileDto = Field(alias="configProfile")
|
||||
|
||||
|
||||
class NodesResponseDto(RootModel[List[NodeResponseDto]]):
|
||||
root: List[NodeResponseDto]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.root)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.root[item]
|
||||
provider_uuid: Optional[UUID] = Field(None, alias="providerUuid")
|
||||
provider: Optional[NodeProviderDto] = None
|
||||
|
||||
|
||||
class CreateNodeResponseDto(NodeResponseDto):
|
||||
|
|
@ -172,14 +182,16 @@ class DisableNodeResponseDto(NodeResponseDto):
|
|||
|
||||
|
||||
class RestartNodeResponseDto(BaseModel):
|
||||
message: str
|
||||
event_sent: bool = Field(alias="eventSent")
|
||||
|
||||
|
||||
class RestartAllNodesResponseDto(BaseModel):
|
||||
message: str
|
||||
event_sent: bool = Field(alias="eventSent")
|
||||
|
||||
|
||||
class ReorderNodeResponseDto(RootModel[List[NodeResponseDto]]):
|
||||
root: List[NodeResponseDto]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.root)
|
||||
|
||||
|
|
@ -194,5 +206,10 @@ class DeleteNodeResponseDto(BaseModel):
|
|||
return self.is_deleted
|
||||
|
||||
|
||||
class RestartAllNodesRequestDto(BaseModel):
|
||||
class RestartAllNodesRequestBodyDto(BaseModel):
|
||||
force_restart: bool = Field(default=False, alias="forceRestart")
|
||||
|
||||
|
||||
# Для обратной совместимости
|
||||
RestartAllNodesRequestDto = RestartAllNodesRequestBodyDto
|
||||
NodesResponseDto = NodeResponseDto
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
from uuid import UUID
|
||||
|
||||
|
|
@ -18,11 +19,13 @@ class GetUserAccessibleNodesResponseDto(GetUserAccessibleNodesResponse):
|
|||
|
||||
|
||||
class NodeUsageDto(BaseModel):
|
||||
date: str
|
||||
upload: int
|
||||
download: int
|
||||
|
||||
"""Individual node usage item"""
|
||||
node_uuid: UUID = Field(alias="nodeUuid")
|
||||
date: datetime
|
||||
upload: int = Field(0, alias="totalBytes")
|
||||
download: int = Field(0, alias="totalBytes")
|
||||
|
||||
|
||||
class GetNodesUsageByRangeResponseDto(RootModel[List[NodeUsageDto]]):
|
||||
def __iter__(self):
|
||||
return iter(self.root)
|
||||
|
|
|
|||
46
remnawave/models/passkeys.py
Normal file
46
remnawave/models/passkeys.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PasskeyDto(BaseModel):
|
||||
"""Passkey data model"""
|
||||
id: str
|
||||
name: str
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
last_used_at: datetime = Field(alias="lastUsedAt")
|
||||
|
||||
|
||||
# Registration models
|
||||
class GetPasskeyRegistrationOptionsResponseDto(BaseModel):
|
||||
"""Response with passkey registration options"""
|
||||
# WebAuthn registration options are complex objects, using Any for flexibility
|
||||
response: Dict[str, Any]
|
||||
|
||||
|
||||
class VerifyPasskeyRegistrationRequestDto(BaseModel):
|
||||
"""Request to verify passkey registration"""
|
||||
# WebAuthn registration response is complex object
|
||||
response: Dict[str, Any]
|
||||
|
||||
|
||||
class VerifyPasskeyRegistrationResponseDto(BaseModel):
|
||||
"""Response with passkey registration verification result"""
|
||||
verified: bool
|
||||
|
||||
|
||||
# Passkeys management models
|
||||
class GetAllPasskeysResponseDto(BaseModel):
|
||||
"""Response with all user's passkeys"""
|
||||
passkeys: List[PasskeyDto]
|
||||
|
||||
|
||||
class DeletePasskeyRequestDto(BaseModel):
|
||||
"""Request to delete a passkey"""
|
||||
id: str
|
||||
|
||||
|
||||
class DeletePasskeyResponseDto(BaseModel):
|
||||
"""Response with updated passkeys list after deletion"""
|
||||
passkeys: List[PasskeyDto]
|
||||
88
remnawave/models/remnawave_settings.py
Normal file
88
remnawave/models/remnawave_settings.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, HttpUrl
|
||||
|
||||
|
||||
class PasskeySettings(BaseModel):
|
||||
"""Passkey authentication settings"""
|
||||
enabled: bool
|
||||
rp_id: Optional[str] = Field(None, alias="rpId")
|
||||
origin: Optional[str] = None
|
||||
|
||||
|
||||
class GitHubOAuth2Settings(BaseModel):
|
||||
"""GitHub OAuth2 settings"""
|
||||
enabled: bool
|
||||
client_id: Optional[str] = Field(None, alias="clientId")
|
||||
client_secret: Optional[str] = Field(None, alias="clientSecret")
|
||||
allowed_emails: List[str] = Field(alias="allowedEmails")
|
||||
|
||||
|
||||
class PocketIdOAuth2Settings(BaseModel):
|
||||
"""PocketID OAuth2 settings"""
|
||||
enabled: bool
|
||||
client_id: Optional[str] = Field(None, alias="clientId")
|
||||
client_secret: Optional[str] = Field(None, alias="clientSecret")
|
||||
plain_domain: Optional[str] = Field(None, alias="plainDomain")
|
||||
allowed_emails: List[str] = Field(alias="allowedEmails")
|
||||
|
||||
|
||||
class YandexOAuth2Settings(BaseModel):
|
||||
"""Yandex OAuth2 settings"""
|
||||
enabled: bool
|
||||
client_id: Optional[str] = Field(None, alias="clientId")
|
||||
client_secret: Optional[str] = Field(None, alias="clientSecret")
|
||||
allowed_emails: List[str] = Field(alias="allowedEmails")
|
||||
|
||||
|
||||
class OAuth2Settings(BaseModel):
|
||||
"""OAuth2 authentication settings"""
|
||||
github: GitHubOAuth2Settings
|
||||
pocketid: PocketIdOAuth2Settings
|
||||
yandex: YandexOAuth2Settings
|
||||
|
||||
|
||||
class TelegramAuthSettings(BaseModel):
|
||||
"""Telegram authentication settings"""
|
||||
enabled: bool
|
||||
bot_token: Optional[str] = Field(None, alias="botToken")
|
||||
admin_ids: List[str] = Field(alias="adminIds")
|
||||
|
||||
|
||||
class PasswordSettings(BaseModel):
|
||||
"""Password authentication settings"""
|
||||
enabled: bool
|
||||
|
||||
|
||||
class BrandingSettings(BaseModel):
|
||||
"""Branding settings"""
|
||||
title: Optional[str] = None
|
||||
logo_url: Optional[HttpUrl] = Field(None, alias="logoUrl")
|
||||
|
||||
|
||||
class RemnawaveSettingsData(BaseModel):
|
||||
"""Remnawave settings data"""
|
||||
passkey_settings: Optional[PasskeySettings] = Field(None, alias="passkeySettings")
|
||||
oauth2_settings: Optional[OAuth2Settings] = Field(None, alias="oauth2Settings")
|
||||
tg_auth_settings: Optional[TelegramAuthSettings] = Field(None, alias="tgAuthSettings")
|
||||
password_settings: Optional[PasswordSettings] = Field(None, alias="passwordSettings")
|
||||
branding_settings: Optional[BrandingSettings] = Field(None, alias="brandingSettings")
|
||||
|
||||
|
||||
class GetRemnawaveSettingsResponseDto(BaseModel):
|
||||
"""Get Remnawave settings response"""
|
||||
response: RemnawaveSettingsData
|
||||
|
||||
|
||||
class UpdateRemnawaveSettingsRequestDto(BaseModel):
|
||||
"""Update Remnawave settings request"""
|
||||
passkey_settings: Optional[PasskeySettings] = Field(None, serialization_alias="passkeySettings")
|
||||
oauth2_settings: Optional[OAuth2Settings] = Field(None, serialization_alias="oauth2Settings")
|
||||
tg_auth_settings: Optional[TelegramAuthSettings] = Field(None, serialization_alias="tgAuthSettings")
|
||||
password_settings: Optional[PasswordSettings] = Field(None, serialization_alias="passwordSettings")
|
||||
branding_settings: Optional[BrandingSettings] = Field(None, serialization_alias="brandingSettings")
|
||||
|
||||
|
||||
class UpdateRemnawaveSettingsResponseDto(BaseModel):
|
||||
"""Update Remnawave settings response"""
|
||||
response: RemnawaveSettingsData
|
||||
56
remnawave/models/snippets.py
Normal file
56
remnawave/models/snippets.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
from typing import Annotated, Any, List
|
||||
|
||||
from pydantic import BaseModel, Field, StringConstraints, RootModel
|
||||
|
||||
|
||||
class SnippetItem(BaseModel):
|
||||
"""Individual snippet item"""
|
||||
name: str
|
||||
snippet: Any # Can be any JSON object or array
|
||||
|
||||
|
||||
class SnippetsData(BaseModel):
|
||||
"""Snippets response data"""
|
||||
total: int
|
||||
snippets: List[SnippetItem]
|
||||
|
||||
|
||||
# Изменяем структуру - API возвращает данные напрямую
|
||||
class GetSnippetsResponseDto(SnippetsData):
|
||||
"""Get all snippets response - extends SnippetsData directly"""
|
||||
pass
|
||||
|
||||
|
||||
class CreateSnippetResponseDto(SnippetsData):
|
||||
"""Create snippet response - extends SnippetsData directly"""
|
||||
pass
|
||||
|
||||
|
||||
class UpdateSnippetResponseDto(SnippetsData):
|
||||
"""Update snippet response - extends SnippetsData directly"""
|
||||
pass
|
||||
|
||||
|
||||
class DeleteSnippetResponseDto(SnippetsData):
|
||||
"""Delete snippet response - extends SnippetsData directly"""
|
||||
pass
|
||||
|
||||
|
||||
class CreateSnippetRequestDto(BaseModel):
|
||||
"""Create snippet request"""
|
||||
name: Annotated[str, StringConstraints(min_length=2, max_length=255, pattern=r"^[A-Za-z0-9_\s-]+$")]
|
||||
snippet: List[dict] # Array of objects
|
||||
|
||||
|
||||
class UpdateSnippetRequestDto(BaseModel):
|
||||
"""Update snippet request"""
|
||||
name: Annotated[str, StringConstraints(min_length=2, max_length=255, pattern=r"^[A-Za-z0-9_\s-]+$")]
|
||||
snippet: List[dict] # Array of objects
|
||||
|
||||
|
||||
class DeleteSnippetRequestDto(BaseModel):
|
||||
"""Delete snippet request"""
|
||||
name: Annotated[str, StringConstraints(min_length=2, max_length=255, pattern=r"^[A-Za-z0-9_\s-]+$")]
|
||||
|
||||
class DeleteSnippetResponseDto(SnippetsData):
|
||||
"""Delete snippet response"""
|
||||
|
|
@ -185,7 +185,7 @@ class SubscriptionWithoutHapp(BaseModel):
|
|||
|
||||
class GetAllSubscriptionsResponseDto(BaseModel):
|
||||
subscriptions: List[SubscriptionWithoutHapp]
|
||||
total: float
|
||||
total: int
|
||||
|
||||
|
||||
class GetSubscriptionByUsernameResponseDto(BaseModel):
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class SubscriptionRequestHistoryRecord(BaseModel):
|
|||
|
||||
class SubscriptionRequestHistoryData(BaseModel):
|
||||
records: List[SubscriptionRequestHistoryRecord]
|
||||
total: float
|
||||
total: int
|
||||
|
||||
|
||||
class GetAllSubscriptionRequestHistoryResponseDto(SubscriptionRequestHistoryData):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,56 @@ from uuid import UUID
|
|||
|
||||
from pydantic import BaseModel, Field, StringConstraints
|
||||
|
||||
from remnawave.enums import (
|
||||
ResponseRuleConditionOperator,
|
||||
ResponseRuleOperator,
|
||||
ResponseRuleVersion,
|
||||
ResponseType,
|
||||
)
|
||||
|
||||
|
||||
class ResponseRuleCondition(BaseModel):
|
||||
"""Condition to check against request headers"""
|
||||
header_name: Annotated[str, StringConstraints(pattern=r"^[!#$%&'*+\-.0-9A-Z^_`a-z|~]+$")] = Field(
|
||||
alias="headerName"
|
||||
)
|
||||
operator: ResponseRuleConditionOperator
|
||||
value: Annotated[str, StringConstraints(min_length=1, max_length=255)]
|
||||
case_sensitive: bool = Field(alias="caseSensitive")
|
||||
|
||||
|
||||
class ResponseModificationHeader(BaseModel):
|
||||
"""Response header modification"""
|
||||
key: Annotated[str, StringConstraints(pattern=r"^[!#$%&'*+\-.0-9A-Z^_`a-z|~]+$")]
|
||||
value: Annotated[str, StringConstraints(min_length=1)]
|
||||
|
||||
|
||||
class ResponseModifications(BaseModel):
|
||||
"""Response modifications to apply when rule matches"""
|
||||
headers: Optional[List[ResponseModificationHeader]] = None
|
||||
subscription_template: Optional[Annotated[str, StringConstraints(min_length=1)]] = Field(
|
||||
None, alias="subscriptionTemplate"
|
||||
)
|
||||
|
||||
|
||||
class ResponseRule(BaseModel):
|
||||
"""Individual response rule configuration"""
|
||||
name: Annotated[str, StringConstraints(min_length=1, max_length=50)]
|
||||
description: Optional[Annotated[str, StringConstraints(min_length=1, max_length=250)]] = None
|
||||
enabled: bool
|
||||
operator: ResponseRuleOperator
|
||||
conditions: List[ResponseRuleCondition]
|
||||
response_type: ResponseType = Field(alias="responseType")
|
||||
response_modifications: Optional[ResponseModifications] = Field(
|
||||
None, alias="responseModifications"
|
||||
)
|
||||
|
||||
|
||||
class ResponseRules(BaseModel):
|
||||
"""Response rules configuration"""
|
||||
version: ResponseRuleVersion
|
||||
rules: List[ResponseRule]
|
||||
|
||||
|
||||
class SubscriptionSettingsResponseDto(BaseModel):
|
||||
uuid: UUID
|
||||
|
|
@ -27,6 +77,7 @@ class SubscriptionSettingsResponseDto(BaseModel):
|
|||
None, alias="customResponseHeaders"
|
||||
)
|
||||
randomize_hosts: bool = Field(alias="randomizeHosts")
|
||||
response_rules: Optional[ResponseRules] = Field(None, alias="responseRules")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
|
@ -41,37 +92,38 @@ class UpdateSubscriptionSettingsResponseDto(SubscriptionSettingsResponseDto):
|
|||
|
||||
class UpdateSubscriptionSettingsRequestDto(BaseModel):
|
||||
uuid: UUID
|
||||
profile_title: Optional[str] = Field(None, serialization_alias="profileTitle")
|
||||
support_link: Optional[str] = Field(None, serialization_alias="supportLink")
|
||||
profile_title: Optional[str] = Field(None, alias="profileTitle")
|
||||
support_link: Optional[str] = Field(None, alias="supportLink")
|
||||
profile_update_interval: Optional[int] = Field(
|
||||
None, serialization_alias="profileUpdateInterval"
|
||||
None, alias="profileUpdateInterval"
|
||||
)
|
||||
is_profile_webpage_url_enabled: Optional[bool] = Field(
|
||||
None, serialization_alias="isProfileWebpageUrlEnabled"
|
||||
None, alias="isProfileWebpageUrlEnabled"
|
||||
)
|
||||
serve_json_at_base_subscription: Optional[bool] = Field(
|
||||
None, serialization_alias="serveJsonAtBaseSubscription"
|
||||
None, alias="serveJsonAtBaseSubscription"
|
||||
)
|
||||
add_username_to_base_subscription: Optional[bool] = Field(
|
||||
None, serialization_alias="addUsernameToBaseSubscription"
|
||||
None, alias="addUsernameToBaseSubscription"
|
||||
)
|
||||
is_show_custom_remarks: Optional[bool] = Field(
|
||||
None, serialization_alias="isShowCustomRemarks"
|
||||
None, alias="isShowCustomRemarks"
|
||||
)
|
||||
happ_announce: Annotated[Optional[str], StringConstraints(max_length=200)] = Field(
|
||||
None, serialization_alias="happAnnounce"
|
||||
None, alias="happAnnounce"
|
||||
)
|
||||
happ_routing: Optional[str] = Field(None, serialization_alias="happRouting")
|
||||
happ_routing: Optional[str] = Field(None, alias="happRouting")
|
||||
expired_users_remarks: Optional[List[str]] = Field(
|
||||
None, serialization_alias="expiredUsersRemarks"
|
||||
None, alias="expiredUsersRemarks"
|
||||
)
|
||||
limited_users_remarks: Optional[List[str]] = Field(
|
||||
None, serialization_alias="limitedUsersRemarks"
|
||||
None, alias="limitedUsersRemarks"
|
||||
)
|
||||
disabled_users_remarks: Optional[List[str]] = Field(
|
||||
None, serialization_alias="disabledUsersRemarks"
|
||||
None, alias="disabledUsersRemarks"
|
||||
)
|
||||
custom_response_headers: Optional[Dict[str, str]] = Field(
|
||||
None, serialization_alias="customResponseHeaders"
|
||||
None, alias="customResponseHeaders"
|
||||
)
|
||||
randomize_hosts: Optional[bool] = Field(None, serialization_alias="randomizeHosts")
|
||||
randomize_hosts: Optional[bool] = Field(None, alias="randomizeHosts")
|
||||
response_rules: Optional[ResponseRules] = Field(None, alias="responseRules")
|
||||
|
|
@ -1,13 +1,23 @@
|
|||
from typing import Any, Optional
|
||||
from typing import Annotated, Any, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, StringConstraints
|
||||
|
||||
from remnawave.enums import TemplateType
|
||||
|
||||
|
||||
class TemplateResponseDto(BaseModel):
|
||||
uuid: UUID
|
||||
name: str
|
||||
template_type: TemplateType = Field(alias="templateType")
|
||||
template_json: Optional[Any] = Field(None, alias="templateJson")
|
||||
encoded_template_yaml: Optional[str] = Field(None, alias="encodedTemplateYaml")
|
||||
|
||||
|
||||
class TemplateInfoDto(BaseModel):
|
||||
"""Template info without content - used in list responses"""
|
||||
uuid: UUID
|
||||
name: str
|
||||
template_type: TemplateType = Field(alias="templateType")
|
||||
template_json: Optional[Any] = Field(None, alias="templateJson")
|
||||
encoded_template_yaml: Optional[str] = Field(None, alias="encodedTemplateYaml")
|
||||
|
|
@ -16,9 +26,26 @@ class TemplateResponseDto(BaseModel):
|
|||
class GetTemplateResponseDto(TemplateResponseDto):
|
||||
pass
|
||||
|
||||
class GetTemplatesData(BaseModel):
|
||||
total: int
|
||||
templates: List[TemplateInfoDto]
|
||||
|
||||
class GetTemplatesResponseDto(GetTemplatesData):
|
||||
pass
|
||||
|
||||
|
||||
class CreateSubscriptionTemplateRequestDto(BaseModel):
|
||||
name: Annotated[str, StringConstraints(min_length=2, max_length=255, pattern=r"^[A-Za-z0-9_\s-]+$")]
|
||||
template_type: TemplateType = Field(serialization_alias="templateType")
|
||||
|
||||
|
||||
class CreateSubscriptionTemplateResponseDto(TemplateResponseDto):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateTemplateRequestDto(BaseModel):
|
||||
template_type: TemplateType = Field(serialization_alias="templateType")
|
||||
uuid: UUID
|
||||
name: Optional[Annotated[str, StringConstraints(min_length=2, max_length=255, pattern=r"^[A-Za-z0-9_\s-]+$")]] = None
|
||||
template_json: Optional[dict] = Field(None, serialization_alias="templateJson")
|
||||
encoded_template_yaml: Optional[str] = Field(
|
||||
None, serialization_alias="encodedTemplateYaml"
|
||||
|
|
@ -27,3 +54,23 @@ class UpdateTemplateRequestDto(BaseModel):
|
|||
|
||||
class UpdateTemplateResponseDto(TemplateResponseDto):
|
||||
pass
|
||||
|
||||
class DeleteTemplateData(BaseModel):
|
||||
is_deleted: bool = Field(alias="isDeleted")
|
||||
|
||||
|
||||
class DeleteSubscriptionTemplateResponseDto(DeleteTemplateData):
|
||||
pass
|
||||
|
||||
|
||||
# Legacy aliases for backward compatibility
|
||||
class UpdateTemplateRequestDtoLegacy(BaseModel):
|
||||
template_type: TemplateType = Field(serialization_alias="templateType")
|
||||
template_json: Optional[dict] = Field(None, serialization_alias="templateJson")
|
||||
encoded_template_yaml: Optional[str] = Field(
|
||||
None, serialization_alias="encodedTemplateYaml"
|
||||
)
|
||||
|
||||
|
||||
class UpdateTemplateResponseDtoLegacy(TemplateResponseDto):
|
||||
pass
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import datetime
|
||||
from typing import List
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from remnawave.enums import ResponseType
|
||||
from remnawave.models.subscriptions_settings import ResponseRule, ResponseRules
|
||||
|
||||
|
||||
class NodeStatistic(BaseModel):
|
||||
node_name: str = Field(alias="nodeName")
|
||||
|
|
@ -105,26 +108,60 @@ class GetRemnawaveHealthResponseDto(BaseModel):
|
|||
pm2_stats: List[PM2Stat] = Field(alias="pm2Stats")
|
||||
|
||||
|
||||
|
||||
class NodeMetric(BaseModel):
|
||||
uuid: str
|
||||
name: str
|
||||
address: str
|
||||
is_online: bool = Field(alias="isOnline")
|
||||
cpu_usage: float = Field(alias="cpuUsage")
|
||||
memory_usage: float = Field(alias="memoryUsage")
|
||||
network_upload: int = Field(alias="networkUpload")
|
||||
network_download: int = Field(alias="networkDownload")
|
||||
uptime: int
|
||||
last_seen: datetime.datetime = Field(alias="lastSeen")
|
||||
connected_users: int = Field(alias="connectedUsers")
|
||||
"""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
|
||||
|
||||
|
||||
class GetNodesMetricsResponseDto(BaseModel):
|
||||
nodes: List[NodeMetric]
|
||||
|
||||
|
||||
class X25519KeyPair(BaseModel):
|
||||
public_key: str = Field(alias="publicKey")
|
||||
private_key: str = Field(alias="privateKey")
|
||||
|
||||
|
||||
class GetX25519KeyPairResponseDto(BaseModel):
|
||||
key_pairs: List[X25519KeyPair] = Field(alias="keyPairs")
|
||||
key_pairs: List[X25519KeyPair] = Field(alias="keyPairs")
|
||||
|
||||
|
||||
class EncryptHappCryptoLinkRequestDto(BaseModel):
|
||||
link_to_encrypt: str = Field(serialization_alias="linkToEncrypt")
|
||||
|
||||
|
||||
class EncryptHappCryptoLinkData(BaseModel):
|
||||
encrypted_link: str = Field(alias="encryptedLink")
|
||||
|
||||
|
||||
class EncryptHappCryptoLinkResponseDto(BaseModel):
|
||||
response: EncryptHappCryptoLinkData
|
||||
|
||||
|
||||
class DebugSrrMatcherRequestDto(BaseModel):
|
||||
response_rules: ResponseRules = Field(serialization_alias="responseRules")
|
||||
|
||||
|
||||
class DebugSrrMatcherData(BaseModel):
|
||||
matched: bool
|
||||
response_type: ResponseType = Field(alias="responseType")
|
||||
matched_rule: Optional[ResponseRule] = Field(alias="matchedRule")
|
||||
input_headers: Dict[str, str] = Field(alias="inputHeaders")
|
||||
output_headers: Dict[str, str] = Field(alias="outputHeaders")
|
||||
|
||||
|
||||
class DebugSrrMatcherResponseDto(BaseModel):
|
||||
response: DebugSrrMatcherData
|
||||
|
|
@ -68,6 +68,7 @@ class CreateUserRequestDto(BaseModel):
|
|||
active_internal_squads: list[str] | None = Field(
|
||||
None, serialization_alias="activeInternalSquads"
|
||||
)
|
||||
external_squad_uuid: UUID | None = Field(None, alias="externalSquadUuid")
|
||||
uuid: Optional[UUID] = Field(None, description="UUID of the user. Optional. If not provided, a new UUID will be generated by Remnawave.")
|
||||
|
||||
|
||||
|
|
@ -128,6 +129,7 @@ class UserResponseDto(BaseModel):
|
|||
)
|
||||
happ: HappCrypto | None = Field(None, alias="happ")
|
||||
tag: str | None = Field(None, alias="tag")
|
||||
external_squad_uuid: UUID | None = Field(None, alias="externalSquadUuid")
|
||||
created_at: datetime = Field(alias="createdAt")
|
||||
updated_at: datetime = Field(alias="updatedAt")
|
||||
|
||||
|
|
@ -158,7 +160,7 @@ class TelegramUserResponseDto(RootModel[list[UserResponseDto]]):
|
|||
|
||||
class UsersResponseDto(BaseModel):
|
||||
users: list[UserResponseDto]
|
||||
total: float
|
||||
total: int
|
||||
|
||||
|
||||
class DeleteUserResponseDto(BaseModel):
|
||||
|
|
@ -229,7 +231,7 @@ class SubscriptionRequestRecord(BaseModel):
|
|||
|
||||
|
||||
class SubscriptionRequestsResponseData(BaseModel):
|
||||
total: float
|
||||
total: int
|
||||
records: List[SubscriptionRequestRecord]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ class UpdateUserFields(BaseModel):
|
|||
)
|
||||
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
|
||||
)
|
||||
telegram_id: Optional[int] = Field(
|
||||
None, serialization_alias="telegramId"
|
||||
)
|
||||
|
||||
|
||||
|
||||
class BulkAllUpdateUsersRequestDto(BaseModel):
|
||||
|
|
@ -66,6 +73,10 @@ class BulkAllUpdateUsersRequestDto(BaseModel):
|
|||
serialization_alias="telegramId"
|
||||
)
|
||||
|
||||
hwid_device_limit: Optional[int] = Field(
|
||||
None, serialization_alias="hwidDeviceLimit", strict=True, ge=0
|
||||
)
|
||||
|
||||
email: Optional[str] = None
|
||||
|
||||
tag: Optional[TagStr] = Field(
|
||||
|
|
|
|||
77
tests/test_snippets.py
Normal file
77
tests/test_snippets.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import pytest
|
||||
|
||||
from remnawave.models import (
|
||||
CreateSnippetRequestDto,
|
||||
DeleteSnippetRequestDto,
|
||||
GetSnippetsResponseDto,
|
||||
UpdateSnippetRequestDto,
|
||||
)
|
||||
|
||||
def random_string(length=10):
|
||||
import random
|
||||
import string
|
||||
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_snippets_full_workflow(remnawave):
|
||||
# Test getting all snippets
|
||||
snippets = await remnawave.snippets.get_snippets()
|
||||
assert isinstance(snippets, GetSnippetsResponseDto)
|
||||
initial_count = snippets.total
|
||||
|
||||
# Test creating a snippet with unique name
|
||||
rand_name = random_string()
|
||||
create_request = CreateSnippetRequestDto(
|
||||
name=rand_name,
|
||||
snippet=[
|
||||
{"type": "vmess", "port": 443},
|
||||
{"type": "vless", "encryption": "none"}
|
||||
]
|
||||
)
|
||||
created = await remnawave.snippets.create_snippet(create_request)
|
||||
assert created.total == initial_count + 1
|
||||
|
||||
# Verify snippet was created
|
||||
snippets_after_create = await remnawave.snippets.get_snippets()
|
||||
snippet_names = [s.name for s in snippets_after_create.snippets]
|
||||
assert rand_name in snippet_names
|
||||
|
||||
# Test updating the snippet
|
||||
update_request = UpdateSnippetRequestDto(
|
||||
name=rand_name,
|
||||
snippet=[
|
||||
{"type": "shadowsocks", "method": "aes-256-gcm"},
|
||||
{"type": "trojan", "password": "test-password"}
|
||||
]
|
||||
)
|
||||
updated = await remnawave.snippets.update_snippet(update_request)
|
||||
assert updated.total == initial_count + 1
|
||||
|
||||
# Test deleting the snippet
|
||||
delete_request = DeleteSnippetRequestDto(name=rand_name)
|
||||
deleted = await remnawave.snippets.delete_snippet_by_name(delete_request)
|
||||
assert deleted.total == initial_count
|
||||
|
||||
# Verify snippet was deleted
|
||||
snippets_after_delete = await remnawave.snippets.get_snippets()
|
||||
snippet_names_after = [s.name for s in snippets_after_delete.snippets]
|
||||
assert rand_name not in snippet_names_after
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_snippet_name_validation(remnawave):
|
||||
"""Test that snippet names are properly validated"""
|
||||
# Valid names
|
||||
valid_names = ["Test Snippet", "My_Snippet", "Snippet-123", "A B C"]
|
||||
|
||||
for name in valid_names:
|
||||
request = CreateSnippetRequestDto(name=name, snippet=[{"test": "data"}])
|
||||
# Should not raise validation error
|
||||
assert request.name == name
|
||||
|
||||
# Invalid names would be caught by Pydantic validation
|
||||
with pytest.raises(ValueError):
|
||||
CreateSnippetRequestDto(name="x", snippet=[]) # Too short
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
CreateSnippetRequestDto(name="", snippet=[]) # Empty name
|
||||
|
|
@ -1,19 +1,91 @@
|
|||
import pytest
|
||||
|
||||
from remnawave.enums import TemplateType
|
||||
from remnawave.models import GetTemplateResponseDto, UpdateTemplateRequestDto, UpdateTemplateResponseDto
|
||||
from remnawave.models import (
|
||||
CreateSubscriptionTemplateRequestDto,
|
||||
CreateSubscriptionTemplateResponseDto,
|
||||
DeleteSubscriptionTemplateResponseDto,
|
||||
GetTemplateResponseDto,
|
||||
GetTemplatesResponseDto,
|
||||
UpdateTemplateRequestDto,
|
||||
UpdateTemplateResponseDto,
|
||||
)
|
||||
|
||||
def random_string(length=10):
|
||||
import random
|
||||
import string
|
||||
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_all_templates(remnawave):
|
||||
"""Проверка получения всех шаблонов"""
|
||||
templates = await remnawave.subscriptions_template.get_all_templates()
|
||||
assert isinstance(templates, GetTemplatesResponseDto)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subscriptions_template(remnawave):
|
||||
template_type: TemplateType = TemplateType.SINGBOX
|
||||
template = await remnawave.subscriptions_template.get_template(
|
||||
template_type=template_type
|
||||
async def test_create_template(remnawave):
|
||||
"""Проверка создания шаблона"""
|
||||
rand_name = random_string()
|
||||
create_request = CreateSubscriptionTemplateRequestDto(
|
||||
name=rand_name,
|
||||
template_type=TemplateType.SINGBOX,
|
||||
)
|
||||
created_template = await remnawave.subscriptions_template.create_template(create_request)
|
||||
assert isinstance(created_template, CreateSubscriptionTemplateResponseDto)
|
||||
assert created_template.name == rand_name
|
||||
assert created_template.template_type == TemplateType.SINGBOX
|
||||
|
||||
# Удаляем после проверки
|
||||
await remnawave.subscriptions_template.delete_template(str(created_template.uuid))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def created_template(remnawave):
|
||||
"""Фикстура: создать временный шаблон и удалить после теста"""
|
||||
create_request = CreateSubscriptionTemplateRequestDto(
|
||||
name="Temp Template",
|
||||
template_type=TemplateType.SINGBOX,
|
||||
)
|
||||
template = await remnawave.subscriptions_template.create_template(create_request)
|
||||
yield template
|
||||
await remnawave.subscriptions_template.delete_template(str(template.uuid))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_template_by_uuid(remnawave, created_template):
|
||||
"""Проверка получения шаблона по UUID"""
|
||||
template = await remnawave.subscriptions_template.get_template_by_uuid(
|
||||
str(created_template.uuid)
|
||||
)
|
||||
assert isinstance(template, GetTemplateResponseDto)
|
||||
assert template.uuid == created_template.uuid
|
||||
|
||||
update_template = await remnawave.subscriptions_template.update_template(
|
||||
UpdateTemplateRequestDto(template_type=template_type)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_template(remnawave, created_template):
|
||||
"""Проверка обновления шаблона"""
|
||||
update_request = UpdateTemplateRequestDto(
|
||||
uuid=created_template.uuid,
|
||||
name="Updated Template Name",
|
||||
)
|
||||
assert isinstance(update_template, UpdateTemplateResponseDto)
|
||||
assert update_template.template_type == template_type
|
||||
updated_template = await remnawave.subscriptions_template.update_template(update_request)
|
||||
assert isinstance(updated_template, UpdateTemplateResponseDto)
|
||||
assert updated_template.name == "Updated Template Name"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_template(remnawave):
|
||||
"""Проверка удаления шаблона"""
|
||||
# Сначала создаем
|
||||
create_request = CreateSubscriptionTemplateRequestDto(
|
||||
name="Temp Delete Template",
|
||||
template_type=TemplateType.SINGBOX,
|
||||
)
|
||||
created = await remnawave.subscriptions_template.create_template(create_request)
|
||||
|
||||
# Теперь удаляем
|
||||
delete_response = await remnawave.subscriptions_template.delete_template(
|
||||
str(created.uuid)
|
||||
)
|
||||
assert isinstance(delete_response, DeleteSubscriptionTemplateResponseDto)
|
||||
assert delete_response.is_deleted is True
|
||||
Loading…
Add table
Add a link
Reference in a new issue