diff --git a/api/blueprints/finstitutions/trading/symbols/list.py b/api/blueprints/finstitutions/trading/symbols/list.py index dd040b4..5cf3565 100644 --- a/api/blueprints/finstitutions/trading/symbols/list.py +++ b/api/blueprints/finstitutions/trading/symbols/list.py @@ -66,9 +66,9 @@ from shared import constants from models.core.auth_token import CoreAuthTokenModel from models.api.finstitutions.trading.symbols.list import ( TradingSymbolListRequestHeaders, - TradingSymbolListRequestData, - TradingSymbolListBrokerResponse + TradingSymbolListRequestData ) +from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse # To work with dat and time: import datetime diff --git a/api/blueprints/chat/__init__.py b/api/blueprints/message/__init__.py similarity index 100% rename from api/blueprints/chat/__init__.py rename to api/blueprints/message/__init__.py diff --git a/api/blueprints/mail/__init__.py b/api/blueprints/message/chat/__init__.py similarity index 100% rename from api/blueprints/mail/__init__.py rename to api/blueprints/message/chat/__init__.py diff --git a/api/blueprints/chat/auth.py b/api/blueprints/message/chat/auth.py similarity index 99% rename from api/blueprints/chat/auth.py rename to api/blueprints/message/chat/auth.py index 30e5cab..3948181 100644 --- a/api/blueprints/chat/auth.py +++ b/api/blueprints/message/chat/auth.py @@ -60,7 +60,7 @@ from utils_v2.api.async_quart import ( ) # Data Models: -from models.api.chat.auth import ChatAuthRequestHeaders, ChatAuthRequestData +from models.api.message.chat.auth import ChatAuthRequestHeaders, ChatAuthRequestData from models.core.auth_token import CoreAuthTokenModel # Common: diff --git a/api/blueprints/chat/webhook.py b/api/blueprints/message/chat/webhook.py similarity index 100% rename from api/blueprints/chat/webhook.py rename to api/blueprints/message/chat/webhook.py diff --git a/api/blueprints/mail/oauth/__init__.py b/api/blueprints/message/mail/__init__.py similarity index 100% rename from api/blueprints/mail/oauth/__init__.py rename to api/blueprints/message/mail/__init__.py diff --git a/api/blueprints/mail/retrieve/__init__.py b/api/blueprints/message/mail/oauth/__init__.py similarity index 100% rename from api/blueprints/mail/retrieve/__init__.py rename to api/blueprints/message/mail/oauth/__init__.py diff --git a/api/blueprints/mail/oauth/callback.py b/api/blueprints/message/mail/oauth/callback.py similarity index 100% rename from api/blueprints/mail/oauth/callback.py rename to api/blueprints/message/mail/oauth/callback.py diff --git a/api/blueprints/message/mail/oauth/callback_v2.py b/api/blueprints/message/mail/oauth/callback_v2.py new file mode 100644 index 0000000..3ef1692 --- /dev/null +++ b/api/blueprints/message/mail/oauth/callback_v2.py @@ -0,0 +1,275 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 16th Jan., 2025. + + OBJECTIVE: + + To receive callbacks (webhooks). + + 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, g, request, render_template + +# My utils: +from utils_v2.string import json +from utils_v2.logging.context import AsyncLoggerContext +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 +) + +# GMail-related utils: +from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAGEMENT + +# Data Models: +from models.core.auth_token import CoreAuthTokenModel +from models.message.mail.oauth import OAuthMailHandleCallbackResponse + +# Common: +from shared import constants + +# For asynchronous activities: +import asyncio + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# Related to Quart: +mail_oauth_callback_bp = Blueprint("mail_cb", __name__) + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +@mail_oauth_callback_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 + + +# --------------------------------------------------------------------------------------------------------------------- + + +@AsyncLoggerContext.log_it( + api_version = "1.0.0", + project = constants.PROJECT_NAME, + log_type = constants.MODULE_NAME, + operation = "mailOAuthClbk", + log_input = 2, + log_output = 1, + sensitive_keys = ["sessionToken", "X-Session-Token"] +) +async def handle_mail_callback( + client_controller, + client_connector, + request_url: str, + inbound_data: dict, +) -> OAuthMailHandleCallbackResponse: + + """ + This function has been kept separate only for convenience of logging. + :param client_controller: The mail controller instance. + :param client_connector: The instance of the third-party client to send to the mail controller. + :param request_url: The full request URL that came in. + :param inbound_data: The data received in the request. + :return: The client's response. + """ + + return await client_controller.handle_authorization_callback( + sql_conn = current_app.sql_writer, + mongo_data_conn = current_app.data_mongo, + mail_client = client_connector, + request_url = request_url, + inbound_data = inbound_data, + session_token = None + ) + + +# --------------------------------------------------------------------------------------------------------------------- + + +@mail_oauth_callback_bp.route("/callback/", methods = ["POST", "GET"]) +@set_api_version(api_version = "1.0.0") +@read_input(sanitize_headers = False, sanitize_data = False) +@log_request_to_mongo( + attr_name = "logs_mongo", + project = constants.PROJECT_NAME, + log_type = constants.MODULE_NAME, + operation = "mailOAuthClbkApi", + log_input = True, + log_output = True, + sensitive_keys = None +) +@log_chain_to_mongo(attr_name = "logs_mongo") +@should_not_be_under_maintenance(attr_name = "is_under_maintenance") +@handle_cancelled_request() +async def mail_auth_callback( + mail_client: str = None, + inbound_headers: dict = None, + inbound_data: dict = None, + inbound_files: dict = None, + **kwargs +): + + """ + This is the callback received when authorizing someone's mail client. + :param mail_client: The mail company/brand that you want the authorization from. + :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. + """ + + # ┳┓ ┳┳┓ •┓ ┏┓┓• + # ┣┫┏┓┓┏╋┏┓ ╋┏┓ ┃┃┃┏┓┓┃ ┃ ┃┓┏┓┏┓╋ + # ┛┗┗┛┗┻┗┗ ┗┗┛ ┛ ┗┗┻┗┗ ┗┛┗┗┗ ┛┗┗ + + # Start by assuming failure: + client_controller = None + client_connector = None + client_response = None + exception = None + + # Figure out the client connector: + match mail_client: + case "gmail": client_controller, client_connector = current_app.gmail_controller, current_app.gmail_client + case _: client_controller, client_connector = None, None + + # Invoke the mail client: + if client_controller is not None and client_connector is not None: + try: client_response = await handle_mail_callback( + client_controller, + client_connector, + request_url = request.url, + inbound_data = inbound_data + ) + except Exception as excp: exception = excp + + # ┳┓ + # ┣┫┏┓┏┏┓┏┓┏┓┏┏┓ + # ┛┗┗ ┛┣┛┗┛┛┗┛┗ + # ┛ + + # Make the mail client a label: + mail_client = { + "gmail": "Gmail", + "outlook": "Outlook" + }.get(mail_client, mail_client) + + # If there was some exception: + if exception: return await render_template( + "/message/mail/oauth/oauth_failure_v2.html", + mail_client = mail_client, + failure_hint = ( + f"An internal server error occurred. " + f"Please use log-id '{kwargs.get('log_id')}' to check with the support team." + ) + ) + + # For an invalid client: + if client_controller is None or client_connector is None: + return await render_template( + "/message/mail/oauth/oauth_failure_v2.html", + mail_client = mail_client, + failure_hint = ( + f"Invalid client '{mail_client}' selected. " + f"Please use log-id '{kwargs.get('log_id')}' to check with the support team." + ) + ) + + # For a valid client whose authorization was denied/cancelled: + if client_response.action in ["denied", "cancelled"]: + return await render_template( + "/message/mail/oauth/oauth_cancelled_v2.html", + mail_client = mail_client + ) + + # For successful authorization: + if client_response.success: + return await render_template( + "/message/mail/oauth/oauth_success_v2.html", + mail_client = mail_client + ) + + # For failed authorization: + return await render_template( + "/message/mail/oauth/oauth_failure_v2.html", + mail_client = mail_client.title(), + failure_hint = f"Unknown error. Please use log-id '{kwargs.get('log_id')}' to check with the support team." + ) + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/api/blueprints/mail/oauth/request.py b/api/blueprints/message/mail/oauth/request.py similarity index 99% rename from api/blueprints/mail/oauth/request.py rename to api/blueprints/message/mail/oauth/request.py index 8eee5a7..aaf8e67 100644 --- a/api/blueprints/mail/oauth/request.py +++ b/api/blueprints/message/mail/oauth/request.py @@ -67,7 +67,7 @@ from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAG from shared import constants # Data Models: -from models.api.mail.oauth import ( +from models.api.message.mail.oauth import ( OAuthMailAuthorizationRequestHeaders, OAuthMailAuthorizationRequestData ) diff --git a/api/blueprints/message/mail/oauth/request_v2.py b/api/blueprints/message/mail/oauth/request_v2.py new file mode 100644 index 0000000..0d7a0dd --- /dev/null +++ b/api/blueprints/message/mail/oauth/request_v2.py @@ -0,0 +1,229 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 16th jan., 2025. + + OBJECTIVE: + + To receive authorization requests (OAuth2.0) for various mail providers and accordingly respond with the + authorization request URLs. + + 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 +) + +# GMail-related utils: +from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAGEMENT + +# Common: +from shared import constants + +# Data Models: +from models.core.user import CoreUserInfoModel +from models.api.message.mail.oauth import ( + OAuthMailAuthorizationRequestHeaders, + OAuthMailAuthorizationRequestData +) +from models.core.auth_token import CoreAuthTokenModel + +# For asynchronous activities: +import asyncio + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# Related to Quart: +mail_oauth_request_bp = Blueprint("mail_oauth", __name__) + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +@mail_oauth_request_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 + + +# --------------------------------------------------------------------------------------------------------------------- + + +@mail_oauth_request_bp.route("/oauth", 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 = "mailOAuthUrlReqApi", + 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: + client_controller = None + client_connector = None + client_response = None + + # Figure out the client connector: + match inbound_data.mailClient: + case "gmail": client_controller, client_connector = current_app.gmail_controller, current_app.gmail_client + case _: client_controller, client_connector = None, None + + # If a client connector was matched: + if client_controller is not None and client_connector is not None: + client_response = await client_controller.get_authorization_url( + sql_conn = current_app.sql_writer, + mongo_data_conn = current_app.data_mongo, + mail_client = client_connector, + user_info = CoreUserInfoModel(**kwargs["session_info"]), + inbound_data = inbound_data, + session_token = inbound_headers["X-Session-Token"] + ) + + # ┳┓ + # ┣┫┏┓┏┏┓┏┓┏┓┏┏┓ + # ┛┗┗ ┛┣┛┗┛┛┗┛┗ + # ┛ + + # Unknown client: + if client_controller is None or client_connector is None: return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.BAD_REQUEST, + message = f"Unknown/unimplemented client '{inbound_data.mailClient}'." + ) + + # Known client (could be a success or a failure): + return ResponseModel( + status_code = StatusCodes.OK if client_response.success else StatusCodes.FAILED, + http_code = HttpCodes.SUCCESS if client_response.success else HttpCodes.INTERNAL_SERVER_ERROR, + message = client_response.message, + data = { + "client": inbound_data.mailClient, + "authorizationUrl": client_response.url + } if client_response.success else None + ) + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/api/blueprints/mail/send/__init__.py b/api/blueprints/message/mail/retrieve/__init__.py similarity index 100% rename from api/blueprints/mail/send/__init__.py rename to api/blueprints/message/mail/retrieve/__init__.py diff --git a/api/blueprints/mail/retrieve/get.py b/api/blueprints/message/mail/retrieve/get.py similarity index 98% rename from api/blueprints/mail/retrieve/get.py rename to api/blueprints/message/mail/retrieve/get.py index ab8bc5b..ecd2092 100644 --- a/api/blueprints/mail/retrieve/get.py +++ b/api/blueprints/message/mail/retrieve/get.py @@ -69,7 +69,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens from shared import constants # Data Models: -from models.api.mail.get import MailGetRequestHeaders, MailGetRequestData +from models.api.message.mail.get import MailGetRequestHeaders, MailGetRequestData from models.core.user import CoreUserInfoModel # To work with datatypes: diff --git a/api/blueprints/mail/retrieve/list.py b/api/blueprints/message/mail/retrieve/list.py similarity index 98% rename from api/blueprints/mail/retrieve/list.py rename to api/blueprints/message/mail/retrieve/list.py index 38f8f72..b386915 100644 --- a/api/blueprints/mail/retrieve/list.py +++ b/api/blueprints/message/mail/retrieve/list.py @@ -70,7 +70,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens from shared import constants # Data Models: -from models.api.mail.list import MailListRequestHeaders, MailListRequestData +from models.api.message.mail.list import MailListRequestHeaders, MailListRequestData # To work with datatypes: from typing import Literal diff --git a/api/blueprints/mail/sync/__init__.py b/api/blueprints/message/mail/send/__init__.py similarity index 100% rename from api/blueprints/mail/sync/__init__.py rename to api/blueprints/message/mail/send/__init__.py diff --git a/api/blueprints/mail/send/send.py b/api/blueprints/message/mail/send/send.py similarity index 98% rename from api/blueprints/mail/send/send.py rename to api/blueprints/message/mail/send/send.py index 1d93156..7ee201c 100644 --- a/api/blueprints/mail/send/send.py +++ b/api/blueprints/message/mail/send/send.py @@ -69,7 +69,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens from shared import constants # Data Models: -from models.api.mail.send import MailSendRequestHeaders, MailSendRequestData +from models.api.message.mail.send import MailSendRequestHeaders, MailSendRequestData from models.core.user import CoreUserInfoModel # To work with datatypes: diff --git a/api/blueprints/mail/tags/__init__.py b/api/blueprints/message/mail/sync/__init__.py similarity index 100% rename from api/blueprints/mail/tags/__init__.py rename to api/blueprints/message/mail/sync/__init__.py diff --git a/api/blueprints/mail/sync/sync_v2.py b/api/blueprints/message/mail/sync/sync_v2.py similarity index 98% rename from api/blueprints/mail/sync/sync_v2.py rename to api/blueprints/message/mail/sync/sync_v2.py index 2277c7f..f1c93d0 100644 --- a/api/blueprints/mail/sync/sync_v2.py +++ b/api/blueprints/message/mail/sync/sync_v2.py @@ -71,8 +71,8 @@ from shared import constants # Data Models: from models.core.user import CoreUserInfoModel -from models.api.mail.sync import MailSyncRequestHeaders, MailSyncRequestData -from models.api.mail.sync import MailSyncOneResult, MailSyncManyResults +from models.api.message.mail.sync import MailSyncRequestHeaders, MailSyncRequestData +from models.message.mail.sync import MailSyncOneResult, MailSyncManyResults # To work with datatypes: from typing import Literal diff --git a/api/blueprints/sms/__init__.py b/api/blueprints/message/mail/tags/__init__.py similarity index 100% rename from api/blueprints/sms/__init__.py rename to api/blueprints/message/mail/tags/__init__.py diff --git a/api/blueprints/mail/tags/update.py b/api/blueprints/message/mail/tags/update.py similarity index 98% rename from api/blueprints/mail/tags/update.py rename to api/blueprints/message/mail/tags/update.py index a1040f2..217cd72 100644 --- a/api/blueprints/mail/tags/update.py +++ b/api/blueprints/message/mail/tags/update.py @@ -69,7 +69,7 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens from shared import constants # Data Models: -from models.api.mail.tags import MailUpdateTagsRequestHeaders, MailUpdateTagsRequestData +from models.api.message.mail.tags import MailUpdateTagsRequestHeaders, MailUpdateTagsRequestData from models.core.user import CoreUserInfoModel # To work with datatypes: diff --git a/models/api/chat/__init__.py b/api/blueprints/message/sms/__init__.py similarity index 100% rename from models/api/chat/__init__.py rename to api/blueprints/message/sms/__init__.py diff --git a/api/blueprints/sms/auth_v2.py b/api/blueprints/message/sms/auth_v2.py similarity index 99% rename from api/blueprints/sms/auth_v2.py rename to api/blueprints/message/sms/auth_v2.py index 11c49ab..457dd9b 100644 --- a/api/blueprints/sms/auth_v2.py +++ b/api/blueprints/message/sms/auth_v2.py @@ -63,7 +63,7 @@ from utils_v2.api.async_quart import ( from shared import constants # Data Models: -from models.api.sms.auth import SMSAuthRequestHeaders, SMSAuthRequestData +from models.api.message.sms.auth import SMSAuthRequestHeaders, SMSAuthRequestData from models.core.auth_token import CoreAuthTokenModel # For asynchronous activities: diff --git a/api/blueprints/sms/list.py b/api/blueprints/message/sms/list.py similarity index 99% rename from api/blueprints/sms/list.py rename to api/blueprints/message/sms/list.py index 4c6962f..dab5c38 100644 --- a/api/blueprints/sms/list.py +++ b/api/blueprints/message/sms/list.py @@ -64,7 +64,7 @@ from shared import constants # Data Models: from models.core.user import CoreUserInfoModel -from models.api.sms.list import SMSListRequestHeaders, SMSListRequestData +from models.api.message.sms.list import SMSListRequestHeaders, SMSListRequestData # Helpers: from api.helpers.user import token_check diff --git a/api/blueprints/sms/send_v2.py b/api/blueprints/message/sms/send_v2.py similarity index 98% rename from api/blueprints/sms/send_v2.py rename to api/blueprints/message/sms/send_v2.py index d575db9..12e550c 100644 --- a/api/blueprints/sms/send_v2.py +++ b/api/blueprints/message/sms/send_v2.py @@ -62,8 +62,8 @@ from utils_v2.api.async_quart import ( # Models: from models.core.auth_token import CoreAuthTokenModel -from models.api.sms.send import SMSSendRequestHeaders, SMSSendRequestData -from models.api.sms.send import ( +from models.api.message.sms.send import SMSSendRequestHeaders, SMSSendRequestData +from models.message.sms.send import ( NimbusSMSIndiaMessage, SavvyBulkSMSKenyaMessage, SMSSendManyResults diff --git a/api/blueprints/sms/tags.py b/api/blueprints/message/sms/tags.py similarity index 98% rename from api/blueprints/sms/tags.py rename to api/blueprints/message/sms/tags.py index ea8c075..db4283b 100644 --- a/api/blueprints/sms/tags.py +++ b/api/blueprints/message/sms/tags.py @@ -64,7 +64,7 @@ from shared import constants # Data Models: from models.core.user import CoreUserInfoModel -from models.api.sms.tags import SMSUpdateTagsRequestHeaders, SMSUpdateTagsRequestData +from models.api.message.sms.tags import SMSUpdateTagsRequestHeaders, SMSUpdateTagsRequestData # Helpers: from api.helpers.user import token_check diff --git a/api/main.py b/api/main.py index 6a19081..340ae9a 100644 --- a/api/main.py +++ b/api/main.py @@ -80,6 +80,8 @@ from controllers_v2.message.sms.all_sms import AllSMSController from controllers_v2.message.sms.nimbus_sms_india import NimbusSMSIndiaController from controllers_v2.message.sms.savvy_bulk_sms_kenya import SavvyBulkSMSKenyaController # --- +from controllers_v2.message.mail.gmail import GmailController +# --- from controllers_v2.message.chat.all_chat import AllChatController from controllers_v2.message.chat.whatsapp_nimbus import WhatsAppNimbusController # --- @@ -98,23 +100,23 @@ import httpx from icecream import IceCreamDebugger # Mail Blueprints: -from api.blueprints.mail.oauth.request import mail_oauth_request_bp -from api.blueprints.mail.oauth.callback import mail_oauth_callback_bp -from api.blueprints.mail.sync.sync_v2 import mail_sync_bp -from api.blueprints.mail.retrieve.list import mail_list_bp -from api.blueprints.mail.retrieve.get import mail_get_bp -from api.blueprints.mail.tags.update import mail_tags_update_bp -from api.blueprints.mail.send.send import mail_send_bp +from api.blueprints.message.mail.oauth.request_v2 import mail_oauth_request_bp +from api.blueprints.message.mail.oauth.callback_v2 import mail_oauth_callback_bp +from api.blueprints.message.mail.sync.sync_v2 import mail_sync_bp +from api.blueprints.message.mail.retrieve.list import mail_list_bp +from api.blueprints.message.mail.retrieve.get import mail_get_bp +from api.blueprints.message.mail.tags.update import mail_tags_update_bp +from api.blueprints.message.mail.send.send import mail_send_bp # SMS Blueprints: -from api.blueprints.sms.auth_v2 import sms_auth_bp -from api.blueprints.sms.send_v2 import sms_send_bp -from api.blueprints.sms.list import sms_list_bp -from api.blueprints.sms.tags import sms_update_tags_bp +from api.blueprints.message.sms.auth_v2 import sms_auth_bp +from api.blueprints.message.sms.send_v2 import sms_send_bp +from api.blueprints.message.sms.list import sms_list_bp +from api.blueprints.message.sms.tags import sms_update_tags_bp # Chat Blueprints: -from api.blueprints.chat.auth import chat_auth_bp -# from api.blueprints.chat.webhook import chat_webhook_bp +from api.blueprints.message.chat.auth import chat_auth_bp +# from api.blueprints.message.chat.webhook import chat_webhook_bp # Software Blueprints: from api.blueprints.software.auth import sw_auth_bp @@ -393,22 +395,22 @@ async def app_startup(**kwargs): # ┃ ┏┓┏┓┏┓ ┃┃┃┏┓┏┫┏┓┃┏ # ┗┛┗┛┛ ┗ ┛ ┗┗┛┗┻┗ ┗┛ - current_app.core_auth_token_controller = CoreAuthTokenController( - cache = current_app.module_cache, - alert_url = current_app.script_data["alerts"]["url"], - http_client = current_app.http_client, - debug = enable_debugging, - debug_prefix = "AuthToken (CM) | ", - debug_only_errors = True - ) - current_app.core_message_controller = CoreMessageController( - cache = current_app.module_cache, - alert_url = current_app.script_data["alerts"]["url"], - http_client = current_app.http_client, - debug = enable_debugging, - debug_prefix = "Message (CM) | ", - debug_only_errors = True - ) + # current_app.core_auth_token_controller = CoreAuthTokenController( + # cache = current_app.module_cache, + # alert_url = current_app.script_data["alerts"]["url"], + # http_client = current_app.http_client, + # debug = enable_debugging, + # debug_prefix = "AuthToken (CM) | ", + # debug_only_errors = True + # ) + # current_app.core_message_controller = CoreMessageController( + # cache = current_app.module_cache, + # alert_url = current_app.script_data["alerts"]["url"], + # http_client = current_app.http_client, + # debug = enable_debugging, + # debug_prefix = "Message (CM) | ", + # debug_only_errors = True + # ) # current_app.core_payment_controller = CorePaymentController( # cache = current_app.module_cache, # alert_url = current_app.script_data["alerts"]["url"], @@ -458,6 +460,14 @@ async def app_startup(**kwargs): debug = enable_debugging ) + # Messages / Mail Controllers: + current_app.gmail_controller = GmailController( + cache = current_app.module_cache, + http_client = current_app.http_client, + alert_url = current_app.script_data["alerts"]["url"], + debug = enable_debugging + ) + # Messages / Chat Controllers: current_app.chat_controller = AllChatController( cache = current_app.module_cache, diff --git a/controllers/api/mail.py b/controllers/api/mail.py index d522a56..2a05fca 100644 --- a/controllers/api/mail.py +++ b/controllers/api/mail.py @@ -50,8 +50,9 @@ from controllers.base import BaseModel from models.core.user import CoreUserInfoModel from models.core.auth_token import CoreAuthTokenModel from models.core.message import CoreMessageModel -from models.api.mail.sync import MailSyncOneResult, MailSyncManyResults, MailSendOneResult -from models.api.mail.send import MailSendRequestData +from models.message.mail.sync import MailSyncOneResult, MailSyncManyResults +from models.message.mail.send import MailSendOneResult +from models.api.message.mail.send import MailSendRequestData # Mail Clients: from utils_v2.goog.controllers.gmail.gmail_client import AsyncGMailClient diff --git a/controllers_v2/finstitutions/trading/all_trading.py b/controllers_v2/finstitutions/trading/all_trading.py index 557a36a..1b4534b 100644 --- a/controllers_v2/finstitutions/trading/all_trading.py +++ b/controllers_v2/finstitutions/trading/all_trading.py @@ -45,7 +45,8 @@ from controllers_v2.finstitutions.trading.base import TradingController # Models: from models.core.auth_token import CoreAuthTokenModel -from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData, TradingSymbolListBrokerResponse +from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData +from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with datatypes: diff --git a/controllers_v2/finstitutions/trading/base.py b/controllers_v2/finstitutions/trading/base.py index 109b838..0ce0dd1 100644 --- a/controllers_v2/finstitutions/trading/base.py +++ b/controllers_v2/finstitutions/trading/base.py @@ -45,7 +45,8 @@ from controllers_v2.core.auth_token import CoreAuthTokenController # Models: from models.core.auth_token import CoreAuthTokenModel -from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData, TradingSymbolListBrokerResponse +from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData +from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with datatypes: diff --git a/controllers_v2/finstitutions/trading/icici_breeze.py b/controllers_v2/finstitutions/trading/icici_breeze.py index 133d651..b06a16f 100644 --- a/controllers_v2/finstitutions/trading/icici_breeze.py +++ b/controllers_v2/finstitutions/trading/icici_breeze.py @@ -47,11 +47,8 @@ from controllers_v2.finstitutions.trading.base import TradingController # Models: from models.core.auth_token import CoreAuthTokenModel from utils_v2.trading.icici_breeze.models.auth_tokens import ICICIBreezeAuthTokens -from models.api.finstitutions.trading.symbols.list import ( - TradingSymbolListRequestData, - TradingSymbolListBrokerResponse, - TradingSymbol -) +from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData +from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse, TradingSymbol from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with MongoDB: diff --git a/controllers_v2/finstitutions/trading/paper_trading.py b/controllers_v2/finstitutions/trading/paper_trading.py index 68117f8..6d8a094 100644 --- a/controllers_v2/finstitutions/trading/paper_trading.py +++ b/controllers_v2/finstitutions/trading/paper_trading.py @@ -47,11 +47,8 @@ from controllers_v2.finstitutions.trading.base import TradingController # Models: from models.core.auth_token import CoreAuthTokenModel from utils_v2.trading.zerodha_kite.models.auth_tokens import ZerodhaKiteAuthTokens -from models.api.finstitutions.trading.symbols.list import ( - TradingSymbolListRequestData, - TradingSymbolListBrokerResponse, - TradingSymbol -) +from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData +from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse, TradingSymbol from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with MongoDB: diff --git a/controllers_v2/finstitutions/trading/zerodha_kite.py b/controllers_v2/finstitutions/trading/zerodha_kite.py index a68201d..8a42147 100644 --- a/controllers_v2/finstitutions/trading/zerodha_kite.py +++ b/controllers_v2/finstitutions/trading/zerodha_kite.py @@ -47,11 +47,8 @@ from controllers_v2.finstitutions.trading.base import TradingController # Models: from models.core.auth_token import CoreAuthTokenModel from utils_v2.trading.zerodha_kite.models.auth_tokens import ZerodhaKiteAuthTokens -from models.api.finstitutions.trading.symbols.list import ( - TradingSymbolListRequestData, - TradingSymbolListBrokerResponse, - TradingSymbol -) +from models.api.finstitutions.trading.symbols.list import TradingSymbolListRequestData +from models.finstitutions.trading.symbols import TradingSymbolListBrokerResponse, TradingSymbol from models.finstitutions.trading.oauth import TradingOAuthCallbackResponse # To work with MongoDB: diff --git a/controllers_v2/message/chat/base.py b/controllers_v2/message/chat/base.py index f039690..e97e909 100644 --- a/controllers_v2/message/chat/base.py +++ b/controllers_v2/message/chat/base.py @@ -44,7 +44,7 @@ from controllers_v2.core.message import CoreMessageController # Models: from models.core.auth_token import CoreAuthTokenModel -from models.api.sms.send import ( +from models.api.message.sms.send import ( NimbusSMSIndiaMessage, SavvyBulkSMSKenyaMessage, SMSSendOneResult, diff --git a/models/api/mail/__init__.py b/controllers_v2/message/mail/__init__.py similarity index 100% rename from models/api/mail/__init__.py rename to controllers_v2/message/mail/__init__.py diff --git a/controllers_v2/message/mail/all_mail.py b/controllers_v2/message/mail/all_mail.py new file mode 100644 index 0000000..913e712 --- /dev/null +++ b/controllers_v2/message/mail/all_mail.py @@ -0,0 +1,201 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 16th Jan., 2025. + + OBJECTIVE: + + To handle all mail-related behaviour for Gmail from one place. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# My async utils: +from utils_v2.database.async_mongo_v2 import AsyncMongo +from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache + +# Controllers: +from controllers_v2.message.mail.base import MailController + +# Models: +from models.core.auth_token import CoreAuthTokenModel +from models.api.sms.send import ( + NimbusSMSIndiaMessage, + SavvyBulkSMSKenyaMessage, + SMSSendOneResult, + SMSSendManyResults +) + +# SMS clients: +from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS +from utils_v2.sms.kenya.savvy_bulk_sms.controllers.async_savvy_bulk_sms import AsyncSavvyBulkSMS + +# To work with datatypes: +from typing import List, Any + +# To make HTTP requests: +import httpx + +# To make abstract classes: +from abc import ABC, abstractmethod + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class AllMailController(MailController): + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + def __init__( + self, + cache: AsyncRedisCache = None, + http_client: httpx.AsyncClient = None, + alert_url: str = None, + base_filter: dict = None, + debug: bool = True, + debug_prefix: str = "Mail (C) | ", + debug_only_errors: bool = True + ): + + """ + This is the foundational controller for all mail services. This is built on top of the core message controller, + and, in turn, all individual mail client controllers must be built on top of this. + :param cache: The object to use for caching results from database calls. + :param http_client: The HTTP client + :param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE + FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY. + :param debug: Whether, or not, you would like to print debugging messages: + :param debug_prefix: The prefix to print with the debugging messages. + :param debug_only_errors: Whether you would like to print only error messages or all messages. + :return: None. + """ + + # Prepare the combined base filter: + sms_filter = {} + for k, v in (base_filter or {}).items(): sms_filter[k] = v + sms_filter["serviceType"] = "sms" + + # Invoke the parent's constructor: + CoreMessageController.__init__( + self, + cache = cache, + alert_url = alert_url, + http_client = http_client, + base_filter = sms_filter, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # ┏┓┏┓ ┓ ┏┓ ┏┓ + # ┃┃┣┫┓┏╋┣┓┏┛ ┃┫ + # ┗┛┛┗┗┻┗┛┗┗━•┗┛ + + pass + + # ┳┳┓ •┓ ┏┓ • • + # ┃┃┃┏┓┓┃ ┗┓┓┏┏┳┓┏┳┓┏┓┏┓┓┓┏┓╋┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┻┛┗┗┛┗┗┗┻┛ ┗┗┗┻┗┗┗┛┛┗ + + pass + + # ┳┳┓ •┓ ┏┓ ╹• + # ┃┃┃┏┓┓┃ ┗┓┓┏┏┓┏ ┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┫┛┗┗ ┗┛┗┗┫ + # ┛ ┛ + + # To synchronize the mails on the third-party client's server and your server. You are effectively making a copy of + # the mail on your database. + + pass + + # ┳┳┓ •┓ ┓ • • + # ┃┃┃┏┓┓┃ ┃ ┓┏╋┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┛┗┗┛┗┗┫ + # ┛ + + # Use these to show your users their mails once the mails are on your server. This would include activities like + # listing mails, showing full mails, showing mail trails, etc. + + pass + + # ┳┳┓ •┓ ┏┓ ┓• + # ┃┃┃┏┓┓┃ ┗┓┏┓┏┓┏┫┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗ ┛┗┗┻┗┛┗┗┫ + # ┛ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/controllers_v2/message/mail/base.py b/controllers_v2/message/mail/base.py new file mode 100644 index 0000000..fcc723a --- /dev/null +++ b/controllers_v2/message/mail/base.py @@ -0,0 +1,224 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 16th Jan., 2025. + + OBJECTIVE: + + To handle all mail-related behaviour from one place. The initially known client is only Gmail. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# My async utils: +from utils_v2.database.async_mongo_v2 import AsyncMongo +from utils_v2.database.async_mysql_v2 import AsyncMySQL +from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache + +# Controllers: +from controllers_v2.core.message import CoreMessageController + +# Models: +from models.core.user import CoreUserInfoModel +from models.core.auth_token import CoreAuthTokenModel +from models.api.message.mail.oauth import ( + OAuthMailAuthorizationRequestHeaders, + OAuthMailAuthorizationRequestData +) +from models.message.mail.oauth import OAuthMailGetAuthorizationURLResponse + +# Mail Client(s): +from utils_v2.goog.controllers.gmail.gmail_client import AsyncGMailClient + +# To work with datatypes: +from typing import List, Any + +# To make HTTP requests: +import httpx + +# To make abstract classes: +from abc import ABC, abstractmethod + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class MailController(CoreMessageController, ABC): + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + def __init__( + self, + cache: AsyncRedisCache = None, + http_client: httpx.AsyncClient = None, + alert_url: str = None, + base_filter: dict = None, + debug: bool = True, + debug_prefix: str = "Mail (C) | ", + debug_only_errors: bool = True + ): + + """ + This is the foundational controller for all mail services. This is built on top of the core message controller, + and, in turn, all individual mail client controllers must be built on top of this. + :param cache: The object to use for caching results from database calls. + :param http_client: The HTTP client + :param base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE + FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY. + :param debug: Whether, or not, you would like to print debugging messages: + :param debug_prefix: The prefix to print with the debugging messages. + :param debug_only_errors: Whether you would like to print only error messages or all messages. + :return: None. + """ + + # Prepare the combined base filter: + sms_filter = {} + for k, v in (base_filter or {}).items(): sms_filter[k] = v + sms_filter["serviceType"] = "sms" + + # Invoke the parent's constructor: + CoreMessageController.__init__( + self, + cache = cache, + alert_url = alert_url, + http_client = http_client, + base_filter = sms_filter, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # ┏┓┏┓ ┓ ┏┓ ┏┓ + # ┃┃┣┫┓┏╋┣┓┏┛ ┃┫ + # ┗┛┛┗┗┻┗┛┗┗━•┗┛ + + @abstractmethod + async def get_authorization_url( + self, + sql_conn: AsyncMySQL, + mongo_data_conn: AsyncMongo, + mail_client: AsyncGMailClient, + user_info: CoreUserInfoModel, + inbound_data: OAuthMailAuthorizationRequestData, + session_token: str + ) -> OAuthMailGetAuthorizationURLResponse: + + """ + To accept an incoming request for mail integration and provide a URL that the user can use to authorize your + service to access his mail inbox. + :param sql_conn: The database connection to use to perform this task. + :param mongo_data_conn: The database connection to use to perform this task. + :param mail_client: The instance of the third-party mail client that will be used to get the URL. + :param user_info: The information about your user who is trying to use this system. + :param inbound_data: The data that came in with the request (API call). + :param session_token: The session token of the user. + :return: A structure response with details about the URL generation process. + """ + + pass + + # ┳┳┓ •┓ ┏┓ • • + # ┃┃┃┏┓┓┃ ┗┓┓┏┏┳┓┏┳┓┏┓┏┓┓┓┏┓╋┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┻┛┗┗┛┗┗┗┻┛ ┗┗┗┻┗┗┗┛┛┗ + + pass + + # ┳┳┓ •┓ ┏┓ ╹• + # ┃┃┃┏┓┓┃ ┗┓┓┏┏┓┏ ┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┫┛┗┗ ┗┛┗┗┫ + # ┛ ┛ + + # To synchronize the mails on the third-party client's server and your server. You are effectively making a copy of + # the mail on your database. + + pass + + # ┳┳┓ •┓ ┓ • • + # ┃┃┃┏┓┓┃ ┃ ┓┏╋┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┛┗┗┛┗┗┫ + # ┛ + + # Use these to show your users their mails once the mails are on your server. This would include activities like + # listing mails, showing full mails, showing mail trails, etc. + + pass + + # ┳┳┓ •┓ ┏┓ ┓• + # ┃┃┃┏┓┓┃ ┗┓┏┓┏┓┏┫┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗ ┛┗┗┻┗┛┗┗┫ + # ┛ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/controllers_v2/message/mail/gmail.py b/controllers_v2/message/mail/gmail.py new file mode 100644 index 0000000..e58a40e --- /dev/null +++ b/controllers_v2/message/mail/gmail.py @@ -0,0 +1,385 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 16th Jan., 2025. + + OBJECTIVE: + + To handle all mail-related behaviour for Gmail from one place. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# My async utils: +from utils_v2.date_time import date_time +from utils_v2.database.async_mysql_v2 import AsyncMySQL +from utils_v2.database.async_mongo_v2 import AsyncMongo +from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache + +# Controllers: +from controllers_v2.message.mail.base import MailController + +# Models: +from models.core.user import CoreUserInfoModel +from models.core.auth_token import CoreAuthTokenModel +from models.core.message import CoreMessageModel +from models.api.message.mail.oauth import ( + OAuthMailAuthorizationRequestHeaders, + OAuthMailAuthorizationRequestData +) +from models.message.mail.oauth import OAuthMailGetAuthorizationURLResponse, OAuthMailHandleCallbackResponse + +# Mail Client(s): +from utils_v2.goog.controllers.gmail.gmail_client import AsyncGMailClient, SCOPES_GMAIL_MAIL_MANAGEMENT + +# To work with datatypes: +from typing import List, Any + +# To make HTTP requests: +import httpx + +# For asynchronous activities: +import asyncio + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class GmailController(MailController): + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + def __init__( + self, + cache: AsyncRedisCache = None, + http_client: httpx.AsyncClient = None, + alert_url: str = None, + debug: bool = True, + debug_prefix: str = "Gmail (C) | ", + debug_only_errors: bool = True + ): + + """ + This is the controller specifically built for Gmail's services. It is built on top of the base mail controller. + :param cache: The object to use for caching results from database calls. + :param http_client: The HTTP client + :param debug: Whether, or not, you would like to print debugging messages: + :param debug_prefix: The prefix to print with the debugging messages. + :param debug_only_errors: Whether you would like to print only error messages or all messages. + :return: None. + """ + + # Invoke the parent's constructor: + super().__init__( + cache = cache, + alert_url = alert_url, + http_client = http_client, + base_filter = {"client": "nimbusSmsIndia"}, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # ┏┓┏┓ ┓ ┏┓ ┏┓ + # ┃┃┣┫┓┏╋┣┓┏┛ ┃┫ + # ┗┛┛┗┗┻┗┛┗┗━•┗┛ + + async def get_authorization_url( + self, + sql_conn: AsyncMySQL, + mongo_data_conn: AsyncMongo, + mail_client: AsyncGMailClient, + user_info: CoreUserInfoModel, + inbound_data: OAuthMailAuthorizationRequestData, + session_token: str + ) -> OAuthMailGetAuthorizationURLResponse: + + """ + To accept an incoming request for mail integration and provide a URL that the user can use to authorize your + service to access his mail inbox. + :param sql_conn: The database connection to use to perform this task. + :param mongo_data_conn: The database connection to use to perform this task. + :param mail_client: The instance of the third-party mail client that will be used to get the URL. + :param user_info: The information about your user who is trying to use this system. + :param inbound_data: The data that came in with the request (API call). + :param session_token: The session token of the user. + :return: A structure response with details about the URL generation process. + """ + + # Start by assuming failure: + response = OAuthMailGetAuthorizationURLResponse() + + # First, we create/update a record for this integration request: + token_key = await self.generate_token_key( + sql_conn = sql_conn, + mongo_data_conn = mongo_data_conn, + auth_token = CoreAuthTokenModel( + serviceType = "email", + client = inbound_data.mailClient, + authType = "oauth", + user = user_info, + clientUserId = {"email": inbound_data.mailId}, + status = "pending", + syncFreq = inbound_data.syncFreq, + ), + token_notes = { + "email": inbound_data.mailId, + "client": inbound_data.mailClient + }, + display_name = inbound_data.mailId, + display_picture = None, + session_token = session_token + ) + + # If generating the token key fails: + if token_key is None: + response.message = "Failed to generate token key." + return response + + # Now we create the URL: + response.url = await mail_client.get_authorization_url( + scopes = SCOPES_GMAIL_MAIL_MANAGEMENT, + state = str(token_key), + access_type = "offline", + approval_prompt = "force", + include_granted_scopes = "true", + user_email = inbound_data.mailId + ) + response.success = True + response.message = "Please use the URL to integrate your Gmail account." + + # Done here: + return response + + async def handle_authorization_callback( + self, + sql_conn: AsyncMySQL, + mongo_data_conn: AsyncMongo, + mail_client: AsyncGMailClient, + request_url: str, + inbound_data: dict, + session_token: str = None + ) -> OAuthMailHandleCallbackResponse: + + # Start by assuming failure: + response = OAuthMailHandleCallbackResponse() + + # In case the user denied access: + if inbound_data.get("error") == "access_denied": + response.action = "denied" + response.message = "The user denied authorization." + return response + + # Otherwise we know that the user authorized access: + else: + response.action = "authorized" + response.message = "The user has given authorization." + + # Generate the tokens from the callback. Google sends all the needed params in the callback as the URL's query + # params. We can simply use the exact URL that was hit to generate the tokens. In Quart (and Flask) this can be + # achieved by 'request.url' like this: + google_tokens = await mail_client.get_authorization_tokens( + redirect_url = request_url, + scopes = None + ) + + # If no tokens were generated: + if not google_tokens: + response.message = "Failed to get access token(s) from Gmail." + return response + + # Try getting the user's profile from Gmail: + user_profile = await mail_client.get_user_profile(tokens = google_tokens) + if user_profile.success: + google_tokens.email = user_profile.data["emailAddress"] + google_tokens.displayName = user_profile.data["displayName"] + google_tokens.displayPictureUrl = user_profile.data["displayPictureUrl"] + else: + response.message = "Failed to get the user's profile from Gmail." + return response + + # Now we check if the email that the user originally claimed to authorize is the same as the one that gave the + # authorization. We must fetch the auth-token for that: + auth_token = await self.get_token_from_key( + mongo_data_conn = mongo_data_conn, + token_key = inbound_data["state"] + ) + if not auth_token: + response.message = "Failed to load the auth-token for this flow." + return response + + # If the two email ids don't match: + if auth_token.clientUserId["email"] != str(google_tokens.email): + response.message = ( + f"We were expecting authorization from '{auth_token.clientUserId['email']}', " + f"but got authorization from '{google_tokens.email}' instead." + ) + return response + + # Now we try to create the standard set of Labels: + labels = [ + { + "name": "TCAOFF", + "textColor": "#434343", + "backgroundColor": "#e7e7e7" + }, + { + "name": "CA-Doc", + "textColor": "#434343", + "backgroundColor": "#e7e7e7" + }, + { + "name": "CA-AI", + "textColor": "#434343", + "backgroundColor": "#e7e7e7" + } + ] + tasks = [ + mail_client.create_label( + tokens = google_tokens, + label_name = label["name"], + label_visibility = "labelShow", + message_visibility = "show", + label_text_color = label["textColor"], + label_background_color = label["backgroundColor"] + ) for label in labels + ] + client_responses = await asyncio.gather(*tasks) + + # Add the labels to the tokens data: + client_response = await mail_client.list_labels(tokens = google_tokens) + google_tokens.labels = client_response.data if client_response.success else None + + # Now that we have passed the check, + # we save the tokens to the database: + auth_token.clientUserId = google_tokens.client_user_id + auth_token.token = google_tokens.model_dump() + auth_token.status = "active" + tokens_saved = await self.set_token( + sql_conn = sql_conn, + mongo_data_conn = mongo_data_conn, + token_key = inbound_data["state"], + auth_token = auth_token, + token_notes = { + "email": google_tokens.email, + "client": auth_token.client + }, + display_name = google_tokens.displayName, + display_picture = google_tokens.displayPictureUrl, + session_token = session_token + ) + + # Note down the final result: + if tokens_saved: + response.success = True + response.message = "Authorization flow completed successfully." + else: response.message = "Failed to save the token(s)." + + # Done here: + return response + + # ┳┳┓ •┓ ┏┓ • • + # ┃┃┃┏┓┓┃ ┗┓┓┏┏┳┓┏┳┓┏┓┏┓┓┓┏┓╋┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┻┛┗┗┛┗┗┗┻┛ ┗┗┗┻┗┗┗┛┛┗ + + pass + + # ┳┳┓ •┓ ┏┓ ╹• + # ┃┃┃┏┓┓┃ ┗┓┓┏┏┓┏ ┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┫┛┗┗ ┗┛┗┗┫ + # ┛ ┛ + + # To synchronize the mails on the third-party client's server and your server. You are effectively making a copy of + # the mail on your database. + + pass + + # ┳┳┓ •┓ ┓ • • + # ┃┃┃┏┓┓┃ ┃ ┓┏╋┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗┛┗┗┛┗┗┫ + # ┛ + + # Use these to show your users their mails once the mails are on your server. This would include activities like + # listing mails, showing full mails, showing mail trails, etc. + + pass + + # ┳┳┓ •┓ ┏┓ ┓• + # ┃┃┃┏┓┓┃ ┗┓┏┓┏┓┏┫┓┏┓┏┓ + # ┛ ┗┗┻┗┗ ┗┛┗ ┛┗┗┻┗┛┗┗┫ + # ┛ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/controllers_v2/message/sms/all_sms.py b/controllers_v2/message/sms/all_sms.py index 23a8fb1..e8b6876 100644 --- a/controllers_v2/message/sms/all_sms.py +++ b/controllers_v2/message/sms/all_sms.py @@ -46,7 +46,7 @@ from controllers_v2.message.sms.base import SMSController # Models: from models.core.auth_token import CoreAuthTokenModel from models.core.message import CoreMessageModel -from models.api.sms.send import ( +from models.api.message.sms.send import ( NimbusSMSIndiaMessage, SMSSendOneResult, SMSSendManyResults diff --git a/controllers_v2/message/sms/base.py b/controllers_v2/message/sms/base.py index 6689e1e..cc29fb4 100644 --- a/controllers_v2/message/sms/base.py +++ b/controllers_v2/message/sms/base.py @@ -44,7 +44,7 @@ from controllers_v2.core.message import CoreMessageController # Models: from models.core.auth_token import CoreAuthTokenModel -from models.api.sms.send import ( +from models.api.message.sms.send import ( NimbusSMSIndiaMessage, SavvyBulkSMSKenyaMessage, SMSSendOneResult, diff --git a/controllers_v2/message/sms/nimbus_sms_india.py b/controllers_v2/message/sms/nimbus_sms_india.py index 654dc5d..ad213e4 100644 --- a/controllers_v2/message/sms/nimbus_sms_india.py +++ b/controllers_v2/message/sms/nimbus_sms_india.py @@ -47,7 +47,7 @@ from controllers_v2.message.sms.base import SMSController # Models: from models.core.auth_token import CoreAuthTokenModel from models.core.message import CoreMessageModel -from models.api.sms.send import ( +from models.api.message.sms.send import ( NimbusSMSIndiaMessage, SMSSendOneResult, SMSSendManyResults diff --git a/controllers_v2/message/sms/savvy_bulk_sms_kenya.py b/controllers_v2/message/sms/savvy_bulk_sms_kenya.py index 562f135..c6cfd1c 100644 --- a/controllers_v2/message/sms/savvy_bulk_sms_kenya.py +++ b/controllers_v2/message/sms/savvy_bulk_sms_kenya.py @@ -47,7 +47,7 @@ from controllers_v2.message.sms.base import SMSController # Models: from models.core.auth_token import CoreAuthTokenModel from models.core.message import CoreMessageModel -from models.api.sms.send import ( +from models.api.message.sms.send import ( SavvyBulkSMSKenyaMessage, SMSSendOneResult, SMSSendManyResults diff --git a/models/api/finstitutions/trading/auth/oauth.py b/models/api/finstitutions/trading/auth/oauth.py index 5113d87..004ca48 100644 --- a/models/api/finstitutions/trading/auth/oauth.py +++ b/models/api/finstitutions/trading/auth/oauth.py @@ -39,6 +39,13 @@ sys.path.append("..") from pydantic import BaseModel, Field, field_validator, PastDatetime, model_validator from typing import Optional, Literal, Union +# Models: +from models.finstitutions.trading.oauth import ( + PaperTradingAuth, + ZerodhaKiteAuth, + ICICIBreezeAuth +) + # My utils: from utils_v2.string import regex from utils_v2.date_time import date_time @@ -75,104 +82,6 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9] # ***************************************************************************************************************** -class PaperTradingAuth(BaseModel): - - username: str = Field( - description = "??", - frozen = True - ) - - password: str = Field( - description = "??", - frozen = True - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - - -# --------------------------------------------------------------------------------------------------------------------- - - -class ZerodhaKiteAuth(BaseModel): - - userId: str = Field( - description = "How Zerodha's Kite platform identifies this user.", - frozen = True, - alias = "clientId" - ) - - apiKey: str = Field( - description = ( - "The API key of your Kite app. " - "This remains constant throughout the life of the app." - ), - frozen = True - ) - - apiSecret: str = Field( - description = ( - "The API secret of your Kite app. " - "This can be changed if you think the security of your app has been compromised." - ), - frozen = True - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - populate_by_name = True - - -# --------------------------------------------------------------------------------------------------------------------- - - -class ICICIBreezeAuth(BaseModel): - - userId: str = Field( - description = "How ICICI's Breeze platform identifies this user.", - frozen = True, - alias = "clientId" - ) - - apiKey: str = Field( - description = ( - "The API key of your Breeze app. " - "This remains constant throughout the life of the app." - ), - frozen = True - ) - - apiSecret: str = Field( - description = ( - "The API secret of your Breeze app. " - "This can be changed if you think the security of your app has been compromised." - ), - frozen = True - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - populate_by_name = True - - -# --------------------------------------------------------------------------------------------------------------------- - - class TradingAuthRequestHeaders(BaseModel): sessionToken: str = Field( diff --git a/models/api/finstitutions/trading/symbols/list.py b/models/api/finstitutions/trading/symbols/list.py index 646be78..b21f616 100644 --- a/models/api/finstitutions/trading/symbols/list.py +++ b/models/api/finstitutions/trading/symbols/list.py @@ -79,45 +79,6 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9] # ***************************************************************************************************************** -class TradingSymbolListBrokerResponse(BaseModel): - - success: bool = Field( - description = "whether, or not, the symbol list request was successful", - default = False, - frozen = False - ) - - message: str = Field( - description = "a brief message to help debug in failed cases", - default = "", - frozen = False - ) - - data: List[TradingSymbol] | None = Field( - description = "the actual response from the broker with his list of tradeable symbols", - default = None, - frozen = False - ) - - exception: Exception | None = Field( - description = "if something goes wrong, the exception will be held here", - default = None, - frozen = False - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - arbitrary_types_allowed = True - - -# --------------------------------------------------------------------------------------------------------------------- - - class TradingSymbolListRequestHeaders(BaseModel): sessionToken: str = Field( diff --git a/models/api/sms/__init__.py b/models/api/message/__init__.py similarity index 100% rename from models/api/sms/__init__.py rename to models/api/message/__init__.py diff --git a/models/api/message/chat/__init__.py b/models/api/message/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/api/chat/auth.py b/models/api/message/chat/auth.py similarity index 84% rename from models/api/chat/auth.py rename to models/api/message/chat/auth.py index 937fdaa..e4e3a1f 100644 --- a/models/api/chat/auth.py +++ b/models/api/message/chat/auth.py @@ -39,6 +39,9 @@ sys.path.append("..") from pydantic import BaseModel, Field, field_validator, PastDatetime, model_validator from typing import Optional, Literal, Union +# Models: +from models.message.chat.auth import TelegramAuth, WhatsAppNimbusAuth + # My utils: from utils_v2.string import regex from utils_v2.date_time import date_time @@ -75,52 +78,6 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9] # ***************************************************************************************************************** -class TelegramAuth(BaseModel): - - botToken: str = Field( - description = "the token granted by BotFather", - min_length = 1, - frozen = True - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - - -# --------------------------------------------------------------------------------------------------------------------- - - -class WhatsAppNimbusAuth(BaseModel): - - apiKey: str = Field( - description = "???", - min_length = 1, - frozen = True - ) - - senderId: str = Field( - description = "???", - min_length = 1, - frozen = True - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - - -# --------------------------------------------------------------------------------------------------------------------- - - class ChatAuthRequestHeaders(BaseModel): sessionToken: str = Field( diff --git a/models/api/message/mail/__init__.py b/models/api/message/mail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/api/mail/get.py b/models/api/message/mail/get.py similarity index 100% rename from models/api/mail/get.py rename to models/api/message/mail/get.py diff --git a/models/api/mail/list.py b/models/api/message/mail/list.py similarity index 100% rename from models/api/mail/list.py rename to models/api/message/mail/list.py diff --git a/models/api/mail/oauth.py b/models/api/message/mail/oauth.py similarity index 100% rename from models/api/mail/oauth.py rename to models/api/message/mail/oauth.py diff --git a/models/api/mail/send.py b/models/api/message/mail/send.py similarity index 100% rename from models/api/mail/send.py rename to models/api/message/mail/send.py diff --git a/models/api/mail/sync.py b/models/api/message/mail/sync.py similarity index 71% rename from models/api/mail/sync.py rename to models/api/message/mail/sync.py index 65612e4..828e46f 100644 --- a/models/api/mail/sync.py +++ b/models/api/message/mail/sync.py @@ -156,98 +156,6 @@ class MailSyncRequestData(BaseModel): return value -# --------------------------------------------------------------------------------------------------------------------- - - -class MailSyncOneResult(BaseModel): - - success: bool = Field( - description = "whether, or not, the mail was successfully sync'd", - default = False - ) - - message: str | None = Field( - description = "a brief message to summarize the result of the process", - default = None - ) - - mailMessage: CoreMessageModel | None = Field( - description = "the actual data of the mail; can be null in a successful process if the mail is already sync'd", - default = None - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - - -# --------------------------------------------------------------------------------------------------------------------- - - -class MailSyncManyResults(BaseModel): - - totalCount: int = Field( - description = "the total no. of mails that were to be sync'd", - default = 0 - ) - - successCount: int = Field( - description = "the no. of mails that were successfully sync'd", - default = 0 - ) - - failureCount: int = Field( - description = "the no. of mails that were successfully sync'd", - default = 0 - ) - - message: str = Field( - description = "a brief message to summarize the results of the process", - default = None - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - - -# --------------------------------------------------------------------------------------------------------------------- - - -class MailSendOneResult(BaseModel): - - success: bool = Field( - description = "whether, or not, the mail was successfully sent", - default = False - ) - - message: str | None = Field( - description = "a brief message to summarize the result of the process", - default = None - ) - - mailMessage: CoreMessageModel | None = Field( - description = "the actual data of the mail; can be null in a successful process if the mail is already sent", - default = None - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - - # ***************************************************************************************************************** # ***** **** # *** MAIN PROGRAM *** diff --git a/models/api/mail/tags.py b/models/api/message/mail/tags.py similarity index 100% rename from models/api/mail/tags.py rename to models/api/message/mail/tags.py diff --git a/models/api/message/sms/__init__.py b/models/api/message/sms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/api/message/sms/auth.py b/models/api/message/sms/auth.py new file mode 100644 index 0000000..4259194 --- /dev/null +++ b/models/api/message/sms/auth.py @@ -0,0 +1,144 @@ +""" + + 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, model_validator +from typing import Optional, Literal, Union + +# Models: +from models.message.sms.auth import NimbusSMSIndiaAuth, SavvyBulkSMSKenyaAuth + +# 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 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): + + smsClient: Literal["nimbusSmsIndia", "savvyBulkSmsKenya"] = Field(alias = "client") + auth: Union[NimbusSMSIndiaAuth, SavvyBulkSMSKenyaAuth] + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + @model_validator(mode = "after") + def ensure_harmony(cls, values): + client = values.smsClient + auth = values.auth + harmony_map = { + "nimbusSmsIndia": NimbusSMSIndiaAuth, + "savvyBulkSmsKenya": SavvyBulkSMSKenyaAuth + } + if not isinstance(auth, harmony_map[client]): + raise ValueError(f"incorrect 'auth' for selected client '{client}'") + return values + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/api/sms/list.py b/models/api/message/sms/list.py similarity index 100% rename from models/api/sms/list.py rename to models/api/message/sms/list.py diff --git a/models/api/message/sms/send.py b/models/api/message/sms/send.py new file mode 100644 index 0000000..c60b213 --- /dev/null +++ b/models/api/message/sms/send.py @@ -0,0 +1,160 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Monday, 9th Dec., 2024. + + OBJECTIVE: + + To provide a structure to receive API calls to send SMS messages from various third-party clients. + + 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, List, Any + +# My utils: +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# Models: +from models.core.message import CoreMessageModel +from utils_v2.sms.models.sms_message import SentSMSMessageModel +from models.message.sms.send import ( + NimbusSMSIndiaMessage, + SavvyBulkSMSKenyaMessage, + SMSSendOneResult, + SMSSendManyResults +) + +# To work with date and time: +import datetime + +# To work with MongoDB: +from bson.objectid import ObjectId + + +# ***************************************************************************************************************** +# ***** **** +# *** 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 SMSSendRequestHeaders(BaseModel): + + sessionToken: str | None = Field( + description = "the session token of the user who is requesting the service", + pattern = REGEX_SESSION_TOKEN, + frozen = True, + default = None, + alias = "X-Session-Token" + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "allow" + + def model_dump(self, *args, **kwargs): + return super().model_dump(*args, by_alias = True, **kwargs) + + +# --------------------------------------------------------------------------------------------------------------------- + + +class SMSSendRequestData(BaseModel): + + tokenKey: ObjectId + message: List[NimbusSMSIndiaMessage] | List[SavvyBulkSMSKenyaMessage] + tags: List[Any] | None = Field(default = None, validate_default = True) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + arbitrary_types_allowed = True + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + @field_validator("tokenKey", mode = "before") + def parse_oid(cls, value): + try: value = ObjectId(value) + except: pass + return value + + @field_validator("message", mode = "before") + def ensure_list(cls, value): + if not isinstance(value, list): value = [value] + return value + + @field_validator("tags", mode = "after") + def null_to_list(cls, value): + return [] if value is None else value + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/api/sms/tags.py b/models/api/message/sms/tags.py similarity index 100% rename from models/api/sms/tags.py rename to models/api/message/sms/tags.py diff --git a/models/finstitutions/trading/oauth.py b/models/finstitutions/trading/oauth.py index f212457..c65f460 100644 --- a/models/finstitutions/trading/oauth.py +++ b/models/finstitutions/trading/oauth.py @@ -74,6 +74,104 @@ import datetime # ***************************************************************************************************************** +class PaperTradingAuth(BaseModel): + + username: str = Field( + description = "??", + frozen = True + ) + + password: str = Field( + description = "??", + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# --------------------------------------------------------------------------------------------------------------------- + + +class ZerodhaKiteAuth(BaseModel): + + userId: str = Field( + description = "How Zerodha's Kite platform identifies this user.", + frozen = True, + alias = "clientId" + ) + + apiKey: str = Field( + description = ( + "The API key of your Kite app. " + "This remains constant throughout the life of the app." + ), + frozen = True + ) + + apiSecret: str = Field( + description = ( + "The API secret of your Kite app. " + "This can be changed if you think the security of your app has been compromised." + ), + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + populate_by_name = True + + +# --------------------------------------------------------------------------------------------------------------------- + + +class ICICIBreezeAuth(BaseModel): + + userId: str = Field( + description = "How ICICI's Breeze platform identifies this user.", + frozen = True, + alias = "clientId" + ) + + apiKey: str = Field( + description = ( + "The API key of your Breeze app. " + "This remains constant throughout the life of the app." + ), + frozen = True + ) + + apiSecret: str = Field( + description = ( + "The API secret of your Breeze app. " + "This can be changed if you think the security of your app has been compromised." + ), + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + populate_by_name = True + + +# --------------------------------------------------------------------------------------------------------------------- + + class TradingOAuthCallbackResponse(BaseModel): success: bool = Field( diff --git a/models/finstitutions/trading/symbols.py b/models/finstitutions/trading/symbols.py index d9917da..a506115 100644 --- a/models/finstitutions/trading/symbols.py +++ b/models/finstitutions/trading/symbols.py @@ -220,6 +220,45 @@ class TradingSymbol(BaseModel): return value +# --------------------------------------------------------------------------------------------------------------------- + + +class TradingSymbolListBrokerResponse(BaseModel): + + success: bool = Field( + description = "whether, or not, the symbol list request was successful", + default = False, + frozen = False + ) + + message: str = Field( + description = "a brief message to help debug in failed cases", + default = "", + frozen = False + ) + + data: List[TradingSymbol] | None = Field( + description = "the actual response from the broker with his list of tradeable symbols", + default = None, + frozen = False + ) + + exception: Exception | None = Field( + description = "if something goes wrong, the exception will be held here", + default = None, + frozen = False + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + arbitrary_types_allowed = True + + # ***************************************************************************************************************** # ***** **** # *** MAIN PROGRAM *** diff --git a/models/message/__init__.py b/models/message/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/message/chat/__init__.py b/models/message/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/message/chat/auth.py b/models/message/chat/auth.py new file mode 100644 index 0000000..176426c --- /dev/null +++ b/models/message/chat/auth.py @@ -0,0 +1,130 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Friday, 6th Dec., 2024. + + OBJECTIVE: + + To provide a structure to receive auth details of various chat apps (like Telegram and WhatsApp). + + 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, model_validator +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 TelegramAuth(BaseModel): + + botToken: str = Field( + description = "the token granted by BotFather", + min_length = 1, + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# --------------------------------------------------------------------------------------------------------------------- + + +class WhatsAppNimbusAuth(BaseModel): + + apiKey: str = Field( + description = "???", + min_length = 1, + frozen = True + ) + + senderId: str = Field( + description = "???", + min_length = 1, + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/message/mail/__init__.py b/models/message/mail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/message/mail/oauth.py b/models/message/mail/oauth.py new file mode 100644 index 0000000..d9ab1c2 --- /dev/null +++ b/models/message/mail/oauth.py @@ -0,0 +1,185 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 16th Jan., 2025. + + OBJECTIVE: + + To provide the structure for the request and response of the APIs that will be used to request OAuth2.0 + authorization for mail services. + + 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 +from typing import Optional, Literal, Any + +# My utils: +from utils_v2.string import regex + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +class OAuthMailGetAuthorizationURLResponse(BaseModel): + + success: bool = Field( + description = "To indicate whether or not, the action was a success", + frozen = False, + default = False + ) + + url: str | None = Field( + description = "The URL to use to integrate the mail client.", + frozen = False, + default = None + ) + + message: str = Field( + description = "To explain what happened in the process generating a URL.", + frozen = False, + default = "ERR: Message not captured." + ) + + exception: Any = Field( + description = "To pass on any exception that occurred in the process.", + frozen = False, + default = None + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + # ┏┓ ┏┓ + # ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏ + # ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛ + + pass + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + pass + + +# --------------------------------------------------------------------------------------------------------------------- + + +class OAuthMailHandleCallbackResponse(BaseModel): + + success: bool = Field( + description = "To indicate whether or not, the action was a success", + frozen = False, + default = False + ) + + action: Literal[ + "unknown", # ..... Initial value when the user's intent is not known. + "denied", # ...... The user consciously denied permission. + "cancelled", # ... The user cancelled the process midway. + "authorized" # ... The user gave authorization. + ] = Field( + description = "To describe what the user did with the authorization URL.", + frozen = True, + default = "unknown" + ) + + message: str = Field( + description = "To explain what happened in the process of handling the OAuth callback.", + frozen = False, + default = "ERR: Message not captured." + ) + + exception: Any = Field( + description = "To pass on any exception that occurred in the process.", + frozen = False, + default = None + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + # ┏┓ ┏┓ + # ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏ + # ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛ + + pass + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/message/mail/send.py b/models/message/mail/send.py new file mode 100644 index 0000000..bf7364b --- /dev/null +++ b/models/message/mail/send.py @@ -0,0 +1,120 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Thursday, 19th Dec., 2024. + + OBJECTIVE: + + To provide a structure to send mails. + + 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, EmailStr +from typing import Optional, Literal, List + +# Models: +from models.core.message import CoreMessageModel + +# My utils: +from utils_v2.string import json +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with date and time: +import datetime + +# To work with MongoDB: +from bson.objectid import ObjectId + + +# ***************************************************************************************************************** +# ***** **** +# *** 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 MailSendOneResult(BaseModel): + + success: bool = Field( + description = "whether, or not, the mail was successfully sent", + default = False + ) + + message: str | None = Field( + description = "a brief message to summarize the result of the process", + default = None + ) + + mailMessage: CoreMessageModel | None = Field( + description = "the actual data of the mail; can be null in a successful process if the mail is already sent", + default = None + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/message/mail/sync.py b/models/message/mail/sync.py new file mode 100644 index 0000000..a8212da --- /dev/null +++ b/models/message/mail/sync.py @@ -0,0 +1,150 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Monday, 2nd Dec., 2024. + + OBJECTIVE: + + To provide the structure for the request that will come in to sync the mails of a particular user. + + 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, AwareDatetime +from typing import Optional, Literal + +# My utils: +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# Data models: +from models.core.message import CoreMessageModel + +# 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 MailSyncOneResult(BaseModel): + + success: bool = Field( + description = "whether, or not, the mail was successfully sync'd", + default = False + ) + + message: str | None = Field( + description = "a brief message to summarize the result of the process", + default = None + ) + + mailMessage: CoreMessageModel | None = Field( + description = "the actual data of the mail; can be null in a successful process if the mail is already sync'd", + default = None + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# --------------------------------------------------------------------------------------------------------------------- + + +class MailSyncManyResults(BaseModel): + + totalCount: int = Field( + description = "the total no. of mails that were to be sync'd", + default = 0 + ) + + successCount: int = Field( + description = "the no. of mails that were successfully sync'd", + default = 0 + ) + + failureCount: int = Field( + description = "the no. of mails that were successfully sync'd", + default = 0 + ) + + message: str = Field( + description = "a brief message to summarize the results of the process", + default = None + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/message/sms/__init__.py b/models/message/sms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/api/sms/auth.py b/models/message/sms/auth.py similarity index 76% rename from models/api/sms/auth.py rename to models/message/sms/auth.py index 89c5b2d..0288da8 100644 --- a/models/api/sms/auth.py +++ b/models/message/sms/auth.py @@ -143,63 +143,6 @@ class SavvyBulkSMSKenyaAuth(BaseModel): 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): - - smsClient: Literal["nimbusSmsIndia", "savvyBulkSmsKenya"] = Field(alias = "client") - auth: Union[NimbusSMSIndiaAuth, SavvyBulkSMSKenyaAuth] - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - - # ┓┏ ┓• ┓ • - # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ - # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ - - @model_validator(mode = "after") - def ensure_harmony(cls, values): - client = values.smsClient - auth = values.auth - harmony_map = { - "nimbusSmsIndia": NimbusSMSIndiaAuth, - "savvyBulkSmsKenya": SavvyBulkSMSKenyaAuth - } - if not isinstance(auth, harmony_map[client]): - raise ValueError(f"incorrect 'auth' for selected client '{client}'") - return values - - # ***************************************************************************************************************** # ***** **** # *** MAIN PROGRAM *** diff --git a/models/api/sms/send.py b/models/message/sms/send.py similarity index 81% rename from models/api/sms/send.py rename to models/message/sms/send.py index b24c770..f002354 100644 --- a/models/api/sms/send.py +++ b/models/message/sms/send.py @@ -151,69 +151,6 @@ class SavvyBulkSMSKenyaMessage(BaseModel): # --------------------------------------------------------------------------------------------------------------------- -class SMSSendRequestHeaders(BaseModel): - - sessionToken: str | None = Field( - description = "the session token of the user who is requesting the service", - pattern = REGEX_SESSION_TOKEN, - frozen = True, - default = None, - alias = "X-Session-Token" - ) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "allow" - - def model_dump(self, *args, **kwargs): - return super().model_dump(*args, by_alias = True, **kwargs) - - -# --------------------------------------------------------------------------------------------------------------------- - - -class SMSSendRequestData(BaseModel): - - tokenKey: ObjectId - message: List[NimbusSMSIndiaMessage] | List[SavvyBulkSMSKenyaMessage] - tags: List[Any] | None = Field(default = None, validate_default = True) - - # ┏┓ ┏• - # ┃ ┏┓┏┓╋┓┏┓ - # ┗┛┗┛┛┗┛┗┗┫ - # ┛ - - class Config: - extra = "forbid" - arbitrary_types_allowed = True - - # ┓┏ ┓• ┓ • - # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ - # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ - - @field_validator("tokenKey", mode = "before") - def parse_oid(cls, value): - try: value = ObjectId(value) - except: pass - return value - - @field_validator("message", mode = "before") - def ensure_list(cls, value): - if not isinstance(value, list): value = [value] - return value - - @field_validator("tags", mode = "after") - def null_to_list(cls, value): - return [] if value is None else value - - -# --------------------------------------------------------------------------------------------------------------------- - - class SMSSendOneResult(BaseModel): success: bool = Field( diff --git a/utils_v2/goog/controllers/base.py b/utils_v2/goog/controllers/base.py index d5f51b4..46cbff4 100644 --- a/utils_v2/goog/controllers/base.py +++ b/utils_v2/goog/controllers/base.py @@ -218,7 +218,7 @@ class AsyncGoogleBase: async def get_authorization_tokens( self, - scopes: List[str], + scopes: List[str] | None, redirect_url: str ) -> GoogleAuthTokens: