diff --git a/api/blueprints/mail/oauth_callback.py b/api/blueprints/mail/oauth_callback.py index e479d85..5e529f0 100644 --- a/api/blueprints/mail/oauth_callback.py +++ b/api/blueprints/mail/oauth_callback.py @@ -138,36 +138,38 @@ async def handle_gmail_callback() -> render_template: # Generate the tokens from the callback. Google sends all the needed params in the callback as the URL's query # params. We can simply use the exact URL that was hit to generate the tokens. In Quart (and Flask) this can be # achieved by 'request.url' like this: - tokens = await current_app.gmail_client.get_authorization_tokens( + google_tokens = await current_app.gmail_client.get_authorization_tokens( redirect_url = request.url, - # scopes = g.inbound_data["scope"].split(" ") scopes = None ) - if tokens: + if google_tokens: # Get the e-mail id that granted authorization. We will be comparing this to the e-mail id that had been given # to us when the authorization was initiated. We don't mind any e-mail id being used, but we need them to be the # same at both ends: - user_profile = await current_app.gmail_client.get_user_profile(tokens = tokens) + user_profile = await current_app.gmail_client.get_user_profile(tokens = google_tokens) if user_profile.success: - tokens.email = user_profile.data["emailAddress"] - tokens.displayName = user_profile.data["displayName"] - tokens.displayPictureUrl = user_profile.data["displayPictureUrl"] + google_tokens.email = user_profile.data["emailAddress"] + google_tokens.displayName = user_profile.data["displayName"] + google_tokens.displayPictureUrl = user_profile.data["displayPictureUrl"] # Here's where we do the checking of the e-mails, # if they don't match, we reject the authorization: - placeholder_token = await current_app.mail_oauth_model.get_token( + auth_token = await current_app.mail_oauth_model.get_token( mongo_conn = current_app.data_mongo, token_id = g.inbound_data["state"] ) if ( - (not placeholder_token) or - placeholder_token["clientUserId"]["email"] != str(tokens.email) + (not auth_token) or + auth_token.clientUserId["email"] != str(google_tokens.email) ): return await render_template( "/mail/oauth/oauth_failure_v2.html", mail_client = g.mail_client.title(), - failure_hint = f"We were expecting authorization from '{placeholder_token['clientUserId']['email']}' but got authorization from '{tokens.email}' instead." + failure_hint = ( + f"We were expecting authorization from '{auth_token.clientUserId['email']}', " + f"but got authorization from '{google_tokens.email}' instead." + ) ) # We create standard labels that we will use: @@ -190,7 +192,7 @@ async def handle_gmail_callback() -> render_template: ] tasks = [ current_app.gmail_client.create_label( - tokens = tokens, + tokens = google_tokens, label_name = label["name"], label_visibility = "labelShow", message_visibility = "show", @@ -201,18 +203,20 @@ async def handle_gmail_callback() -> render_template: client_responses = await asyncio.gather(*tasks) # Add the labels to the tokens data: - client_response = await current_app.gmail_client.list_labels(tokens = tokens) - tokens.labels = client_response.data if client_response.success else None + client_response = await current_app.gmail_client.list_labels(tokens = google_tokens) + google_tokens.labels = client_response.data if client_response.success else None # Now that we have passed the check, # we save the tokens to the database: + auth_token.clientUserId = google_tokens.client_user_id + auth_token.token = google_tokens.model_dump() + auth_token.status = "active" tokens_saved = await current_app.mail_oauth_model.set_token( db_conn = current_app.sql_writer, mongo_conn = current_app.data_mongo, session_token = g.inbound_headers.get("X-Session-Token"), token_id = g.inbound_data["state"], - client_user_id = tokens.client_user_id, - token = tokens.model_dump() + auth_token = auth_token ) # Return an HTML response for success: @@ -294,7 +298,10 @@ async def mail_auth_callback( return await render_template( "/mail/oauth/oauth_failure_v2.html", 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." + failure_hint = ( + f"Invalid client '{mail_client}' selected. " + "Please use log-id '{g.log_id}' to check with the support team." + ) ) diff --git a/api/blueprints/mail/oauth_request.py b/api/blueprints/mail/oauth_request.py index 2ad9cd3..287696f 100644 --- a/api/blueprints/mail/oauth_request.py +++ b/api/blueprints/mail/oauth_request.py @@ -71,6 +71,7 @@ from models.data.api.mail.oauth import ( OAuthMailAuthorizationRequestHeaders, OAuthMailAuthorizationRequestData ) +from models.data.core.auth_token import CoreAuthTokenModel # For asynchronous activities: import asyncio @@ -169,22 +170,24 @@ async def request_oauth_authorization_url( # Start by assuming failure: auth_url = None - # ┳ ┓ •┏ ┳┳ - # ┃┏┫┏┓┏┓╋┓╋┓┏ ┃┃┏┏┓┏┓ - # ┻┗┻┗ ┛┗┗┗┛┗┫ ┗┛┛┗ ┛ - # ┛ + # ┏┓ ┏┳┓ ┓ ┳ ┓ + # ┃┓┏┓┏┓┏┓┏┓┏┓╋┏┓ ┃ ┏┓┃┏┏┓┏┓ ┃┏┫ + # ┗┛┗ ┛┗┗ ┛ ┗┻┗┗ ┻ ┗┛┛┗┗ ┛┗ ┻┗┻ # Make a user identifier from the session info: token_id = await current_app.mail_oauth_model.get_token_id( db_conn = current_app.sql_writer, mongo_conn = current_app.data_mongo, - session_token = inbound_headers["X-Session-Token"], - user_info = kwargs["session_info"], - client_user_id = {"email": inbound_data.mailId}, - auth = None, - service_client = inbound_data.mailClient, - auth_type = "oauth", - sync_freq = inbound_data.syncFreq + auth_token = CoreAuthTokenModel( + serviceType = "email", + client = inbound_data.mailClient, + authType = "oauth", + user = kwargs["session_info"], + clientUserId = {"email": inbound_data.mailId}, + status = "pending", + syncFreq = inbound_data.syncFreq, + ), + session_token = inbound_headers["X-Session-Token"] ) if token_id is None: return ResponseModel( diff --git a/api/blueprints/test/callback.py b/api/blueprints/test/callback.py index 5aca1f0..48ee9f6 100644 --- a/api/blueprints/test/callback.py +++ b/api/blueprints/test/callback.py @@ -111,6 +111,7 @@ def init(blueprint_setup_state): @test_callback_bp.route("/callback", methods = ["POST", "GET"]) @set_api_version(api_version = "1.0.0") @read_input(sanitize_headers = False, sanitize_data = False) +@get_session_info(key = "X-Session-Token", session_coro = "get_session") @log_request_to_mongo( attr_name = "logs_mongo", project = constants.PROJECT_NAME, @@ -118,7 +119,7 @@ def init(blueprint_setup_state): operation = "testCllBckApi", log_input = True, log_output = True, - sensitive_keys = None + sensitive_keys = ["sessionToken", "X-Session-Token"] ) @log_chain_to_mongo(attr_name = "logs_mongo") @should_not_be_under_maintenance(attr_name = "is_under_maintenance") @@ -130,6 +131,8 @@ async def callback_test( **kwargs ): + print("SESSION INFO:", kwargs.get("session_info")) + # Return a random page: return await render_template( random.choice([ diff --git a/api/helpers/user/session.py b/api/helpers/user/session.py index dde608d..0543a9b 100644 --- a/api/helpers/user/session.py +++ b/api/helpers/user/session.py @@ -37,6 +37,9 @@ sys.path.append("..") # To use Quart: from quart import current_app +# The data model: +from models.data.core.user_info import CoreUserInfoModel + # ***************************************************************************************************************** # ***** **** @@ -84,18 +87,27 @@ async def get_session(session_token): """ try: + + # Fetch the raw info from cache: raw_info = await current_app.module_cache.get(key = session_token) - session_info = { - "fullName": raw_info["value"]["full_name"], - "userId": raw_info["value"]["user_id"], - "entityId": raw_info["value"]["entity_id"], - "billingAccountId": raw_info["value"]["billing_account_id"], - "departmentId": raw_info["value"]["department_id"], - "branchId": raw_info["value"]["branch_id"], - "industry": raw_info["value"]["industry"] - } + + # Feed needed field into the core model: + session_info = CoreUserInfoModel( + fullName = raw_info["value"]["full_name"], + userId = raw_info["value"]["user_id"], + entityId = raw_info["value"]["entity_id"], + billingAccountId = raw_info["value"]["billing_account_id"], + departmentId = raw_info["value"]["department_id"], + branchId = raw_info["value"]["branch_id"], + industry = raw_info["value"]["industry"] + ) + + # Done here: return session_info + + # In case something goes wrong: except Exception as exception: + current_app.printer(exception) return None diff --git a/api/main.py b/api/main.py index 3d5cd01..427244e 100644 --- a/api/main.py +++ b/api/main.py @@ -71,7 +71,7 @@ from utils_v2.api.async_quart import ( from utils_v2.goog.gmail.gmail_client import AsyncGMailClient # Behaviour Models: -from models.behaviour.mail.oauth_v2 import MailOAuthModel +from models.behaviour.mail.oauth_v3 import MailOAuthModel from models.behaviour.mail.sync_v2 import MailSyncModel from models.behaviour.mail.retrieve import MailRetrieveModel from models.behaviour.sms.auth import SMSAuthModel diff --git a/models/behaviour/mail/oauth_v3.py b/models/behaviour/mail/oauth_v3.py index 04d66e9..911bb02 100644 --- a/models/behaviour/mail/oauth_v3.py +++ b/models/behaviour/mail/oauth_v3.py @@ -6,7 +6,8 @@ DATE: - Monday, 2nd Dec., 2024 + ORIGINAL: Monday, 2nd Dec., 2024 + UPGRADE: Monday, 9th Dec., 2024 OBJECTIVE: @@ -44,6 +45,9 @@ 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 @@ -99,12 +103,7 @@ class MailOAuthModel(BaseModel): self, db_conn: AsyncMySQL, mongo_conn: AsyncMongo, - user_info: dict, - client_user_id: dict, - auth: dict, - service_client: Literal["gmail"], - auth_type: Literal["oauth"], - sync_freq: Literal[60, 300, 900] = 300, + auth_token: CoreAuthTokenModel, session_token: str = None ) -> ObjectId: @@ -113,13 +112,7 @@ class MailOAuthModel(BaseModel): user requests an authorization URL to link your service to another service (like GMail). :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 user_info: The dictionary that has the user's session information. - :param client_user_id: The way the third-party client recognizes your user. - :param auth: The authentication details of the account. - :param service_client: The name of the company or brand that is providing this service that is being integrated. - :param auth_type: To identify the type of authentication being done here. This could indicate simple password - authentication, more advance OAuth2.0 authentication, etc. - :param sync_freq: The time interval in which mails need to be sync'd. Specify this in seconds. + :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. """ @@ -127,35 +120,36 @@ class MailOAuthModel(BaseModel): # 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: + # 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 = MailOAuthModel.AUTH_COLLECTION, filter = mongo_conn.dict_to_dot_notation({ "serviceType": "email", "user": { - "entityId": user_info["entityId"], - "billingAccountId": user_info["billingAccountId"] + "entityId": auth_token.user.entityId, + "billingAccountId": auth_token.user.billingAccountId }, - "clientUserId": client_user_id + "clientUserId": auth_token.clientUserId }), update = { "$set": { "lastRequestTs": request_ts, - "status": "active", - "syncFreq": max(sync_freq, 60) + "status": auth_token.status, + "syncFreq": max(auth_token.syncFreq, 60) }, "$setOnInsert": { - "version": "1.1.1", - "serviceType": "email", - "client": service_client, - "authType": auth_type, - "user": user_info, - "clientUserId": client_user_id, - "auth": auth, - "token": None, - "firstRefreshTs": None, - "lastRefreshTs": None, - "firstRequestTs": request_ts, + "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, } }, projection = { @@ -172,15 +166,15 @@ class MailOAuthModel(BaseModel): db_conn = db_conn, proc_name = "entity_integration_save", proc_args = ( - user_info["entityId"], # ............................................ 'p_entity_id' - service_client, # ................................................... 'p_provider' + auth_token.user.entityId, # ......................................... 'p_entity_id' + auth_token.client, # ................................................ 'p_provider' "Pending", # ........................................................ 'p_current_status' "Auth Requested", # ................................................. 'p_last_action' None, # ............................................................. 'p_display_name' None, # ............................................................. 'p_display_picture' str(mongo_json["_id"]), # ........................................... 'p_token_id' json.to_string(python_data = {"email": None}, no_space = True), # ... 'p_notes' - user_info["userId"] # ............................................... 'p_created_by' + auth_token.user.userId # ............................................ 'p_created_by' ), session_token = session_token ) @@ -193,8 +187,7 @@ class MailOAuthModel(BaseModel): db_conn: AsyncMySQL, mongo_conn: AsyncMongo, token_id: ObjectId | str, - client_user_id: dict, - token: dict, + auth_token: CoreAuthTokenModel, session_token: str = None ) -> bool: @@ -205,9 +198,7 @@ class MailOAuthModel(BaseModel): :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 token_id: The identifier granted by the 'get_token_id' method. - :param client_user_id: The way the third-party client recognizes your user. These details should match the - details furnished while requesting the authorization through 'get_token_id' method. - :param token: The token granted by the third-party service. + :param auth_token: The actual auth/token data to be saved to the database. :param session_token: The session token of the user who requested this service. :return: True if saved, False if failed. """ @@ -218,17 +209,18 @@ class MailOAuthModel(BaseModel): # Note down the timestamp at which this event occurred: request_ts = date_time.get_current_utc_date_time(as_string = False) - # Save the token to MongoDB: + # Save the token to MongoDB. + # 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 = MailOAuthModel.AUTH_COLLECTION, filter = mongo_conn.dict_to_dot_notation({ "_id": ObjectId(token_id), - "clientUserId": client_user_id + "clientUserId": auth_token.clientUserId }), update = [{ "$set": { - "token": token, - "status": "active", + "token": auth_token.token, + "status": auth_token.status, "lastRefreshTs": request_ts, "firstRefreshTs": { "$cond": { @@ -252,9 +244,9 @@ class MailOAuthModel(BaseModel): # Tell MariaDB that the token was saved: if mongo_json is not None: token_notes = { - "email": token["email"], - "displayName": token.get("displayName"), - "displayPictureUrl": token.get("displayPictureUrl"), + "email": auth_token.token["email"], + "displayName": auth_token.token.get("displayName"), + "displayPictureUrl": auth_token.token.get("displayPictureUrl"), } db_json = await self.call_procedure( db_conn = db_conn, @@ -264,11 +256,11 @@ class MailOAuthModel(BaseModel): mongo_json["client"], # ......................................... 'p_provider' "Active", # ..................................................... 'p_current_status' "Auth Granted", # ............................................... 'p_last_action' - token["displayName"], # ......................................... 'p_display_name' - token["displayPictureUrl"], # ................................... 'p_display_picture' + auth_token.token.get("displayName"), # .......................... 'p_display_name' + auth_token.token.get("displayPictureUrl"), # .................... 'p_display_picture' token_id, # ..................................................... 'p_token_id' json.to_string(python_data = token_notes, no_space = True), # ... 'p_notes' - mongo_json["user"]["userId"] # .................................. 'p_created_by' + auth_token.user.userId # ........................................ 'p_created_by' ), session_token = session_token ) @@ -282,7 +274,7 @@ class MailOAuthModel(BaseModel): mongo_conn: AsyncMongo, token_id: ObjectId | str = None, **kwargs - ) -> dict | None: + ) -> CoreAuthTokenModel | None: """ To retrieve stored tokens from the database. @@ -301,21 +293,15 @@ class MailOAuthModel(BaseModel): # If there is no search criteria, we exit with failure: if not filter_json: return None - # If there is some filtering possible, - # we fetch and return the token: - return await mongo_conn.find_one( + # If there is some filtering possible, we fetch the token: + token = await mongo_conn.find_one( collection = self.AUTH_COLLECTION, filter = filter_json, - projection = { - "_id": True, - "serviceType": True, - "authType": True, - "client": True, - "clientUserId": True, - "token": True - } ) + # Done here: + return CoreAuthTokenModel(**token) if token else None + # ***************************************************************************************************************** # ***** **** diff --git a/models/data/database/__init__.py b/models/data/core/__init__.py similarity index 100% rename from models/data/database/__init__.py rename to models/data/core/__init__.py diff --git a/models/data/database/auth_token.py b/models/data/core/auth_token.py similarity index 89% rename from models/data/database/auth_token.py rename to models/data/core/auth_token.py index 630a1b9..20f2784 100644 --- a/models/data/database/auth_token.py +++ b/models/data/core/auth_token.py @@ -43,6 +43,9 @@ from typing import Optional, Literal, Union from utils_v2.string import regex from utils_v2.date_time import date_time +# Other core models: +from models.data.core.user_info import CoreUserInfoModel + # To work with MongoDB: from bson.objectid import ObjectId @@ -108,31 +111,41 @@ class CoreAuthTokenModel(BaseModel): firstRequestTs: AwareDatetime = Field( description = "the time (utc) at which authorization was first requested", - frozen = True + frozen = True, + default_factory = lambda: date_time.get_current_utc_date_time(as_string = False) ) lastRequestTs: AwareDatetime = Field( description = "the time (utc) at which authorization was last requested", - frozen = False + frozen = False, + default = None ) firstRefreshTs: AwareDatetime = Field( description = "the time (utc) at which the tokens were first refreshed", - frozen = False + frozen = False, + default = None ) lastRefreshTs: AwareDatetime = Field( description = "the time (utc) at which the tokens were last refreshed", - frozen = False + frozen = False, + default = None + ) + + auth: dict | None = Field( + description = "any direct auth details like api keys or passwords; will differ for each client", + frozen = True, + default = None ) token: dict | None = Field( description = "the actual auth tokens of that client; will differ for each client", frozen = True, - default_factory = lambda: date_time.get_current_utc_date_time(as_string = False) + default = None ) - user: dict = Field( + user: CoreUserInfoModel = Field( description = "how you identify your user", frozen = True ) @@ -142,6 +155,18 @@ class CoreAuthTokenModel(BaseModel): frozen = True ) + status: Literal["pending", "active", "disabled"] = Field( + description = "to indicate the status of this account", + frozen = False, + default = "pending" + ) + + syncFreq: Literal[60, 300, 1500] = Field( + description = "the no. of seconds after which to poll for updates from the client (if applicable)", + frozen = False, + default = 300 + ) + # ┏┓ ┏• # ┃ ┏┓┏┓╋┓┏┓ # ┗┛┗┛┛┗┛┗┗┫ diff --git a/models/data/database/message.py b/models/data/core/message.py similarity index 100% rename from models/data/database/message.py rename to models/data/core/message.py diff --git a/models/data/database/payment.py b/models/data/core/payment.py similarity index 65% rename from models/data/database/payment.py rename to models/data/core/payment.py index 82d3c4c..0aa234f 100644 --- a/models/data/database/payment.py +++ b/models/data/core/payment.py @@ -37,7 +37,7 @@ sys.path.append("..") # For making data behaviour_models: from pydantic import BaseModel, Field, field_validator, PastDatetime, AwareDatetime -from typing import Optional, Literal, Union +from typing import Optional, Literal, Union, List # My utils: from utils_v2.string import regex @@ -80,6 +80,50 @@ import pycountry # ***************************************************************************************************************** +class PaymentEvent(BaseModel): + + eventTs: AwareDatetime = Field( + description = "to know the date and time (utc) of this update", + frozen = True + ) + + initByPG: bool = Field( + description = "to figure out whether the payment gateway initiated this event or we did", + frozen = True + ) + + httpCode: int | None = Field( + description = "the http code generated by the event", + frozen = True, + examples = [200, 400, 401] + ) + + payload: dict = Field( + description = "the json payload or set of query params received from an event from the payment gateway", + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "allow" + arbitrary_types_allowed = True + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + @field_validator("eventTs", mode = "before") + def parse_date_time(cls, value): + return date_time.parse_date_time(input_value = value, timezone = date_time.TIMEZONE_UTC) + + +# --------------------------------------------------------------------------------------------------------------------- + + class CorePaymentModel(BaseModel): version: str = Field( @@ -89,22 +133,22 @@ class CorePaymentModel(BaseModel): default = "1.0.0" ) - paymentStatus: Literal["requested", "paid", "rejected"] = Field( + paymentStatus: Literal[ + "initFailed", # ... When we tried to initiate the request, but the payment gateway (PG) rejected it. + "initiated", # .... When we made a successful payment request, or the customer initiated one from the PG. + "failed", # ....... When the customer tried paying, but it failed (e.g.: because of an incorrect pin). + "rejected", # ..... When the customer explicitly rejected the payment. + "authorized", # ... When the customer made the payment (but it hasn't been settled in your account yet). + "settled", # ...... When the PG sends the money to your account. + "refunded", # ..... When the money was refunded to the client. + ] = Field( description = "the status of the payment request to see what stage of the process we are in", - frozen = False, - default = "requested" + frozen = False ) - requestTs: AwareDatetime = Field( - description = "the time (utc) at which the payment request was initiated", - frozen = True, - default_factory = lambda: date_time.get_current_utc_date_time(as_string = False) - ) - - responseTs: AwareDatetime | None = Field( - description = "the time (utc) at which the payer responded to the payment request", - frozen = False, - default = None + lastEventTs: AwareDatetime = Field( + description = "the time (utc) at which the latest payment event occurred", + frozen = True ) tokenId: ObjectId = Field( @@ -122,7 +166,7 @@ class CorePaymentModel(BaseModel): examples = ["INR", "USD", "KES"] ) - metadata: dict = Field( + metadata: dict | None = Field( description = "any arbitrary amount of data to identify the user and payment details", frozen = True ) @@ -138,28 +182,9 @@ class CorePaymentModel(BaseModel): default = None ) - clientPaymentRequestHttpCode: int | str = Field( - description = "the http code the third-party client returned when you requested the payment", - frozen = False, - default = None - ) - - clientPaymentRequestJSON: str | int = Field( - description = "how the third-party client responded when you requested the payment", - frozen = False, - default = None - ) - - clientPaymentResponseHttpCode: int | str = Field( - description = "the http code the third-party client returned when your user responded to the payment request", - frozen = False, - default = None - ) - - clientPaymentResponseJSON: str | int | None = Field( - description = "how the third-party client responded when your user responded to the payment request", - frozen = False, - default = None + events: List[PaymentEvent] = Field( + description = "an array of all the events that happened in the process of this payment", + frozen = False ) # ┏┓ ┏• @@ -175,7 +200,7 @@ class CorePaymentModel(BaseModel): # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ - @field_validator("requestTs", "responseTs", mode = "before") + @field_validator("lastEventTs", mode = "before") def parse_date_time(cls, value): return date_time.parse_date_time(input_value = value, timezone = date_time.TIMEZONE_UTC) @@ -203,16 +228,42 @@ if __name__ == "__main__": from utils_v2.string import json + now = date_time.get_current_utc_date_time(as_string = False) + payment = CorePaymentModel( - paymentStatus = "requested", + paymentStatus = "authorized", tokenId = "67519cf3a7804fcbc6f12452", amount = 1.00, currencyCode = "INR", metadata = { "userId": 1, - "name": "My Test" + "name": "Bhopli" }, - client = "safaricomMPesaExpress" + client = "razorpay", + clientPaymentReferenceId = "txn_123_abc", + lastEventTs = now, + events = [ + PaymentEvent( + eventTs = now - datetime.timedelta(minutes = 1, seconds = 12), + initByPG = True, + httpCode = None, + payload = { + "status": "captured", + "from": "Barfi", + } + ), + PaymentEvent( + eventTs = now, + initByPG = True, + httpCode = None, + payload = { + "status": "authorized", + "from": "Barfi", + "amount": -100.00, + "description": "meow" + } + ) + ] ) print("PAYMENT TXN. MODEL:", json.to_string(payment.model_dump(), default = str)) diff --git a/models/data/core/user_info.py b/models/data/core/user_info.py index b391c20..f2a8edf 100644 --- a/models/data/core/user_info.py +++ b/models/data/core/user_info.py @@ -6,11 +6,11 @@ DATE: - Saturday, 7th Dec., 2024. + Monday, 9th Dec., 2024. OBJECTIVE: - To define how auth tokens will be stored in the database. + To define how user info will be stored in the database. REFERENCES: @@ -77,7 +77,7 @@ import datetime # ***************************************************************************************************************** -class CoreAuthTokenModel(BaseModel): +class CoreUserInfoModel(BaseModel): version: str = Field( description = "a hint about the version no. of this message", @@ -86,72 +86,40 @@ class CoreAuthTokenModel(BaseModel): default = "1.0.0" ) - serviceType: Literal["email", "sms", "chat"] = Field( - description = "the kind of service this message was sent/received from", - frozen = True - ) - - client: Literal[ - "gmail", "outlook", # ...................... Mail Clients - "telegram", "whatsapp", # .................. Chat Clients - "nimbusSmsIndia", "savvyBulkSmsKenya", # ... SMS Clients - "razorpay", "safaricomMPesaExpress" # ...... Payment Gateways - ] = Field( - description = "the third-part client that was used", - frozen = True - ) - - authType: Literal["oauth", "auth"] = Field( - description = "the type of authentication procedure used", - frozen = True - ) - - firstRequestTs: AwareDatetime = Field( - description = "the time (utc) at which authorization was first requested", - frozen = True - ) - - lastRequestTs: AwareDatetime = Field( - description = "the time (utc) at which authorization was last requested", - frozen = False - ) - - firstRefreshTs: AwareDatetime = Field( - description = "the time (utc) at which the tokens were first refreshed", - frozen = False - ) - - lastRefreshTs: AwareDatetime = Field( - description = "the time (utc) at which the tokens were last refreshed", - frozen = False - ) - - token: dict | None = Field( - description = "the actual auth tokens of that client; will differ for each client", + fullName: str | None = Field( + description = "the full name of the user as found in the database", frozen = True, - default_factory = lambda: date_time.get_current_utc_date_time(as_string = False) + examples = ["Bhopli Narangi"] ) - user: dict = Field( - description = "how you identify your user", + userId: int | str | None = Field( + description = "the id of the user as found in the database", frozen = True ) - clientUserId: dict = Field( - description = "how third-party client identifies the same user", + entityId: int | str | None = Field( + description = "the id of the entity with which this user is associated", frozen = True ) - status: Literal["active", "disabled"] = Field( - description = "to indicate the status of this account", - frozen = False, - default = "active" + billingAccountId: int | str | None = Field( + description = "the id of the billing account with which this user is associated", + frozen = True ) - syncFreq: Literal[60, 300, 1500] = Field( - description = "the no. of seconds after which to poll for updates from the client (if applicable)", - frozen = False, - default = 300 + departmentId: int | str | None = Field( + description = "the id of the dept. in which this user is working", + frozen = True + ) + + branchId: int | str | None = Field( + description = "the id of the branch in which this user is working", + frozen = True + ) + + industry: str | None = Field( + description = "the name of the industry this user is working in", + frozen = True ) # ┏┓ ┏• @@ -160,21 +128,9 @@ class CoreAuthTokenModel(BaseModel): # ┛ class Config: - extra = "allow" + extra = "ignore" arbitrary_types_allowed = True - # ┓┏ ┓• ┓ • - # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ - # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ - - @field_validator( - "firstRequestTs", - "lastRequestTs", "firstRefreshTs", "lastRefreshTs", - mode = "before" - ) - def parse_date_time(cls, value): - return date_time.parse_date_time(input_value = value, timezone = date_time.TIMEZONE_UTC) - # ***************************************************************************************************************** # ***** **** @@ -184,29 +140,4 @@ class CoreAuthTokenModel(BaseModel): if __name__ == "__main__": - from utils_v2.string import json - - auth_token = CoreAuthTokenModel( - serviceType = "email", - client = "gmail", - authType = "oauth", - firstRequestTs = date_time.get_current_utc_date_time(as_string = False), - lastRequestTs = date_time.get_current_utc_date_time(as_string = False), - firstRefreshTs = date_time.get_current_utc_date_time(as_string = False), - lastRefreshTs = date_time.get_current_utc_date_time(as_string = False), - token = { - "username": "testing123", - "password": "abcdefgh" - }, - user = { - "userId": 0, - "entityId": 1, - "billingAccountId": 2, - "fullName": "Bhopli" - }, - clientUserId = { - "email": "bhopli@gmail.com" - } - ) - - print("AUTH-TOKEN MODEL:", json.to_string(auth_token.model_dump(), default = str)) + pass