diff --git a/api/blueprints/mail/oauth_callback.py b/api/blueprints/mail/oauth_callback.py index 5e529f0..2fdca5b 100644 --- a/api/blueprints/mail/oauth_callback.py +++ b/api/blueprints/mail/oauth_callback.py @@ -113,7 +113,7 @@ def init(blueprint_setup_state): api_version = "1.0.0", project = constants.PROJECT_NAME, log_type = constants.MODULE_NAME, - operation = "gmailCallbk", + operation = "gmailCllbk", log_input = 2, log_output = 1, sensitive_keys = ["sessionToken", "X-Session-Token"] @@ -300,7 +300,7 @@ async def mail_auth_callback( mail_client = mail_client.title(), failure_hint = ( f"Invalid client '{mail_client}' selected. " - "Please use log-id '{g.log_id}' to check with the support team." + f"Please use log-id '{g.log_id}' to check with the support team." ) ) diff --git a/models/behaviour/core/__init__.py b/models/behaviour/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/models/behaviour/core/auth_token.py b/models/behaviour/core/auth_token.py deleted file mode 100644 index bb0bb99..0000000 --- a/models/behaviour/core/auth_token.py +++ /dev/null @@ -1,221 +0,0 @@ -""" - - AUTHOR: - - Khushal P Soonderji - - DATE: - - Thursday, 5th Dec., 2024 - - OBJECTIVE: - - To create an interface between OpenAI and our internal system to perform LLM-based activities. - - 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 - -# Base model: -from models.behaviour.base import BaseModel - -# Data Models: -from models.data.api.ai.llm import LLMInput, LLMOutput, LLMUsageTokens - -# To work with LLMs: -from langchain_openai import ChatOpenAI - -# To work with MongoDB: -from bson import ObjectId - -# To work with datatypes: -from typing import Literal - -# To make deep-copies: -import copy - - -# ***************************************************************************************************************** -# ***** **** -# *** MACROS / ONE-TIME INIT *** -# ***** **** -# ***************************************************************************************************************** - - -# --- Nothing Yet - - -# ***************************************************************************************************************** -# ***** **** -# *** VARIABLES *** -# ***** **** -# ***************************************************************************************************************** - - -# --- Nothing Yet - - -# ***************************************************************************************************************** -# ***** **** -# *** FUNCTIONS *** -# ***** **** -# ***************************************************************************************************************** - - -# --- Nothing Yet - - -# ***************************************************************************************************************** -# ***** **** -# *** CLASSES *** -# ***** **** -# ***************************************************************************************************************** - - -class LLMOpenAI(BaseModel): - - AI_USAGE_COLLECTION = "_aiUsage" - - def __init__( - self, - llm_creds: dict, - cache = None, - alert_url = None, - http_client = None, - debug = True, - debug_prefix = "Model | ", - debug_only_errors = True - ): - - """ - This is the model that works with OpenAi's LLM to perform tasks like text completion. - :param llm_creds: The JSON that holds the credentials to access your OpenAI account. Should have the keys - 'model', and 'openai_api_key'. - :param cache: The object to use for caching results from database calls. - :param alert_url: Which URL to call when something goes wrong. - :param http_client: The instance of an HTTP client to use when trying to send alerts and make other APIs. - :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. - """ - - # Initialize the parent: - super().__init__( - cache = cache, - alert_url = alert_url, - http_client = http_client, - debug = debug, - debug_prefix = debug_prefix, - debug_only_errors = debug_only_errors - ) - - # Create the interface to the LLM: - self.__llm = ChatOpenAI(**llm_creds) - - async def invoke( - self, - mongo_conn: AsyncMongo, - user_info: dict, - llm_input: LLMInput - ) -> LLMOutput: - - # Format the message as per the format of OpenAI: - prompt = [ - { - "role": {"system": "system", "ai": "assistant", "human": "user"}[message.role], - "content": message.content - } for message in llm_input.messages - ] - - # Invoke the AI, and format the response: - llm_response = await self.__llm.ainvoke(prompt) - llm_response = LLMOutput( - messages = llm_input.messages, - output = llm_response.content, - client = "openai", - model = llm_response.response_metadata["model_name"], - tokens = LLMUsageTokens( - input = llm_response.usage_metadata["input_tokens"], - output = llm_response.usage_metadata["output_tokens"], - total = llm_response.usage_metadata["total_tokens"], - ) - ) - - # Store this into MongoDB: - mongo_document = {"user": user_info} - for k, v in llm_response.model_dump().items(): mongo_document[k] = v - inserted_id = await mongo_conn.insert_one( - collection = self.AI_USAGE_COLLECTION, - document = mongo_document - ) - - # Done here: - return llm_response - - -# ***************************************************************************************************************** -# ***** **** -# *** MAIN PROGRAM *** -# ***** **** -# ***************************************************************************************************************** - - -if __name__ == "__main__": - - pass - - # import asyncio - # - # llm_messages = [ - # { - # "role": "system", - # "content": "You are an office assistant." - # }, - # { - # "role": "ai", - # "content": "Hello, sir. How may I help you today?" - # }, - # { - # "role": "human", - # "content": "Please summarize this mail for me..." - # } - # ] - # - # my_llm = LLMOpenAI( - # llm_creds = { - # "model": "gpt-4o-mini", - # "openai_api_key": "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" - # } - # ) - # - # async def main(): - # - # llm_response = await my_llm.invoke(llm_input = LLMInput(messages = llm_messages)) - # print("LLM RESPONSE:", llm_response.model_dump_json(indent = 4)) - # - # asyncio.run(main()) diff --git a/models/behaviour/mail/oauth_v3.py b/models/behaviour/mail/oauth_v3.py index 911bb02..2e8338a 100644 --- a/models/behaviour/mail/oauth_v3.py +++ b/models/behaviour/mail/oauth_v3.py @@ -7,7 +7,7 @@ DATE: ORIGINAL: Monday, 2nd Dec., 2024 - UPGRADE: Monday, 9th Dec., 2024 + UPGRADED: Monday, 9th Dec., 2024 OBJECTIVE: @@ -134,9 +134,9 @@ class MailOAuthModel(BaseModel): }), update = { "$set": { - "lastRequestTs": request_ts, + "lastRequestTs": auth_token.lastRequestTs, "status": auth_token.status, - "syncFreq": max(auth_token.syncFreq, 60) + "syncFreq": auth_token.syncFreq }, "$setOnInsert": { "version": auth_token.version, @@ -149,7 +149,7 @@ class MailOAuthModel(BaseModel): "token": auth_token.token, "firstRefreshTs": auth_token.firstRefreshTs, "lastRefreshTs": auth_token.lastRefreshTs, - "firstRequestTs": auth_token.firstRequestTs, + "firstRequestTs": auth_token.firstRequestTs or request_ts, } }, projection = { @@ -168,7 +168,7 @@ class MailOAuthModel(BaseModel): proc_args = ( auth_token.user.entityId, # ......................................... 'p_entity_id' auth_token.client, # ................................................ 'p_provider' - "Pending", # ........................................................ 'p_current_status' + auth_token.status, # ................................................ 'p_current_status' "Auth Requested", # ................................................. 'p_last_action' None, # ............................................................. 'p_display_name' None, # ............................................................. 'p_display_picture' @@ -254,7 +254,7 @@ class MailOAuthModel(BaseModel): proc_args = ( mongo_json["user"]["entityId"], # ............................... 'p_entity_id' mongo_json["client"], # ......................................... 'p_provider' - "Active", # ..................................................... 'p_current_status' + auth_token.status, # ............................................ 'p_current_status' "Auth Granted", # ............................................... 'p_last_action' auth_token.token.get("displayName"), # .......................... 'p_display_name' auth_token.token.get("displayPictureUrl"), # .................... 'p_display_picture' diff --git a/models/behaviour/sms/auth_v2.py b/models/behaviour/sms/auth_v2.py new file mode 100644 index 0000000..238e801 --- /dev/null +++ b/models/behaviour/sms/auth_v2.py @@ -0,0 +1,227 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + ORIGINAL: Thursday, 5th Dec., 2024 + UPGRADED: Monday, 9th Dec., 2024 + + OBJECTIVE: + + To work with auth details of SMS clients like Nimbus SMS (India) and Savvy Bulk SMS (Kenya). + + 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 + +# Base model: +from models.behaviour.base import BaseModel + +# Data models: +from models.data.core.auth_token import CoreAuthTokenModel + +# To work with MongoDB: +from bson import ObjectId + +# To work with datatypes: +from typing import Literal + +# To make deep-copies: +import copy + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** CLASSES *** +# ***** **** +# ***************************************************************************************************************** + + +class SMSAuthModel(BaseModel): + + AUTH_COLLECTION = "_authTokens" + + async def set( + self, + db_conn: AsyncMySQL, + mongo_conn: AsyncMongo, + auth_token: CoreAuthTokenModel, + session_token: str = None + ) -> ObjectId | None: + + """ + To store auth/tokens for a particular service to the database. + :param db_conn: The database connection (MariaDB) to use to perform the action. + :param mongo_conn: The database connection (MongoDB) to use to perform the action. + :param auth_token: An instance of the core auth-token model that holds data in the database. + :param session_token: The session token of the user who requested this service. + :return: An ObjectId to later store the granted tokens. + """ + + # Note down the timestamp at which this event occurred: + request_ts = date_time.get_current_utc_date_time(as_string = False) + + # Get the identifier from the database: + # BE CAREFUL WITH THE KEYS HERE, THEY SHOULD MATCH THE FIELDS OF THE CORE AUTH-TOKEN MODEL: + mongo_json = await mongo_conn.find_one_and_update( + collection = self.AUTH_COLLECTION, + filter = mongo_conn.dict_to_dot_notation({ + "serviceType": "email", + "user": { + "entityId": auth_token.user.entityId, + "billingAccountId": auth_token.user.billingAccountId + }, + "clientUserId": auth_token.clientUserId + }), + update = { + "$set": { + "lastRequestTs": auth_token.lastRequestTs, + "status": auth_token.status, + "syncFreq": auth_token.syncFreq + }, + "$setOnInsert": { + "version": auth_token.version, + "serviceType": auth_token.serviceType, + "client": auth_token.client, + "authType": auth_token.authType, + "user": auth_token.user.model_dump(), + "clientUserId": auth_token.clientUserId, + "auth": auth_token.auth, + "token": auth_token.token, + "firstRefreshTs": auth_token.firstRefreshTs, + "lastRefreshTs": auth_token.lastRefreshTs, + "firstRequestTs": auth_token.firstRequestTs or request_ts + } + }, + projection = { + "_id": True + }, + upsert = True, + return_updated = True + ) + + # Tell MariaDB that an authorization request was initiated: + db_json = {} + if mongo_json is not None: + token_notes = auth_token.clientUserId + db_json = await self.call_procedure( + db_conn = db_conn, + proc_name = "entity_integration_save", + proc_args = ( + auth_token.user.entityId, # ..................................... 'p_entity_id' + auth_token.client, # ............................................ 'p_provider' + auth_token.status, # ............................................ 'p_current_status' + "Auth Details Accepted", # ...................................... 'p_last_action' + None, # ......................................................... 'p_display_name' + None, # ......................................................... 'p_display_picture' + str(mongo_json["_id"]), # ....................................... 'p_token_id' + json.to_string(python_data = token_notes, no_space = True), # ... 'p_notes' + auth_token.user.userId # ........................................ 'p_created_by' + ), + session_token = session_token + ) + + # Done here: + return mongo_json["_id"] if mongo_json and db_json.get("status") == 1 else None + + async def get( + self, + mongo_conn: AsyncMongo, + token_id: ObjectId | str = None, + **kwargs + ) -> dict | None: + + """ + To retrieve stored auth/tokens from the database. + :param mongo_conn: The database connection (MongoDB) to use to perform the action. + :param token_id: The identifier granted providing auth details for the first time in 'set_token'. + :param kwargs: Any set of key-value pairs to build custom search criteria. This could be things like the user + info, the client, the type of authentication used, or even the kind of service. + :return: The retrieved record that has the token, and information about the service and client if found, else + None when there is no matching record. + """ + + # Build the filter: + filter_json = {k: v for k, v in kwargs.items()} + if token_id: filter_json["_id"] = ObjectId(token_id) + + # If there is no search criteria, we exit with failure: + if not filter_json: return None + + # If there is some filtering possible, we fetch the token: + token = await mongo_conn.find_one( + collection = self.AUTH_COLLECTION, + filter = filter_json, + ) + + # Done here: + return CoreAuthTokenModel(**token) if token else None + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/data/core/auth_token.py b/models/data/core/auth_token.py index 20f2784..ad5155b 100644 --- a/models/data/core/auth_token.py +++ b/models/data/core/auth_token.py @@ -112,13 +112,13 @@ class CoreAuthTokenModel(BaseModel): firstRequestTs: AwareDatetime = Field( description = "the time (utc) at which authorization was first requested", frozen = True, - default_factory = lambda: date_time.get_current_utc_date_time(as_string = False) + default = None ) lastRequestTs: AwareDatetime = Field( description = "the time (utc) at which authorization was last requested", frozen = False, - default = None + default_factory = lambda: date_time.get_current_utc_date_time(as_string = False) ) firstRefreshTs: AwareDatetime = Field( diff --git a/servers.txt b/servers.txt index 0e4e6c2..654999b 100644 --- a/servers.txt +++ b/servers.txt @@ -1,4 +1,3 @@ -kbdev.bicree.com +jc.ditscentre.in wtt.ditscentre.in -del.ditscentre.in -jc.ditscentre.in \ No newline at end of file +del.ditscentre.in \ No newline at end of file