(20250625) - Implemented the ecommerce shopify auth integration.

with multiple files added new API for get token details
This commit is contained in:
yatmesh
2025-06-25 14:06:02 +05:30
parent 388b9bdd1b
commit c022844824
14 changed files with 1029 additions and 7 deletions
+193
View File
@@ -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
+25 -1
View File
@@ -236,6 +236,29 @@ async def authorize_software_client(
success = response.success success = response.success
message = f"Action Chain '{response.actionChain}': {response.message}" 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, message = message,
data = { data = {
"client": inbound_data.softwareClient, "client": inbound_data.softwareClient,
"authorized": success "authorized": success,
"authorizationUrl": auth_url_success if success else auth_url_failed
} }
) )
+14 -1
View File
@@ -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.all_mikrotik import AllMikroTikController
from controllers_v2.software.mikrotik.mikrotik_pppoe_1000 import MikroTikPPPoE1000Controller from controllers_v2.software.mikrotik.mikrotik_pppoe_1000 import MikroTikPPPoE1000Controller
from controllers_v2.software.mikrotik.mikrotik_hostpot_1000 import MikroTikHotspot1000Controller 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 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.session_token import session_token_bp
from api.blueprints.common.otp.timed_otp import timed_otp_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.otp.timed_otp_test import timed_otp_bp_test
from api.blueprints.common.integrations.get import integration_get_bp
# Tech and Testing Blueprints: # Tech and Testing Blueprints:
from api.blueprints.tech.chat_alerts import tech_chat_alert_bp from api.blueprints.tech.chat_alerts import tech_chat_alert_bp
from api.blueprints.test.callback import test_callback_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(auth_token_disable_bp, url_prefix = f"/{MODULE_BASE}")
app.register_blueprint(session_token_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(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") # app.register_blueprint(timed_otp_bp_test, url_prefix = f"/{MODULE_BASE}/otp/timed")
# AI Blueprints: # AI Blueprints:
@@ -601,6 +603,17 @@ async def app_startup(**kwargs):
) )
current_app.printer("Software/MikroTik (C) ready.") 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: # Common / OTP:
current_app.timed_otp_controller = TimedOTPController( current_app.timed_otp_controller = TimedOTPController(
debug = enable_debugging debug = enable_debugging
+58
View File
@@ -408,6 +408,64 @@ class CoreAuthTokenController(CoreBaseModel):
# Done here: # Done here:
return success 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( async def modify_status_by_token_key(
self, self,
sql_conn: AsyncMySQL, sql_conn: AsyncMySQL,
+206
View File
@@ -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
@@ -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
+141
View File
@@ -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
+5 -4
View File
@@ -42,7 +42,7 @@ from typing import Optional, Literal, Union
# Other data models: # Other data models:
from models.software.tcaoff_ai.auth import TheCAOfficeAIAuth from models.software.tcaoff_ai.auth import TheCAOfficeAIAuth
from models.software.mikrotik.auth import MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth from models.software.mikrotik.auth import MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth
from models.software.ecommerce.auth import ShopifyAuth
# My utils: # My utils:
from utils_v2.string import regex from utils_v2.string import regex
from utils_v2.date_time import date_time from utils_v2.date_time import date_time
@@ -105,8 +105,8 @@ class SoftwareAuthRequestHeaders(BaseModel):
class SoftwareAuthRequestData(BaseModel): class SoftwareAuthRequestData(BaseModel):
softwareClient: Literal["theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000"] = Field(alias = "client") softwareClient: Literal["theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000", "shopify"] = Field(alias = "client")
auth: Union[TheCAOfficeAIAuth, MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth] auth: Union[TheCAOfficeAIAuth, MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth, ShopifyAuth]
# ┏┓ ┏• # ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓ # ┃ ┏┓┏┓╋┓┏┓
@@ -127,7 +127,8 @@ class SoftwareAuthRequestData(BaseModel):
harmony_map = { harmony_map = {
"theCaOfficeAi": TheCAOfficeAIAuth, "theCaOfficeAi": TheCAOfficeAIAuth,
"mikrotikPPPoE1000": MikroTikPPPoE1000Auth, "mikrotikPPPoE1000": MikroTikPPPoE1000Auth,
"mikrotikHotspot1000": MikroTikHotspot1000Auth "mikrotikHotspot1000": MikroTikHotspot1000Auth,
"shopify": ShopifyAuth
} }
if not isinstance(auth, harmony_map[client]): if not isinstance(auth, harmony_map[client]):
raise ValueError(f"incorrect 'auth' for selected client '{client}'") raise ValueError(f"incorrect 'auth' for selected client '{client}'")
+14 -1
View File
@@ -99,6 +99,7 @@ class CoreAuthTokenModel(BaseModel):
"email", "sms", "chat", # ............. Message "email", "sms", "chat", # ............. Message
"paymentGateway", "stockTrading", # ... Finstitutions "paymentGateway", "stockTrading", # ... Finstitutions
"software", # ......................... God knows "software", # ......................... God knows
"ecommerce", # ......................... God knows
] = Field( ] = Field(
description = "the kind of service this message was sent/received from", description = "the kind of service this message was sent/received from",
frozen = True frozen = True
@@ -110,7 +111,8 @@ class CoreAuthTokenModel(BaseModel):
"nimbusSmsIndia", "savvyBulkSmsKenya", # ........................ SMS Clients "nimbusSmsIndia", "savvyBulkSmsKenya", # ........................ SMS Clients
"razorpay", "safaricomMPesaExpress", # .......................... Payment Gateways "razorpay", "safaricomMPesaExpress", # .......................... Payment Gateways
"zerodhaKite", "iciciBreeze", "paperTrading", # ................. Stock Brokers "zerodhaKite", "iciciBreeze", "paperTrading", # ................. Stock Brokers
"theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000" # ... Software "theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000", # ... Software
"shopify"
] = Field( ] = Field(
description = "the third-part client that was used", description = "the third-part client that was used",
frozen = True frozen = True
@@ -245,6 +247,17 @@ class CoreAuthTokenModel(BaseModel):
if value is None: value = {} if value is None: value = {}
return 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
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
+163
View File
@@ -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