""" AUTHOR: Khushal P Soonderji DATE: Tuesday, 3rd Dec., 2024 OBJECTIVE: To enlist multiple e-mails for a given user at a time. 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 # My utils: from utils_v2.string import json from utils_v2.database.async_mongo_v2 import AsyncMongo 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.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAGEMENT from utils_v2.goog.models.data.auth_tokens import GoogleAuthTokens # Common: from shared import constants # Data Models: from models.data.mail.sync import MailSyncRequestHeaders, MailSyncRequestData from models.data.mail.sync import MailSyncOneResult, MailSyncManyResults # To work with datatypes: from typing import Literal # For asynchronous activities: import asyncio # To work with LLMs: from langchain_openai import ChatOpenAI # To work with date and time: import datetime # ***************************************************************************************************************** # ***** **** # *** MACROS / ONE-TIME INIT *** # ***** **** # ***************************************************************************************************************** # Related to Quart: mail_sync_bp = Blueprint("mail_sync", __name__) # ***************************************************************************************************************** # ***** **** # *** VARIABLES *** # ***** **** # ***************************************************************************************************************** # --- Nothing Yet # ***************************************************************************************************************** # ***** **** # *** FUNCTIONS *** # ***** **** # ***************************************************************************************************************** @mail_sync_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 # --------------------------------------------------------------------------------------------------------------------- async def sync_mails( mongo_conn: AsyncMongo, llm: ChatOpenAI, inbound_headers: dict, inbound_data: MailSyncRequestData ) -> MailSyncManyResults: """ A very simple function, but kept separate so that we get the option to switch between running it in the foreground and running it in the background. :param mongo_conn: The instance of the database connector to use to sync the mails. :param llm: The instance of the LLM to use to summarize the mails. :param inbound_headers: The headers that came in with the request. :param inbound_data: The data that came in with the request. :return: The results of the mail-sync'ing attempt. """ # Try to sync the mails: return await current_app.mail_sync_model.sync( session_token = inbound_headers["X-Session-Token"], mongo_conn = mongo_conn, account_identifier = inbound_data.accountId, llm = llm, force_sync = inbound_data.forceSync, start_date = inbound_data.startDate, end_date = inbound_data.endDate, max_count = inbound_data.maxCount ) # --------------------------------------------------------------------------------------------------------------------- @mail_sync_bp.route("/sync", methods = ["POST"]) @mail_sync_bp.route("/sync/", methods = ["POST"]) @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: MailSyncRequestHeaders(**x).model_dump(), data_validator = lambda x: MailSyncRequestData(**x) ) @handle_cancelled_request() async def sync_mail( mode: Literal["background", "bg"] = None, inbound_headers: dict | MailSyncRequestHeaders = None, inbound_data: dict | MailSyncRequestData = None, inbound_files: dict = None, **kwargs ): """ Use this when the user wants to pull old mails from some mail client (like GMail) and save it to the database for ready access on the UI. :param mode: Set it to one of the specified options to make the sync'ing process go to the background. :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 ) # Make the variables available in the scope of the current request: g.inbound_headers = inbound_headers g.inbound_data = inbound_data # If we've been asked to sync the mails in the background: if mode in ["background", "bg"]: current_app.add_background_task( sync_mails, mongo_conn = current_app.data_mongo, llm = current_app.llm, inbound_headers = inbound_headers, inbound_data = inbound_data ) return ResponseModel( status_code = StatusCodes.OK, http_code = HttpCodes.ACCEPTED, message = "your mails are being sync'd in the background" ) # Otherwise we process it right here: sync_results = await sync_mails( mongo_conn = current_app.data_mongo, llm = current_app.llm, inbound_headers = inbound_headers, inbound_data = inbound_data ) # Response: return ResponseModel( status_code = StatusCodes.FAILED if sync_results.failureCount > 0 else StatusCodes.OK, http_code = HttpCodes.INTERNAL_SERVER_ERROR if sync_results.failureCount > 0 else HttpCodes.SUCCESS, message = sync_results.message, data = { "totalCount": sync_results.totalCount, "successCount": sync_results.successCount, "failureCount": sync_results.failureCount } ) # ***************************************************************************************************************** # ***** **** # *** MAIN PROGRAM *** # ***** **** # ***************************************************************************************************************** if __name__ == "__main__": pass