From c022844824781550213344af09497d071613ad5a Mon Sep 17 00:00:00 2001 From: yatmesh Date: Wed, 25 Jun 2025 14:06:02 +0530 Subject: [PATCH] (20250625) - Implemented the ecommerce shopify auth integration. with multiple files added new API for get token details --- .../common/integrations/__init__.py | 0 api/blueprints/common/integrations/get.py | 193 ++++++++++++++++ api/blueprints/software/auth.py | 26 ++- api/main.py | 15 +- controllers_v2/core/auth_token.py | 58 +++++ controllers_v2/software/ecommerce/__init__.py | 0 controllers_v2/software/ecommerce/base.py | 206 +++++++++++++++++ controllers_v2/software/ecommerce/shopify.py | 210 ++++++++++++++++++ models/api/common/integrations/__init__.py | 0 models/api/common/integrations/auth_get.py | 141 ++++++++++++ models/api/software/auth.py | 9 +- models/core/auth_token.py | 15 +- models/software/ecommerce/__init__.py | 0 models/software/ecommerce/auth.py | 163 ++++++++++++++ 14 files changed, 1029 insertions(+), 7 deletions(-) create mode 100644 api/blueprints/common/integrations/__init__.py create mode 100644 api/blueprints/common/integrations/get.py create mode 100644 controllers_v2/software/ecommerce/__init__.py create mode 100644 controllers_v2/software/ecommerce/base.py create mode 100644 controllers_v2/software/ecommerce/shopify.py create mode 100644 models/api/common/integrations/__init__.py create mode 100644 models/api/common/integrations/auth_get.py create mode 100644 models/software/ecommerce/__init__.py create mode 100644 models/software/ecommerce/auth.py diff --git a/api/blueprints/common/integrations/__init__.py b/api/blueprints/common/integrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/blueprints/common/integrations/get.py b/api/blueprints/common/integrations/get.py new file mode 100644 index 0000000..17be9d5 --- /dev/null +++ b/api/blueprints/common/integrations/get.py @@ -0,0 +1,193 @@ +""" + + AUTHOR: + + Omkar Khandare + + DATE: + + Wednesday, 25rd June., 2025. + + OBJECTIVE: + + To disable chat accounts. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + + NOTES: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys + +from api.blueprints.common.disable import auth_token_disable_bp + +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 +) + +# Common: +from shared import constants + +# Data Models: +from models.core.user import CoreUserInfoModel +from models.api.common.integrations.auth_get import AuthTokenGetRequestData, AuthTokenGetRequestHeaders + +# Helpers: +from api.helpers.user import token_check + +# To work with MongoDB: +from bson import ObjectId + +# For asynchronous activities: +import asyncio + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# Related to Quart: +integration_get_bp = Blueprint("integration get", __name__) + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +@integration_get_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 + + +# --------------------------------------------------------------------------------------------------------------------- + +@integration_get_bp.route("/token", methods = ["GET", "POST"]) +@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 = "integrationTokenGetAPI", + log_input = True, + log_output = True, + sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKey"] +) +@log_chain_to_mongo(attr_name = "logs_mongo") +@only_whitelisted_ips(attr_name="whitelisted_ips") +@should_not_be_under_maintenance(attr_name = "is_under_maintenance") +@validate_input( + header_validator = None, + data_validator = lambda x: AuthTokenGetRequestData(**x) +) +@handle_cancelled_request() +async def get_integrations_token( + inbound_headers: dict = None, + inbound_data: dict | AuthTokenGetRequestData = None, + inbound_files: dict = None, + **kwargs +): + + """ + Use this when a user wants to remove/disable his account. + :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. + """ + print("INBOUND", inbound_data) + + # ┳┳┓ ┓•┏ ┏┓ + # ┃┃┃┏┓┏┫┓╋┓┏ ┗┓╋┏┓╋┓┏┏ + # ┛ ┗┗┛┗┻┗┛┗┫ ┗┛┗┗┻┗┗┻┛ + # ┛ + + # Update the message: + auth_token = await current_app.core_auth_token_controller.get_token_from_key( + mongo_data_conn=current_app.data_mongo, + token_key=inbound_data.tokenKey + ) + print("AUTH TOKEN RES:", auth_token) + + success = False if auth_token is None else True + + # ┳┓ + # ┣┫┏┓┏┏┓┏┓┏┓┏┏┓ + # ┛┗┗ ┛┣┛┗┛┛┗┛┗ + # ┛ + + # Done here: + return ResponseModel( + status_code = StatusCodes.OK if success else StatusCodes.FAILED, + http_code = HttpCodes.SUCCESS if success else HttpCodes.INTERNAL_SERVER_ERROR, + data = auth_token.to_json() if success else auth_token + ) + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/api/blueprints/software/auth.py b/api/blueprints/software/auth.py index 06178b4..ab415c7 100644 --- a/api/blueprints/software/auth.py +++ b/api/blueprints/software/auth.py @@ -236,6 +236,29 @@ async def authorize_software_client( success = response.success message = f"Action Chain '{response.actionChain}': {response.message}" + elif inbound_data.softwareClient == "shopify": + # Make the client controller test and save the auth: + response = await current_app.shopify_controller.save_auth( + sql_conn=current_app.sql_writer, + mongo_data_conn=current_app.data_mongo, + auth=inbound_data.auth, + user=kwargs.get("session_info"), + session_token=inbound_headers.get("X-Session-Token") + ) + + # Note down the results: + success = response.success + message = response.message + token_id = response.token_id + + auth_url_success = "" + auth_url_failed = "" + + if token_id is not None: + auth_url_success = f"https://api.thecaoffice.com/shopify/auth/template?status=1&token={token_id}&storeName={inbound_data.auth.storeName}&storeUrl={inbound_data.auth.storeUrl}" + else: + auth_url_failed = f"https://api.thecaoffice.com/shopify/auth/template?status=0&storeName={inbound_data.auth.storeName}&storeUrl={inbound_data.auth.storeUrl}" + # ┳┓ # ┣┫┏┓┏┏┓┏┓┏┓┏┏┓ # ┛┗┗ ┛┣┛┗┛┛┗┛┗ @@ -248,7 +271,8 @@ async def authorize_software_client( message = message, data = { "client": inbound_data.softwareClient, - "authorized": success + "authorized": success, + "authorizationUrl": auth_url_success if success else auth_url_failed } ) diff --git a/api/main.py b/api/main.py index cbb3577..894b189 100644 --- a/api/main.py +++ b/api/main.py @@ -102,6 +102,7 @@ from controllers_v2.finstitutions.payments.safaricom_mpesa_express import Safari from controllers_v2.software.mikrotik.all_mikrotik import AllMikroTikController from controllers_v2.software.mikrotik.mikrotik_pppoe_1000 import MikroTikPPPoE1000Controller from controllers_v2.software.mikrotik.mikrotik_hostpot_1000 import MikroTikHotspot1000Controller +from controllers_v2.software.ecommerce.shopify import ShopifyAppController # --- from controllers_v2.common.otp.timed_otp import TimedOTPController @@ -157,7 +158,7 @@ from api.blueprints.common.disable import auth_token_disable_bp from api.blueprints.common.session_token import session_token_bp from api.blueprints.common.otp.timed_otp import timed_otp_bp from api.blueprints.common.otp.timed_otp_test import timed_otp_bp_test - +from api.blueprints.common.integrations.get import integration_get_bp # Tech and Testing Blueprints: from api.blueprints.tech.chat_alerts import tech_chat_alert_bp from api.blueprints.test.callback import test_callback_bp @@ -233,6 +234,7 @@ app.register_blueprint(trading_symbols_list_bp, url_prefix = f"/{MODULE_BASE}/fi app.register_blueprint(auth_token_disable_bp, url_prefix = f"/{MODULE_BASE}") app.register_blueprint(session_token_bp, url_prefix = f"/{MODULE_BASE}") app.register_blueprint(timed_otp_bp, url_prefix = f"/{MODULE_BASE}/otp/timed") +app.register_blueprint(integration_get_bp, url_prefix = f"/{MODULE_BASE}/integrations/") # app.register_blueprint(timed_otp_bp_test, url_prefix = f"/{MODULE_BASE}/otp/timed") # AI Blueprints: @@ -601,6 +603,17 @@ async def app_startup(**kwargs): ) current_app.printer("Software/MikroTik (C) ready.") + # software / ecommerce + current_app.shopify_controller = ShopifyAppController( + cache=current_app.module_cache, + http_client=current_app.http_client, + alert_url=current_app.script_data["alerts"]["url"], + debug=enable_debugging + ) + current_app.printer("Software/ecommerce (C) ready.") + + + # Common / OTP: current_app.timed_otp_controller = TimedOTPController( debug = enable_debugging diff --git a/controllers_v2/core/auth_token.py b/controllers_v2/core/auth_token.py index cc2ecf8..6752950 100644 --- a/controllers_v2/core/auth_token.py +++ b/controllers_v2/core/auth_token.py @@ -408,6 +408,64 @@ class CoreAuthTokenController(CoreBaseModel): # Done here: return success + # CREATED BY OMKAR ------------------------------------------------------------------------------------------------- + # 25 - 06 - 2025 + # SET TOKEN WITH RETURN TOKEN -------------------------------------------------------------------------------------- + + async def set_token_direct_with_return_id( + self, + sql_conn: AsyncMySQL, + mongo_data_conn: AsyncMongo, + auth_token: CoreAuthTokenModel, + token_notes: dict, + display_name: str = None, + display_picture: str = None, + session_token: str = None + ) -> (bool, str) : + + """ + Some authorizations don't need two steps, but our core system works on the 2-step approach that was developed to + work with Google's GMail OAuth2.0 mechanism. + :param sql_conn: The database connection (MariaDB) to use to perform the action. + :param mongo_data_conn: The database connection (MongoDB) to use to perform the action. + :param auth_token: The actual auth/token data to be saved to the database. + :param token_notes: Any notes to feed into MariaDB with the token identifier. + :param display_name: The name of the user to user as their display name. + :param display_picture: The URL at which you will find a display picture of the user. + :param session_token: The session token of the user who requested this service. + :return: True if saved, False if failed. + """ + + # Start by assuming failure: + success = False + + # Get a token id (and receive its key): + token_key = await self.generate_token_key( + sql_conn = sql_conn, + mongo_data_conn = mongo_data_conn, + auth_token = auth_token, + token_notes = token_notes, + display_name = display_name, + display_picture = display_picture, + session_token = session_token + ) + + # Immediately save the details against that token id: + success = await self.set_token( + sql_conn = sql_conn, + mongo_data_conn = mongo_data_conn, + token_key = token_key, + auth_token = auth_token, + token_notes = token_notes, + display_name = display_name, + display_picture = display_picture, + session_token = session_token + ) + + # Done here: + return success, token_key + + async def modify_status_by_token_key( self, sql_conn: AsyncMySQL, diff --git a/controllers_v2/software/ecommerce/__init__.py b/controllers_v2/software/ecommerce/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/controllers_v2/software/ecommerce/base.py b/controllers_v2/software/ecommerce/base.py new file mode 100644 index 0000000..c82d77b --- /dev/null +++ b/controllers_v2/software/ecommerce/base.py @@ -0,0 +1,206 @@ +""" + + AUTHOR: + + Omkar Khandare + + DATE: + + Tuesday, 24th Jun., 2025. + + OBJECTIVE: + + To handle ecommerce authentication. + + 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.string import json +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.core.software import CoreSoftwareController + +# To make very controlled API calls: +from utils_v2.rest.controllers.async_base import AsyncREST +from utils_v2.rest.models.api_call import ApiResponse + +# Models: +from models.software.ecommerce.auth import ( + ShopifyAuth, + ShopifyAuthResponse +) +from models.core.user import CoreUserInfoModel + +# To make HTTP requests: +import httpx + +# to work with MongoDB: +from bson.objectid import ObjectId + +# To make abstract classes: +from abc import ABC, abstractmethod + + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class EcommerceController(CoreSoftwareController, ABC): + + # ┏┓┓ ┓┏ + # ┃ ┃┏┓┏┏ ┃┃┏┓┏┓┏ + # ┗┛┗┗┻┛┛ ┗┛┗┻┛ ┛ + + SERVICE_TYPE = "ecommerce" + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + 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 = "Shopify (C) | ", + debug_only_errors: bool = True + ): + + """ + This is the foundational controller for generate an authentication key for ecommerce integration, + To Validate user from ecommerce webhooks + :param cache: The object to use for caching results from database calls. + :param http_client: The HTTP client to use to make REST-ful API calls. + :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: + shopify_filter = {} + for k, v in (base_filter or {}).items(): shopify_filter[k] = v + shopify_filter["serviceType"] = self.SERVICE_TYPE + + # Invoke the parent's constructor: + CoreSoftwareController.__init__( + self, + cache = cache, + alert_url = alert_url, + http_client = http_client, + base_filter = shopify_filter, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # Init a variable in a parent: + self._service_type = self.SERVICE_TYPE + + # For controlled REST-ful calls: + self._rest = AsyncREST( + http_client = http_client, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # ┏┓ ┓ + # ┣┫┓┏╋┣┓ + # ┛┗┗┻┗┛┗ + + @abstractmethod + async def save_auth( + self, + sql_conn: AsyncMySQL, + mongo_data_conn: AsyncMongo, + auth: ShopifyAuth, + user: CoreUserInfoModel, + session_token: str + ) -> ShopifyAuthResponse: + + """ + Checks if a particular set of incoming credentials give access to a valid server and then stores the + credentials. + :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 auth: The set of credentials as received from the UI/API. + :return: A structured response to indicate what happened during authorization. + """ + + pass + + + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/controllers_v2/software/ecommerce/shopify.py b/controllers_v2/software/ecommerce/shopify.py new file mode 100644 index 0000000..f56aa6c --- /dev/null +++ b/controllers_v2/software/ecommerce/shopify.py @@ -0,0 +1,210 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Wednesday, 15th Jan., 2025. + + OBJECTIVE: + + To handle all WhatsApp-related behaviour for Nimbus IT's service from one place. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +from abc import ABC + + + +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 +from utils_v2.logging.context import AsyncLoggerContext + +# Controllers: +from controllers_v2.software.ecommerce.base import EcommerceController + +# Models: +from models.core.auth_token import CoreAuthTokenModel +from models.core.user import CoreUserInfoModel +from models.core.message import CoreMessageModel +from models.software.ecommerce.auth import ( + ShopifyAuth, + ShopifyAuthResponse +) + +# Chat clients: +from utils_v2.whatsapp.nimbus.controllers.async_nimbus_whatsapp import AsyncNimbusWhatsapp + +# To work with datatypes: +from typing import List, Any + +# To make HTTP requests: +import httpx + +# For asynchronous activities: +import asyncio + +# Common: +from shared import constants + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class ShopifyAppController(EcommerceController): + + # ┏┓┓ ┓┏ + # ┃ ┃┏┓┏┏ ┃┃┏┓┏┓┏ + # ┗┛┗┗┻┛┛ ┗┛┗┻┛ ┛ + + CLIENT_NAME = "shopify" + + # ┏┓ + # ┃ ┏┓┏┓┏╋┏┓┓┏┏╋┏┓┏┓ + # ┗┛┗┛┛┗┛┗┛ ┗┻┗┗┗┛┛ + + def __init__( + self, + cache: AsyncRedisCache = None, + http_client: httpx.AsyncClient = None, + alert_url: str = None, + debug: bool = True, + debug_prefix: str = "Shopify (C) | ", + debug_only_errors: bool = True + ): + + """ + This is the controller for Nimbus IT's WhatsApp service. + :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": self.CLIENT_NAME}, + debug = debug, + debug_prefix = debug_prefix, + debug_only_errors = debug_only_errors + ) + + # Init a variable in a parent: + self._client = self.CLIENT_NAME + + async def save_auth( + self, + sql_conn: AsyncMySQL, + mongo_data_conn: AsyncMongo, + auth: ShopifyAuth, + user: CoreUserInfoModel, + session_token: str + ) -> ShopifyAuthResponse: + + success, object_id = await self.set_token_direct_with_return_id( + sql_conn=sql_conn, + mongo_data_conn=mongo_data_conn, + auth_token=CoreAuthTokenModel( + serviceType=self.SERVICE_TYPE, + client=self.CLIENT_NAME, + authType="auth", + auth=auth.model_dump(), + user=user, + clientUserId={ + "storeName": auth.storeName, + "storeUrl": auth.storeUrl + }, + status="active", + syncFreq=60 + ), + token_notes={ + "storeName": auth.storeName, + "storeUrl": auth.storeUrl + }, + display_name=auth.storeName, + display_picture=None, + session_token=session_token + ) + + # Done here: + return ShopifyAuthResponse( + success=success, + token_id=str(object_id), + message="Shopify Account Added successfully." if success else "Shopify Account Added failed." + ) + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/api/common/integrations/__init__.py b/models/api/common/integrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/api/common/integrations/auth_get.py b/models/api/common/integrations/auth_get.py new file mode 100644 index 0000000..e166d6e --- /dev/null +++ b/models/api/common/integrations/auth_get.py @@ -0,0 +1,141 @@ +""" + + AUTHOR: + + Omkar Khandare + + DATE: + + Wednesday, 25th June., 2025. + + OBJECTIVE: + + To provide a structure to allow users to disable their third-party integration accounts. + + 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, List, Any + +# My utils: +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with MongoDB: +from bson.objectid import ObjectId + +# 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 AuthTokenGetRequestHeaders(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 AuthTokenGetRequestData(BaseModel): + + tokenKey: ObjectId = Field( + description = "The token identifier that tell you which account needs to be disabled.", + frozen = True, + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + arbitrary_types_allowed = True + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + @field_validator("tokenKey", mode = "before") + def parse_oid(cls, value): + try: value = ObjectId(value) + except: value = None + return value + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/api/software/auth.py b/models/api/software/auth.py index 09daf36..90a71c3 100644 --- a/models/api/software/auth.py +++ b/models/api/software/auth.py @@ -42,7 +42,7 @@ from typing import Optional, Literal, Union # Other data models: from models.software.tcaoff_ai.auth import TheCAOfficeAIAuth from models.software.mikrotik.auth import MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth - +from models.software.ecommerce.auth import ShopifyAuth # My utils: from utils_v2.string import regex from utils_v2.date_time import date_time @@ -105,8 +105,8 @@ class SoftwareAuthRequestHeaders(BaseModel): class SoftwareAuthRequestData(BaseModel): - softwareClient: Literal["theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000"] = Field(alias = "client") - auth: Union[TheCAOfficeAIAuth, MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth] + softwareClient: Literal["theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000", "shopify"] = Field(alias = "client") + auth: Union[TheCAOfficeAIAuth, MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth, ShopifyAuth] # ┏┓ ┏• # ┃ ┏┓┏┓╋┓┏┓ @@ -127,7 +127,8 @@ class SoftwareAuthRequestData(BaseModel): harmony_map = { "theCaOfficeAi": TheCAOfficeAIAuth, "mikrotikPPPoE1000": MikroTikPPPoE1000Auth, - "mikrotikHotspot1000": MikroTikHotspot1000Auth + "mikrotikHotspot1000": MikroTikHotspot1000Auth, + "shopify": ShopifyAuth } if not isinstance(auth, harmony_map[client]): raise ValueError(f"incorrect 'auth' for selected client '{client}'") diff --git a/models/core/auth_token.py b/models/core/auth_token.py index 7e28b35..0b03660 100644 --- a/models/core/auth_token.py +++ b/models/core/auth_token.py @@ -99,6 +99,7 @@ class CoreAuthTokenModel(BaseModel): "email", "sms", "chat", # ............. Message "paymentGateway", "stockTrading", # ... Finstitutions "software", # ......................... God knows + "ecommerce", # ......................... God knows ] = Field( description = "the kind of service this message was sent/received from", frozen = True @@ -110,7 +111,8 @@ class CoreAuthTokenModel(BaseModel): "nimbusSmsIndia", "savvyBulkSmsKenya", # ........................ SMS Clients "razorpay", "safaricomMPesaExpress", # .......................... Payment Gateways "zerodhaKite", "iciciBreeze", "paperTrading", # ................. Stock Brokers - "theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000" # ... Software + "theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000", # ... Software + "shopify" ] = Field( description = "the third-part client that was used", frozen = True @@ -245,6 +247,17 @@ class CoreAuthTokenModel(BaseModel): if value is None: value = {} return value + # ┏┓ ┏┓ + # ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏ + # ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛ + + def to_json(self) -> dict: + dump = self.model_dump() + print(dump) + for key in ["batchId", "key", "_id"]: + dump[key] = str(dump[key]) + return dump + # ***************************************************************************************************************** # ***** **** diff --git a/models/software/ecommerce/__init__.py b/models/software/ecommerce/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/software/ecommerce/auth.py b/models/software/ecommerce/auth.py new file mode 100644 index 0000000..b52da15 --- /dev/null +++ b/models/software/ecommerce/auth.py @@ -0,0 +1,163 @@ +""" + + AUTHOR: + + Omkar Khandare + + DATE: + + Tuesday, 24th Jun., 2025. + + OBJECTIVE: + + To provide a structure to receive auth details for ecommerce + + 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, Any, List + + + +# ***************************************************************************************************************** +# ***** **** +# *** 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 *** +# ***** **** +# ***************************************************************************************************************** + +# Not Yet -- + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class ShopifyAuth(BaseModel): + + storeName: str = Field( + description = "Shopify store name or brand name", + frozen = True + ) + + storeUrl: str = Field( + description = "Shopify store URL", + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + + +# --------------------------------------------------------------------------------------------------------------------- + + +# --------------------------------------------------------------------------------------------------------------------- + + +class ShopifyAuthResponse(BaseModel): + + success: bool = Field( + description = "To indicate whether or not, the action was a success", + frozen = False, + default = False + ) + + token_id : str = Field( + description="MongoDb object id", + frozen=False, + default=False + ) + + 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