Skip to content

Commit d818e2e

Browse files
committed
feat(portaswitch): add offset request parameter to get_account_list API
1 parent 15d684e commit d818e2e

File tree

3 files changed

+44
-15
lines changed

3 files changed

+44
-15
lines changed

app/bss/adapters/portaswitch/adapter.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from datetime import datetime, timedelta, UTC
44
from typing import Final, Iterator, Optional, Dict
55

6+
from jose.exceptions import ExpiredSignatureError, JWTError
7+
68
from app_config import AppConfig
79
from bss.adapters import BSSAdapter
810
from bss.dbs import TiedKeyValue
@@ -24,10 +26,8 @@
2426
UserEventGroup,
2527
UserEventType,
2628
)
27-
from jose.exceptions import ExpiredSignatureError, JWTError
2829
from localization import get_translation_func
2930
from report_error import WebTritErrorException
30-
3131
from .api import AccountAPI, AdminAPI
3232
from .config import Settings
3333
from .serializer import Serializer
@@ -171,7 +171,7 @@ def generate_otp(self, user: UserInfo) -> OTPCreateResponse:
171171
raise WebTritErrorException(500, "Unknown error", code="external_api_issue")
172172

173173
otp_id: str = generate_otp_id()
174-
self._cached_otp_ids[otp_id] = i_account
174+
self._cached_otp_ids[otp_id] = i_account, user.user_id
175175

176176
env_info = self._admin_api.get_env_info()
177177

@@ -204,12 +204,12 @@ def validate_otp(self, otp: OTPVerifyRequest) -> SessionInfo:
204204
# We need the otp_id only for storing the i_account.
205205
otp_id = safely_extract_scalar_value(otp.otp_id)
206206

207-
i_account: int = self._cached_otp_ids.get(otp_id)
207+
(i_account, user_ref) = self._cached_otp_ids.get(otp_id, (None, None))
208208
if not i_account:
209209
raise WebTritErrorException(status_code=404, error_message=f"Incorrect OTP code: {otp.code}")
210210

211211
data: dict = self._admin_api.verify_otp(otp_token=otp.code)
212-
if str(i_account) not in self._otp_settings.IGNORE_ACCOUNTS and not data["success"]:
212+
if user_ref not in self._otp_settings.IGNORE_ACCOUNTS and not data["success"]:
213213
raise WebTritErrorException(status_code=404, error_message=f"Incorrect OTP code: {otp.code}")
214214

215215
self._cached_otp_ids.pop(otp_id)
@@ -407,7 +407,7 @@ def retrieve_contacts(self, session: SessionInfo, user: UserInfo) -> list[Contac
407407
type.value for type in
408408
self._portaswitch_settings.CONTACTS_SELECTING_EXTENSION_TYPES
409409
}
410-
accounts = self._admin_api.get_account_list(i_customer)["account_list"]
410+
accounts = self._get_all_accounts_by_customer(i_customer)
411411
account_to_aliases = {account["i_account"]: account.get("alias_list", []) for account in accounts}
412412
extensions = self._admin_api.get_extensions_list(i_customer)["extensions_list"]
413413

@@ -416,7 +416,7 @@ def retrieve_contacts(self, session: SessionInfo, user: UserInfo) -> list[Contac
416416
aliases = [str(alias) for alias in account_to_aliases.get(ext.get("i_account"), [])]
417417
contacts.append(Serializer.get_contact_info_by_extension(ext, aliases, i_account))
418418
case PortaSwitchContactsSelectingMode.ACCOUNTS:
419-
accounts = self._admin_api.get_account_list(i_customer)["account_list"]
419+
accounts = self._get_all_accounts_by_customer(i_customer)
420420

421421
for account in accounts:
422422
if (status := account.get("status")) and status == "blocked":
@@ -833,7 +833,7 @@ def _get_number_to_customer_accounts_map(self) -> dict[str, dict]:
833833
# Retrieve accounts for a pre-defined list of customers to map them to phonebook records
834834
with ThreadPoolExecutor(max_workers=10) as executor:
835835
future_to_customer_id = {
836-
executor.submit(lambda cid: self._admin_api.get_account_list(int(cid))["account_list"],
836+
executor.submit(lambda cid: self._get_all_accounts_by_customer(int(cid)),
837837
cid): cid
838838
for cid in self._portaswitch_settings.CONTACTS_SELECTING_CUSTOMER_IDS
839839
}
@@ -848,3 +848,26 @@ def _get_number_to_customer_accounts_map(self) -> dict[str, dict]:
848848
f"Error fetching accounts for customer {future_to_customer_id[future]}: {e}")
849849

850850
return number_to_accounts
851+
852+
def _get_all_accounts_by_customer(self, i_customer: int) -> list[dict]:
853+
"""Fetch all accounts for a customer using pagination with offset until total is reached."""
854+
all_accounts: list[dict] = []
855+
offset = 0
856+
limit = 1000
857+
858+
while True:
859+
resp = self._admin_api.get_account_list(i_customer, limit, offset)
860+
page = resp.get("account_list", []) if isinstance(resp, dict) else []
861+
total = resp.get("total") if isinstance(resp, dict) else None
862+
863+
all_accounts.extend(page)
864+
865+
# Stop if we've reached the total or the page is shorter than limit
866+
if total is not None and len(all_accounts) >= int(total):
867+
break
868+
if len(page) < limit:
869+
break
870+
871+
offset += limit
872+
873+
return all_accounts

app/bss/adapters/portaswitch/api/admin.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def refresh(self, user=None, auth_session=None):
5353
self.store_auth_session(session, self._api_user)
5454
return session
5555

56-
def get_account_list(self, i_customer: int):
56+
def get_account_list(self, i_customer: int, limit: int = 1000, offset: int = 0):
5757
"""Returns information about accounts related to the input i_customer.
5858
5959
Parameters:
@@ -72,7 +72,9 @@ def get_account_list(self, i_customer: int):
7272
"get_not_closed_accounts": 1,
7373
"get_only_real_accounts": 1,
7474
"get_statuses": 1,
75-
"limit": 1000,
75+
"get_total": 1,
76+
"limit": limit,
77+
"offset": offset,
7678
"limit_alias_did_number_list": 100
7779
},
7880
)

app/bss/adapters/portaswitch/config.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ def decode_contacts_custom(cls, v: Union[List, str]) -> List[dict]:
5050
@field_validator("ALLOWED_ADDONS", mode='before')
5151
@classmethod
5252
def decode_allowed_addons(cls, v: Union[List, str]) -> List[str]:
53-
v = str(v)
54-
return [x.strip() for x in v.split(';')] if str(v) else v
53+
if isinstance(v, int):
54+
v = str(v)
55+
56+
return [x.strip() for x in v.split(';')] if isinstance(v, str) else v
5557

5658
model_config = {
5759
"env_prefix": "PORTASWITCH_",
@@ -65,9 +67,11 @@ class OTPSettings(BaseSettings):
6567

6668
@field_validator("IGNORE_ACCOUNTS", mode='before')
6769
@classmethod
68-
def decode_ignore_accounts(cls, v: str) -> List[str]:
69-
v = str(v)
70-
return [str(x) for x in v.split(';')] if v else v
70+
def decode_ignore_accounts(cls, v: Union[List, str]) -> List[str]:
71+
if isinstance(v, int):
72+
v = str(v)
73+
74+
return [x.strip() for x in v.split(';')] if isinstance(v, str) else v
7175

7276
model_config = {
7377
"env_prefix": "OTP_",

0 commit comments

Comments
 (0)