(20241209) Standardizing the data models for the database. Started with mail authentication.
This commit is contained in:
@@ -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."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
# ┏┓ ┏•
|
||||
# ┃ ┏┓┏┓╋┓┏┓
|
||||
# ┗┛┗┛┛┗┛┗┗┫
|
||||
@@ -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))
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user