diff --git a/api/blueprints/mail/list.py b/api/blueprints/mail/list.py index 3827b9e..1ae4250 100644 --- a/api/blueprints/mail/list.py +++ b/api/blueprints/mail/list.py @@ -165,9 +165,9 @@ async def list_mails_for_account_id( ) # Get the mail: - mails_list = await current_app.mail_retrieve_model.list_for_account_identifier( + mails_list = await current_app.mail_retrieve_model.list_for_token_id( mongo_conn = current_app.data_mongo, - account_identifier = inbound_data.accountId, + token_id = inbound_data.tokenId, limit = inbound_data.count, skip = inbound_data.fromCount ) diff --git a/api/blueprints/mail/oauth_callback.py b/api/blueprints/mail/oauth_callback.py index 834aca1..310bbee 100644 --- a/api/blueprints/mail/oauth_callback.py +++ b/api/blueprints/mail/oauth_callback.py @@ -159,7 +159,7 @@ async def handle_gmail_callback() -> render_template: # if they don't match, we reject the authorization: placeholder_token = await current_app.mail_oauth_model.get_token( mongo_conn = current_app.data_mongo, - account_identifier = g.inbound_data["state"] + token_id = g.inbound_data["state"] ) if ( (not placeholder_token) or @@ -210,7 +210,7 @@ async def handle_gmail_callback() -> render_template: db_conn = current_app.sql_writer, mongo_conn = current_app.data_mongo, session_token = g.inbound_headers.get("X-Session-Token"), - account_identifier = g.inbound_data["state"], + token_id = g.inbound_data["state"], email_id = tokens.email, token = tokens.model_dump() ) diff --git a/api/blueprints/mail/oauth_request.py b/api/blueprints/mail/oauth_request.py index f4d8658..5eeda08 100644 --- a/api/blueprints/mail/oauth_request.py +++ b/api/blueprints/mail/oauth_request.py @@ -175,7 +175,7 @@ async def request_oauth_authorization_url( # ┛ # Make a user identifier from the session info: - user_identifier = await current_app.mail_oauth_model.get_account_identifier( + token_id = await current_app.mail_oauth_model.get_token_id( db_conn = current_app.sql_writer, mongo_conn = current_app.data_mongo, session_token = inbound_headers["X-Session-Token"], @@ -184,13 +184,13 @@ async def request_oauth_authorization_url( service_client = inbound_data.mailClient, auth_type = "oauth" ) - if user_identifier is None: + if token_id is None: return ResponseModel( status_code = StatusCodes.FAILED, http_code = HttpCodes.INTERNAL_SERVER_ERROR, - message = "failed to generate user identifier" + message = "failed to generate token id" ) - user_identifier = str(user_identifier) + token_id = str(token_id) # ┏┓ ┏┓┳┳┓ •┓ # ┣ ┏┓┏┓ ┃┓┃┃┃┏┓┓┃ @@ -201,7 +201,7 @@ async def request_oauth_authorization_url( # Get the authorization URL: auth_url = await current_app.gmail_client.get_authorization_url( scopes = SCOPES_GMAIL_MAIL_MANAGEMENT, - state = user_identifier, + state = token_id, access_type = "offline", approval_prompt = "force", include_granted_scopes = "true", diff --git a/api/blueprints/mail/retrieve.py b/api/blueprints/mail/retrieve.py index 59bf04a..f210225 100644 --- a/api/blueprints/mail/retrieve.py +++ b/api/blueprints/mail/retrieve.py @@ -166,7 +166,7 @@ async def get_one_mail( # Get the mail: mail_data = await current_app.mail_retrieve_model.get_mail( mongo_conn = current_app.data_mongo, - mail_identifier = inbound_data.mailId + mail_id = inbound_data.mailId ) # Done here: diff --git a/api/blueprints/mail/sync.py b/api/blueprints/mail/sync.py index 04487ea..09add59 100644 --- a/api/blueprints/mail/sync.py +++ b/api/blueprints/mail/sync.py @@ -146,7 +146,7 @@ async def sync_mails( return await current_app.mail_sync_model.sync( session_token = inbound_headers["X-Session-Token"], mongo_conn = mongo_conn, - account_identifier = inbound_data.accountId, + token_id = inbound_data.tokenId, llm = llm, force_sync = inbound_data.forceSync, start_date = inbound_data.startDate, diff --git a/api/blueprints/sms/__init__.py b/api/blueprints/sms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/blueprints/sms/auth.py b/api/blueprints/sms/auth.py new file mode 100644 index 0000000..8f5bb28 --- /dev/null +++ b/api/blueprints/sms/auth.py @@ -0,0 +1,236 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 5th Dec., 2024 + + OBJECTIVE: + + To receive auth details for various SMS client APIs. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + + NOTES: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For using Quart: +from quart import Blueprint, current_app, request + +# My utils: +from utils_v2.string import json +from utils_v2.api.codes import StatusCodes, HttpCodes +from utils_v2.api.response import ResponseModel +from utils_v2.api.async_quart import ( + set_api_version, + read_input, + get_session_info, + log_request_to_mongo, + log_chain_to_mongo, + should_not_be_under_maintenance, + only_whitelisted_ips, + limit_rate, + validate_input, + handle_cancelled_request +) + +# SMS-related utils: +from utils_v2.sms.nimbus.async_nimbus import AsyncNimbusSMS +from utils_v2.sms.savvy_bulk_sms.async_savvy_bulk_sms import AsyncSavvyBulkSMS + +# Common: +from shared import constants + +# Data Models: +from models.data.mail.oauth import ( + OAuthMailAuthorizationRequestHeaders, + OAuthMailAuthorizationRequestData +) + +# For asynchronous activities: +import asyncio + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# Related to Quart: +sms_auth_bp = Blueprint("sms_auth", __name__) + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +@sms_auth_bp.record_once +def init(blueprint_setup_state): + + # This gets called when the blueprint is registered. + # Consider this to be a one-time setup for the whole blueprint: + pass + + +# --------------------------------------------------------------------------------------------------------------------- + + +@sms_auth_bp.route("/auth", methods = ["GET"]) +@set_api_version(api_version = "1.0.0") +@read_input(sanitize_headers = False, sanitize_data = False) +@get_session_info(key = "X-Session-Token", session_coro = "get_session") +@log_request_to_mongo( + attr_name = "logs_mongo", + project = constants.PROJECT_NAME, + log_type = constants.MODULE_NAME, + operation = "smsAuthApi", + log_input = True, + log_output = True, + sensitive_keys = ["sessionToken", "X-Session-Token"] +) +@log_chain_to_mongo(attr_name = "logs_mongo") +@should_not_be_under_maintenance(attr_name = "is_under_maintenance") +@validate_input( + header_validator = lambda x: OAuthMailAuthorizationRequestHeaders(**x).model_dump(), + data_validator = lambda x: OAuthMailAuthorizationRequestData(**x) +) +@handle_cancelled_request() +async def request_oauth_authorization_url( + inbound_headers: dict | OAuthMailAuthorizationRequestHeaders = None, + inbound_data: dict | OAuthMailAuthorizationRequestData = None, + inbound_files: dict = None, + **kwargs +): + + """ + Use this when requesting access to someone's GMail account. This API should be used from the UI. A button click + "Connect to GMail" should hit this API, which will generate a request to gain access to the user's GMail account. + When the URL is hit, it opens Google's own UI, and, when the user clicks "Continue", Google hits your 'redirect_url' + to inform you about the user's action. + :param inbound_headers: auto-extracted by the decorators. + :param inbound_data: auto-extracted by the decorators. + :param inbound_files: auto-extracted by the decorators. + :param kwargs: Any number of extra inputs supplied by the decorators. + :return: A standard response structure. + """ + + # ┏┓ + # ┃┃┏┓┏┓┏┓┏┓┏┓┏┏┓┏┏ + # ┣┛┛ ┗ ┣┛┛ ┗┛┗┗ ┛┛ + # ┛ + + # If the session token is invalid/expired: + if kwargs.get("session_info") is None: + return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.UNAUTHORIZED + ) + + # Start by assuming failure: + auth_url = None + + # ┳ ┓ •┏ ┳┳ + # ┃┏┫┏┓┏┓╋┓╋┓┏ ┃┃┏┏┓┏┓ + # ┻┗┻┗ ┛┗┗┗┛┗┫ ┗┛┛┗ ┛ + # ┛ + + # Make a user identifier from the session info: + user_identifier = await current_app.mail_oauth_model.get_account_identifier( + db_conn = current_app.sql_writer, + mongo_conn = current_app.data_mongo, + session_token = inbound_headers["X-Session-Token"], + user_info = kwargs["session_info"], + email_id = inbound_data.mailId, + service_client = inbound_data.mailClient, + auth_type = "oauth" + ) + if user_identifier is None: + return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.INTERNAL_SERVER_ERROR, + message = "failed to generate user identifier" + ) + user_identifier = str(user_identifier) + + # ┏┓ ┏┓┳┳┓ •┓ + # ┣ ┏┓┏┓ ┃┓┃┃┃┏┓┓┃ + # ┻ ┗┛┛ ┗┛┛ ┗┗┻┗┗ + + if inbound_data.mailClient == "gmail": + + # Get the authorization URL: + auth_url = await current_app.gmail_client.get_authorization_url( + scopes = SCOPES_GMAIL_MAIL_MANAGEMENT, + state = user_identifier, + access_type = "offline", + approval_prompt = "force", + include_granted_scopes = "true", + user_email = inbound_data.mailId + ) + + # ┳┓ + # ┣┫┏┓┏┏┓┏┓┏┓┏┏┓ + # ┛┗┗ ┛┣┛┗┛┛┗┛┗ + # ┛ + + # Done here: + return ResponseModel( + status_code = StatusCodes.OK if auth_url else StatusCodes.FAILED, + http_code = HttpCodes.SUCCESS if auth_url else HttpCodes.INTERNAL_SERVER_ERROR, + data = { + "mailClient": inbound_data.mailClient, + "authorizationUrl": auth_url + } + ) + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/behaviour/mail/oauth_v2.py b/models/behaviour/mail/oauth_v2.py index c8a2e6d..6131e53 100644 --- a/models/behaviour/mail/oauth_v2.py +++ b/models/behaviour/mail/oauth_v2.py @@ -95,7 +95,7 @@ class MailOAuthModel(BaseModel): AUTH_COLLECTION = "_authTokens" - async def get_account_identifier( + async def get_token_id( self, db_conn: AsyncMySQL, mongo_conn: AsyncMongo, @@ -186,7 +186,7 @@ class MailOAuthModel(BaseModel): self, db_conn: AsyncMySQL, mongo_conn: AsyncMongo, - account_identifier: ObjectId | str, + token_id: ObjectId | str, email_id: str, token: dict, session_token: str = None @@ -198,9 +198,9 @@ class MailOAuthModel(BaseModel): ALSO. :param db_conn: The database connection (MariaDB) to use to perform the action. :param mongo_conn: The database connection (MongoDB) to use to perform the action. - :param account_identifier: The identifier granted by the 'get_account_identifier' method. + :param token_id: The identifier granted by the 'get_token_id' method. :param email_id: The e-mail id that the user tried to connect to your service. This should match the e-mail id - the user claimed he wants to connect when he used 'get_account_identifier'. + the user claimed he wants to connect when he used 'get_token_identifier'. :param token: The token granted by the third-party service. :param session_token: The session token of the user who requested this service. :return: True if saved, False if failed. @@ -216,7 +216,7 @@ class MailOAuthModel(BaseModel): mongo_json = await mongo_conn.find_one_and_update( collection = MailOAuthModel.AUTH_COLLECTION, filter = mongo_conn.dict_to_dot_notation({ - "_id": ObjectId(account_identifier), + "_id": ObjectId(token_id), "clientUserId": { "email": email_id } @@ -225,9 +225,18 @@ class MailOAuthModel(BaseModel): "$set": { "token": token, "lastRefreshTs": request_ts, - }, - "$setOnInsert": { - "firstRefreshTs": request_ts, + "firstRefreshTs": { + "$cond": { + "if": { + "$or": [ + {"$eq": ["$fieldName", None]}, + {"$eq": [{"$type": "$fieldName"}, "missing"]} + ] + }, + "then": request_ts, + "else": "$firstRefreshTs" + } + } } }, projection = {"token": False}, @@ -252,7 +261,7 @@ class MailOAuthModel(BaseModel): "Set Token", # .................................................. 'p_last_action' token["displayName"], # ......................................... 'p_display_name' token["displayPictureUrl"], # ................................... 'p_display_picture' - account_identifier, # ........................................... 'p_token_id' + token_id, # ..................................................... 'p_token_id' json.to_string(python_data = token_notes, no_space = True), # ... 'p_notes' mongo_json["user"]["userId"] # .................................. 'p_created_by' ), @@ -266,14 +275,14 @@ class MailOAuthModel(BaseModel): async def get_token( self, mongo_conn: AsyncMongo, - account_identifier: ObjectId | str = None, + token_id: ObjectId | str = None, **kwargs ) -> dict | None: """ To retrieve stored tokens from the database. :param mongo_conn: The database connection (MongoDB) to use to perform the action. - :param account_identifier: The identifier granted by the 'account_identifier' method. + :param token_id: The identifier granted by the 'get_token_id' method. :param kwargs: Any set of key-value pairs to build custom search criteria. This could be things like the user info, the client, the type of authentication used, or even the kind of service. :return: The retrieved record that has the token, and information about the service and client if found, else @@ -282,7 +291,7 @@ class MailOAuthModel(BaseModel): # Build the filter: filter_json = {k: v for k, v in kwargs.items()} - if account_identifier: filter_json["_id"] = ObjectId(account_identifier) + if token_id: filter_json["_id"] = ObjectId(token_id) # If there is no search criteria, we exit with failure: if not filter_json: return None diff --git a/models/behaviour/mail/retrieve.py b/models/behaviour/mail/retrieve.py index c68f956..cbf5dcc 100644 --- a/models/behaviour/mail/retrieve.py +++ b/models/behaviour/mail/retrieve.py @@ -98,20 +98,20 @@ class MailRetrieveModel(BaseModel): async def get_mail( self, mongo_conn: AsyncMongo, - mail_identifier: str | ObjectId + mail_id: str | ObjectId ): """ Retrieves one full mail from the database. :param mongo_conn: The instance of the database connector to use to get the mail's data. - :param mail_identifier: The '_id' of the document that holds the mail. + :param mail_id: The '_id' of the document that holds the mail. :return: Either the JSON that describes the mail or None if such a mail does not exist. """ # Get the data from the database: mail_data = await mongo_conn.find_one( collection = self.MAIL_COLLECTION, - filter = {"_id": ObjectId(mail_identifier)}, + filter = {"_id": ObjectId(mail_id)}, projection = { "_id": True, "serviceType": True, @@ -141,10 +141,10 @@ class MailRetrieveModel(BaseModel): # Done here: return mail_data - async def list_for_account_identifier( + async def list_for_token_id( self, mongo_conn: AsyncMongo, - account_identifier: str | ObjectId, + token_id: str | ObjectId, limit: int = 25, skip: int = 0 ): @@ -152,7 +152,7 @@ class MailRetrieveModel(BaseModel): """ To enlist mails for one account. :param mongo_conn: The instance of the database connector to use to get the mail's data. - :param account_identifier: The id of the document in the database that holds the tokens to access the account. + :param token_id: The id of the document in the database that holds the tokens to access the account. :param limit: How many records to fetch. :param skip: How many initial records to skip. useful for pagination. :return: Either the JSON that describes the mails or None if something failed. @@ -161,7 +161,7 @@ class MailRetrieveModel(BaseModel): # Get the data from the database: mails_list = await mongo_conn.find_many( collection = self.MAIL_COLLECTION, - filter = {"accountId": ObjectId(account_identifier)}, + filter = {"tokenId": ObjectId(token_id)}, projection = { "_id": True, "serviceType": True, diff --git a/models/behaviour/mail/sync_v2.py b/models/behaviour/mail/sync_v2.py index 8b2d4b1..b6031ed 100644 --- a/models/behaviour/mail/sync_v2.py +++ b/models/behaviour/mail/sync_v2.py @@ -344,7 +344,7 @@ class MailSyncModel(BaseModel): self, session_token: str, mongo_conn: AsyncMongo, - account_identifier: ObjectId, + token_id: ObjectId, mail_client: AsyncGMailClient, tokens: GoogleAuthTokens, llm: ChatOpenAI = None, @@ -358,7 +358,7 @@ class MailSyncModel(BaseModel): Sync many mails from GMail in one shot. :param session_token: The session token of the uer who is trying to upload this file. :param mongo_conn: The instance of the connection to the database to use. - :param account_identifier: The id of the document in the database that holds the tokens to access the account. + :param token_id: The id of the document in the database that holds the tokens to access the account. Needed only for refreshing the tokens and saving them. :param mail_client: The instance of the mail client to use to perform the action. :param tokens: The tokens to use to fetch the mails. @@ -383,7 +383,7 @@ class MailSyncModel(BaseModel): if tokens_refreshed: await current_app.mail_oauth_model.set_token( db_conn = current_app.sql_writer, mongo_conn = mongo_conn, - account_identifier = account_identifier, + token_id = token_id, email_id = tokens.email, token = tokens, session_token = session_token @@ -439,7 +439,7 @@ class MailSyncModel(BaseModel): }, replacement = { "version": "1.0.0", - "accountId": ObjectId(account_identifier), + "tokenId": ObjectId(token_id), "serviceType": "email", "client": "gmail", "payload": result.mailMessage @@ -461,8 +461,6 @@ class MailSyncModel(BaseModel): message_ids = [v["id"] for v in messages_list.values()], add_label_ids = [tokens.labels.get("TCAOFF", {}).get("id")] ) - print(client_response) - print([tokens.labels.get("TCAOFF")]) except Exception as exception: self._printer(exception) @@ -474,7 +472,7 @@ class MailSyncModel(BaseModel): self, session_token: str, mongo_conn: AsyncMongo, - account_identifier: ObjectId, + token_id: ObjectId, llm: ChatOpenAI = None, force_sync: bool = False, start_date: datetime.datetime = None, @@ -487,7 +485,7 @@ class MailSyncModel(BaseModel): route the request to the appropriate clients. :param session_token: The session token of the uer who is trying to upload this file. :param mongo_conn: The instance of the connection to the database to use. - :param account_identifier: The id of the document in the database that holds the tokens to access the account. + :param token_id: The id of the document in the database that holds the tokens to access the account. Needed only for refreshing the tokens and saving them. :param llm: The instance of the LLM to use to summarize the mail's content. :param force_sync: Whether you would like to forcefully re-sync the mail even if it is already present in the @@ -508,12 +506,12 @@ class MailSyncModel(BaseModel): # We first load the authorization tokens: auth_json = await current_app.mail_oauth_model.get_token( mongo_conn = mongo_conn, - account_identifier = account_identifier, + token_id = token_id, ) # If we failed to load the authorization tokens: if not auth_json: - sync_results.message = f"no such account identifier '{account_identifier}'" + sync_results.message = f"no such token id '{token_id}'" return sync_results # ┏┓ ┏┓┳┳┓ •┓ @@ -524,7 +522,7 @@ class MailSyncModel(BaseModel): return await self.__sync_many_gmail( session_token = session_token, mongo_conn = mongo_conn, - account_identifier = account_identifier, + token_id = token_id, mail_client = current_app.gmail_client, tokens = GoogleAuthTokens(**auth_json["token"]), llm = llm, diff --git a/models/data/mail/list.py b/models/data/mail/list.py index e7b9805..79cb935 100644 --- a/models/data/mail/list.py +++ b/models/data/mail/list.py @@ -101,7 +101,7 @@ class MailListRequestHeaders(BaseModel): class MailListByAccountIdRequestData(BaseModel): - accountId: str = Field( + tokenId: str = Field( description = "the account identifier (Mongo ObjectId) granted by 'MailOAuthModel.get_account_identifier'", frozen = True ) diff --git a/models/data/mail/sync.py b/models/data/mail/sync.py index ee3965c..99c858e 100644 --- a/models/data/mail/sync.py +++ b/models/data/mail/sync.py @@ -101,7 +101,7 @@ class MailSyncRequestHeaders(BaseModel): class MailSyncRequestData(BaseModel): - accountId: str = Field( + tokenId: str = Field( description = "the account identifier (Mongo ObjectId) granted by 'MailOAuthModel.get_account_identifier'", frozen = True ) diff --git a/models/data/sms/__init__.py b/models/data/sms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/data/sms/auth.py b/models/data/sms/auth.py new file mode 100644 index 0000000..1f190a6 --- /dev/null +++ b/models/data/sms/auth.py @@ -0,0 +1,188 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 5th Dec., 2024. + + OBJECTIVE: + + To provide a structure to receive auth details of various SMS providers. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For making data behaviour_models: +from pydantic import BaseModel, Field, field_validator, PastDatetime +from typing import Optional, Literal, Union + +# My utils: +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with date and time: +import datetime + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# RegEx Patterns: +REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +class NimbusSMSIndiaAuth(BaseModel): + + entityId: str = Field( + description = "the entity id as registered with DLT", + frozen = True + ) + + senderId: str = Field( + description = "the 6-char code that you see in your SMS inbox", + frozen = True, + examples = ["HDFCBK", "NSESMS", "ZRODHA"] + ) + + userId: str = Field( + description = "the 6-digit id that Nimbus has assigned to you", + frozen = True + ) + + apiKey: str = Field( + description = "the key generated through Nimbus's portal", + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# --------------------------------------------------------------------------------------------------------------------- + + +class SavvyBulkSMSKenyaAuth(BaseModel): + + apiKey: str = Field( + description = "the key generated through Savvy's portal", + frozen = True + ) + + partnerId: str = Field( + description = "the key generated through Savvy's portal", + frozen = True + ) + + shortCode: str = Field( + description = "your short code with Savvy", + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# --------------------------------------------------------------------------------------------------------------------- + + +class SMSAuthRequestHeaders(BaseModel): + + sessionToken: str = Field( + description = "the session token of the user who is requesting the service", + pattern = REGEX_SESSION_TOKEN, + frozen = True, + alias = "X-Session-Token" + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "allow" + + def model_dump(self, *args, **kwargs): + return super().model_dump(*args, by_alias = True, **kwargs) + + +# --------------------------------------------------------------------------------------------------------------------- + + +class SMSAuthRequestData(BaseModel): + + client: Union[NimbusSMSIndiaAuth, SavvyBulkSMSKenyaAuth] + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/utils_v2/sms/nimbus/async_nimbus.py b/utils_v2/sms/nimbus/async_nimbus.py index a1feeac..7776f9e 100644 --- a/utils_v2/sms/nimbus/async_nimbus.py +++ b/utils_v2/sms/nimbus/async_nimbus.py @@ -84,10 +84,10 @@ class AsyncNimbusSMS: def __init__( self, - entity_id, - sender_id, - user_id, - api_key, + entity_id: str, + sender_id: str, + user_id: str, + api_key: str, http_client: httpx.AsyncClient = None, debug = True, debug_prefix = "Nimbus SMS | ",