Skip to content

Commit 29ddd29

Browse files
committed
refactor(portaswitch): enhance type handling for user_id and access_token
1 parent ac7b575 commit 29ddd29

File tree

3 files changed

+48
-28
lines changed

3 files changed

+48
-28
lines changed

app/bss/adapters/portaswitch/adapter.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import logging
2-
from concurrent.futures import ThreadPoolExecutor, as_completed
32
from datetime import datetime, timedelta, UTC
43
from typing import Final, Iterator, Optional, Dict
54

@@ -8,7 +7,16 @@
87
from app_config import AppConfig
98
from bss.adapters import BSSAdapter
109
from bss.dbs import TiedKeyValue
11-
from bss.models import DeliveryChannel, SipServer, UserCreateResponse, CustomRequest, CustomResponse, CustomPage
10+
from bss.models import (
11+
DeliveryChannel,
12+
SipServer,
13+
UserCreateResponse,
14+
CustomRequest,
15+
CustomResponse,
16+
CustomPage,
17+
UserId,
18+
AccessToken,
19+
)
1220
from bss.types import (
1321
CallRecordingId,
1422
Capabilities,
@@ -43,11 +51,11 @@
4351

4452

4553
class PortaSwitchAdapter(BSSAdapter):
46-
"""Connects WebTrit and PortaSwitch. Authenticate a user using his/her data in PortaSwitch,
47-
retrieve user's SIP credentials to be used by WebTrit and return a list of other configured
48-
extenstions (to be provided as 'Cloud PBX' contacts).
49-
Currently does not support OTP login.
54+
"""Bridges WebTrit Core with PortaSwitch APIs.
5055
56+
Provides authentication (password, OTP, auto-provision), session lifecycle management,
57+
SIP credential retrieval, contacts, call history, voicemail, notifications, and other
58+
capabilities required by WebTrit clients.
5159
"""
5260

5361
VERSION: Final[str] = "0.1.20"
@@ -129,8 +137,8 @@ def authenticate(self, user: UserInfo, password: str = None) -> SessionInfo:
129137
session_data = self._account_api.login(account_info["login"], account_info["password"])
130138

131139
return SessionInfo(
132-
user_id=account_info["i_account"],
133-
access_token=session_data["access_token"],
140+
user_id=UserId(str(account_info["i_account"])),
141+
access_token=AccessToken(session_data["access_token"]),
134142
refresh_token=session_data["refresh_token"],
135143
expires_at=datetime.now() + timedelta(seconds=session_data["expires_in"]),
136144
)
@@ -224,7 +232,7 @@ def validate_otp(self, otp: OTPVerifyRequest) -> SessionInfo:
224232
session_data: dict = self._account_api.login(account_info["login"], account_info["password"])
225233

226234
return SessionInfo(
227-
user_id=account_info["i_account"],
235+
user_id=str(account_info["i_account"]),
228236
access_token=session_data["access_token"],
229237
refresh_token=session_data["refresh_token"],
230238
expires_at=datetime.now() + timedelta(seconds=session_data["expires_in"]),
@@ -264,7 +272,7 @@ def validate_session(self, access_token: str) -> SessionInfo:
264272
code="access_token_invalid",
265273
)
266274

267-
return SessionInfo(user_id=user_id, access_token=access_token)
275+
return SessionInfo(user_id=str(user_id), access_token=access_token)
268276
except ExpiredSignatureError:
269277
raise WebTritErrorException(
270278
status_code=401,
@@ -303,7 +311,7 @@ def refresh_session(self, refresh_token: str) -> SessionInfo:
303311
account_info: dict = self._account_api.get_account_info(access_token=access_token)["account_info"]
304312

305313
return SessionInfo(
306-
user_id=account_info["i_account"],
314+
user_id=str(account_info["i_account"]),
307315
access_token=session_data["access_token"],
308316
refresh_token=session_data["refresh_token"],
309317
expires_at=datetime.now() + timedelta(seconds=session_data["expires_in"]),

app/bss/adapters/portaswitch/serializer.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime, timezone
22
from typing import Optional
33

4-
from bss.models import SIPTransport
4+
from bss.models import SIPTransport, UserId
55
from bss.types import (
66
Balance,
77
BalanceType,
@@ -20,7 +20,6 @@
2020
VoicemailMessageAttachment,
2121
Direction,
2222
)
23-
2423
from .types import PortaSwitchMailboxMessageFlag
2524

2625
#: dict: Contains a map between a PortaSwitch AccountInfo.billing_model and BalanceType.
@@ -100,7 +99,7 @@ def get_contact_info_by_account(account_info: dict, current_user: int) -> Contac
10099
"""
101100

102101
return ContactInfo(
103-
user_id=account_info["i_account"],
102+
user_id=UserId(str(account_info["i_account"])),
104103
is_current_user=account_info["i_account"] == current_user,
105104
alias_name=account_info.get("extension_name"),
106105
company_name=account_info.get("companyname", ""),
@@ -132,9 +131,11 @@ def get_contact_info_by_extension(extension_info: dict, aliases: list, current_u
132131
ContactInfo: The filled structure of ContactInfo.
133132
"""
134133

134+
i_account = extension_info.get("i_account")
135+
135136
return ContactInfo(
136-
user_id=extension_info.get("i_account"),
137-
is_current_user=extension_info.get("i_account") == current_user,
137+
user_id=UserId(str(i_account)) if i_account else None,
138+
is_current_user=i_account == current_user,
138139
alias_name=extension_info.get("name", ""),
139140
first_name=extension_info.get("firstname", ""),
140141
last_name=extension_info.get("lastname", ""),

app/bss/models.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class UserId(RootModel[str]):
140140
The **Adaptee** must consistently return the same `UserId` for the same user,
141141
regardless of the `UserRef` used for sign-in.
142142
"""
143-
143+
144144
model_config = {"json_schema_extra": {"example": "123456789abcdef0123456789abcdef0"}}
145145

146146

@@ -182,7 +182,7 @@ class UserRef(RootModel[str]):
182182
with the user. When the same user is accessed using different references,
183183
it is crucial to ensure that the same `UserId` is assigned to this user.
184184
"""
185-
185+
186186
model_config = {"json_schema_extra": {"example": "1234567890"}}
187187

188188

@@ -272,7 +272,8 @@ class BalanceType(Enum):
272272

273273

274274
class Balance(BaseModel):
275-
amount: Optional[float] = Field(None, description="The user's current balance.", json_schema_extra={"example": "50.00"})
275+
amount: Optional[float] = Field(None, description="The user's current balance.",
276+
json_schema_extra={"example": "50.00"})
276277
balance_type: Optional[BalanceType] = Field(
277278
None,
278279
description="Meaning of the balance figure for this user.\n\n* `inapplicable` means the **Adaptee** does not handle\n billing and does not have the balance data.\n* `prepaid` means the number reflects the funds that\n the user has available for spending.\n* `postpaid` means the balance reflects the amount of\n previously accumulated charges (how much the user\n owes - to be used in conjunction with a `credit_limit`).\n",
@@ -352,7 +353,8 @@ class SipInfo(BaseModel):
352353
description="The visible identification of the caller to be included in the SIP request.\nThis will be shown to the called party as the caller's name. If not provided,\nthe `display_name` will be populated with the `username`.\n",
353354
json_schema_extra={"example": "Thomas A. Anderson"},
354355
)
355-
password: str = Field(..., description="The password for the SIP account.", json_schema_extra={"example": "strong_password"})
356+
password: str = Field(..., description="The password for the SIP account.",
357+
json_schema_extra={"example": "strong_password"})
356358
transport: Optional[SIPTransport] = Field(
357359
default_factory=lambda: SIPTransport.UDP,
358360
description="The transport protocol for SIP communication. UDP by default.",
@@ -542,7 +544,7 @@ class OtpId(RootModel[str]):
542544
Note: This ID is NOT the code that the user will enter. It serves
543545
to match the originally generated OTP with the one provided by the user.
544546
"""
545-
547+
546548
model_config = {"json_schema_extra": {"example": "12345678-9abc-def0-1234-56789abcdef0"}}
547549

548550

@@ -657,7 +659,10 @@ class SessionOtpVerifyRequest(BaseModel):
657659

658660

659661
class Contact(BaseModel):
660-
user_id: Optional[UserId]
662+
user_id: Optional[UserId] = Field(
663+
default=None,
664+
description="Unique identifier of the contact on the **Adaptee**. Optional for synthetic entries.",
665+
)
661666
is_current_user: Optional[bool] = Field(
662667
None, description="Indicates whether the contact is associated with the same user who making the request."
663668
)
@@ -671,8 +676,10 @@ class Contact(BaseModel):
671676
description="The name of the company the user is associated with.",
672677
json_schema_extra={"example": "Matrix"},
673678
)
674-
email: Optional[EmailStr] = Field(None, description="The user's email address.", json_schema_extra={"example": "a.black@matrix.com"})
675-
first_name: Optional[str] = Field(None, description="The user's first name.", json_schema_extra={"example": "Annabelle"})
679+
email: Optional[EmailStr] = Field(None, description="The user's email address.",
680+
json_schema_extra={"example": "a.black@matrix.com"})
681+
first_name: Optional[str] = Field(None, description="The user's first name.",
682+
json_schema_extra={"example": "Annabelle"})
676683
last_name: Optional[str] = Field(None, description="The user's last name.", json_schema_extra={"example": "Black"})
677684
numbers: Numbers
678685
sip_status: Optional[SipStatus] = Field(
@@ -688,10 +695,14 @@ class UserInfoShowResponse(BaseModel):
688695
json_schema_extra={"example": "CTO"},
689696
)
690697
balance: Optional[Balance] = None
691-
company_name: Optional[str] = Field(None, description="The company the user is associated with.", json_schema_extra={"example": "Matrix"})
692-
email: Optional[EmailStr] = Field(None, description="The user's email address.", json_schema_extra={"example": "neo@matrix.com"})
693-
first_name: Optional[str] = Field(None, description="The user's first name.", json_schema_extra={"example": "Thomas"})
694-
last_name: Optional[str] = Field(None, description="The user's last name.", json_schema_extra={"example": "Anderson"})
698+
company_name: Optional[str] = Field(None, description="The company the user is associated with.",
699+
json_schema_extra={"example": "Matrix"})
700+
email: Optional[EmailStr] = Field(None, description="The user's email address.",
701+
json_schema_extra={"example": "neo@matrix.com"})
702+
first_name: Optional[str] = Field(None, description="The user's first name.",
703+
json_schema_extra={"example": "Thomas"})
704+
last_name: Optional[str] = Field(None, description="The user's last name.",
705+
json_schema_extra={"example": "Anderson"})
695706
numbers: Numbers
696707
sip: SipInfo
697708
status: Optional[Status] = Field(

0 commit comments

Comments
 (0)