(20241213) Mail Module Reworked!
This commit is contained in:
@@ -110,7 +110,7 @@ def init(blueprint_setup_state):
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@llm_invoke_bp.route("/llm/invoke", methods = ["POST"])
|
@llm_invoke_bp.route("/llm/invoke", methods = ["GET"])
|
||||||
@set_api_version(api_version = "1.0.0")
|
@set_api_version(api_version = "1.0.0")
|
||||||
@read_input(sanitize_headers = False, sanitize_data = False)
|
@read_input(sanitize_headers = False, sanitize_data = False)
|
||||||
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
|
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ import asyncio
|
|||||||
|
|
||||||
|
|
||||||
# Related to Quart:
|
# Related to Quart:
|
||||||
mail_callback_bp = Blueprint("mail_cb", __name__)
|
mail_oauth_callback_bp = Blueprint("mail_cb", __name__)
|
||||||
|
|
||||||
|
|
||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
@@ -101,7 +101,7 @@ mail_callback_bp = Blueprint("mail_cb", __name__)
|
|||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
@mail_callback_bp.record_once
|
@mail_oauth_callback_bp.record_once
|
||||||
def init(blueprint_setup_state):
|
def init(blueprint_setup_state):
|
||||||
|
|
||||||
# This gets called when the blueprint is registered.
|
# This gets called when the blueprint is registered.
|
||||||
@@ -239,7 +239,7 @@ async def handle_gmail_callback() -> render_template:
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@mail_callback_bp.route("/callback/<mail_client>", methods = ["POST", "GET"])
|
@mail_oauth_callback_bp.route("/callback/<mail_client>", methods = ["POST", "GET"])
|
||||||
@set_api_version(api_version = "1.0.0")
|
@set_api_version(api_version = "1.0.0")
|
||||||
@read_input(sanitize_headers = False, sanitize_data = False)
|
@read_input(sanitize_headers = False, sanitize_data = False)
|
||||||
@log_request_to_mongo(
|
@log_request_to_mongo(
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ import asyncio
|
|||||||
|
|
||||||
|
|
||||||
# Related to Quart:
|
# Related to Quart:
|
||||||
mail_oauth_bp = Blueprint("mail_oauth", __name__)
|
mail_oauth_request_bp = Blueprint("mail_oauth", __name__)
|
||||||
|
|
||||||
|
|
||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
@@ -105,7 +105,7 @@ mail_oauth_bp = Blueprint("mail_oauth", __name__)
|
|||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
@mail_oauth_bp.record_once
|
@mail_oauth_request_bp.record_once
|
||||||
def init(blueprint_setup_state):
|
def init(blueprint_setup_state):
|
||||||
|
|
||||||
# This gets called when the blueprint is registered.
|
# This gets called when the blueprint is registered.
|
||||||
@@ -116,7 +116,7 @@ def init(blueprint_setup_state):
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@mail_oauth_bp.route("/oauth", methods = ["GET"])
|
@mail_oauth_request_bp.route("/oauth", methods = ["GET"])
|
||||||
@set_api_version(api_version = "1.0.0")
|
@set_api_version(api_version = "1.0.0")
|
||||||
@read_input(sanitize_headers = False, sanitize_data = False)
|
@read_input(sanitize_headers = False, sanitize_data = False)
|
||||||
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
|
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
|
||||||
|
|||||||
@@ -36,8 +36,8 @@
|
|||||||
|
|
||||||
# To make sibling directories accessible for imports:
|
# To make sibling directories accessible for imports:
|
||||||
import sys
|
import sys
|
||||||
sys.path.append(".")
|
|
||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
|
sys.path.append("../..")
|
||||||
|
|
||||||
# For using Quart:
|
# For using Quart:
|
||||||
from quart import Blueprint, current_app, g, request
|
from quart import Blueprint, current_app, g, request
|
||||||
@@ -136,13 +136,13 @@ def init(blueprint_setup_state):
|
|||||||
@log_chain_to_mongo(attr_name = "logs_mongo")
|
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||||
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||||
@validate_input(
|
@validate_input(
|
||||||
header_validator = lambda x: MailGetRequestHeaders(**x).model_dump(),
|
header_validator = lambda x: MailUpdateTagsRequestHeaders(**x).model_dump(),
|
||||||
data_validator = lambda x: MailGetRequestData(**x)
|
data_validator = lambda x: MailUpdateTagsRequestData(**x)
|
||||||
)
|
)
|
||||||
@handle_cancelled_request()
|
@handle_cancelled_request()
|
||||||
async def update_mail_tags(
|
async def update_mail_tags(
|
||||||
inbound_headers: dict | MailGetRequestHeaders = None,
|
inbound_headers: dict | MailUpdateTagsRequestHeaders = None,
|
||||||
inbound_data: dict | MailGetRequestData = None,
|
inbound_data: dict | MailUpdateTagsRequestData = None,
|
||||||
inbound_files: dict = None,
|
inbound_files: dict = None,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
@@ -164,17 +164,18 @@ async def update_mail_tags(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Get the mail:
|
# Get the mail:
|
||||||
message = await current_app.mail_controller.get_one_mail(
|
success = await current_app.mail_controller.update_tags(
|
||||||
mongo_conn = current_app.data_mongo,
|
mongo_conn = current_app.data_mongo,
|
||||||
token_id = inbound_data.tokenId,
|
token_id = inbound_data.tokenId,
|
||||||
message_id = inbound_data.messageId
|
message_id = inbound_data.messageId,
|
||||||
|
unset_tags = inbound_data.unsetTags,
|
||||||
|
set_tags = inbound_data.setTags
|
||||||
)
|
)
|
||||||
|
|
||||||
# Done here:
|
# Done here:
|
||||||
return ResponseModel(
|
return ResponseModel(
|
||||||
status_code = StatusCodes.OK if message else StatusCodes.FAILED,
|
status_code = StatusCodes.OK if success else StatusCodes.FAILED,
|
||||||
http_code = HttpCodes.SUCCESS if message else HttpCodes.NOT_FOUND,
|
http_code = HttpCodes.SUCCESS if success else HttpCodes.INTERNAL_SERVER_ERROR
|
||||||
data = message.full
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ def init(blueprint_setup_state):
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@test_callback_bp.route("/callback", methods = ["POST", "GET"])
|
@test_callback_bp.route("/callback", methods = ["GET", "POST", "PUT", "PATCH", "DELETE"])
|
||||||
@set_api_version(api_version = "1.0.0")
|
@set_api_version(api_version = "1.0.0")
|
||||||
@read_input(sanitize_headers = False, sanitize_data = False)
|
@read_input(sanitize_headers = False, sanitize_data = False)
|
||||||
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
|
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
|
||||||
@@ -131,21 +131,7 @@ async def callback_test(
|
|||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
|
||||||
print("SESSION INFO:", kwargs.get("session_info"))
|
return f"ok ({request.method})"
|
||||||
|
|
||||||
# Return a random page:
|
|
||||||
return await render_template(
|
|
||||||
random.choice([
|
|
||||||
r"/mail/oauth/oauth_success.html",
|
|
||||||
r"/mail/oauth/oauth_failure.html"
|
|
||||||
]),
|
|
||||||
mail_client = random.choice([
|
|
||||||
"GMail",
|
|
||||||
"Outlook",
|
|
||||||
"WhatsApp",
|
|
||||||
"Telegram"
|
|
||||||
])
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
|
|||||||
+16
-22
@@ -42,29 +42,21 @@ import os
|
|||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
# For using Quart:
|
# For using Quart:
|
||||||
from quart import Quart, request, current_app
|
from quart import Quart, current_app
|
||||||
from quart_cors import cors
|
from quart_cors import cors
|
||||||
|
|
||||||
# Common:
|
# Common:
|
||||||
from shared import constants
|
from shared import constants
|
||||||
|
|
||||||
# My utils:
|
# My utils:
|
||||||
from utils_v2.string import json
|
|
||||||
from utils_v2.api import async_quart
|
|
||||||
from utils_v2.date_time import date_time
|
from utils_v2.date_time import date_time
|
||||||
from utils_v2.database.async_mongo_v2 import AsyncMongo, AsyncMongoStorage
|
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
||||||
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
from utils_v2.database.async_mysql_v2 import AsyncMySQL
|
||||||
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
|
||||||
from utils_v2.serialization.json_serializer import JSONSerializer
|
from utils_v2.serialization.json_serializer import JSONSerializer
|
||||||
from utils_v2.api.async_quart import (
|
from utils_v2.api.async_quart import (
|
||||||
set_api_version,
|
set_api_version,
|
||||||
read_input,
|
log_request_to_mongo
|
||||||
log_request_to_mongo,
|
|
||||||
should_not_be_under_maintenance,
|
|
||||||
only_whitelisted_ips,
|
|
||||||
limit_rate,
|
|
||||||
validate_input,
|
|
||||||
handle_cancelled_request
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# GMail-related utils:
|
# GMail-related utils:
|
||||||
@@ -92,11 +84,12 @@ import httpx
|
|||||||
from icecream import IceCreamDebugger
|
from icecream import IceCreamDebugger
|
||||||
|
|
||||||
# All the blueprints:
|
# All the blueprints:
|
||||||
from api.blueprints.mail.oauth_request import mail_oauth_bp
|
from api.blueprints.mail.oauth.request import mail_oauth_request_bp
|
||||||
from api.blueprints.mail.oauth_callback import mail_callback_bp
|
from api.blueprints.mail.oauth.callback import mail_oauth_callback_bp
|
||||||
from api.blueprints.mail.sync_v2 import mail_sync_bp
|
from api.blueprints.mail.sync.sync_v2 import mail_sync_bp
|
||||||
# from api.blueprints.mail.list import mail_list_bp
|
from api.blueprints.mail.retrieve.list import mail_list_bp
|
||||||
# from api.blueprints.mail.retrieve import mail_retrieve_bp
|
from api.blueprints.mail.retrieve.get import mail_get_bp
|
||||||
|
from api.blueprints.mail.tags.update import mail_tags_update_bp
|
||||||
# from api.blueprints.sms.auth import sms_auth_bp
|
# from api.blueprints.sms.auth import sms_auth_bp
|
||||||
# from api.blueprints.sms.send import sms_send_bp
|
# from api.blueprints.sms.send import sms_send_bp
|
||||||
# from api.blueprints.chat.auth import chat_auth_bp
|
# from api.blueprints.chat.auth import chat_auth_bp
|
||||||
@@ -131,11 +124,12 @@ APP_VERSION = constants.APP_VERSION
|
|||||||
# The Quart app:
|
# The Quart app:
|
||||||
app = Quart(__name__, template_folder = r"../views")
|
app = Quart(__name__, template_folder = r"../views")
|
||||||
app = cors(app)
|
app = cors(app)
|
||||||
app.register_blueprint(mail_oauth_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
app.register_blueprint(mail_oauth_request_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
||||||
app.register_blueprint(mail_callback_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
app.register_blueprint(mail_oauth_callback_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
||||||
app.register_blueprint(mail_sync_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
app.register_blueprint(mail_sync_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
||||||
# app.register_blueprint(mail_list_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
app.register_blueprint(mail_list_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
||||||
# app.register_blueprint(mail_retrieve_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
app.register_blueprint(mail_get_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
||||||
|
app.register_blueprint(mail_tags_update_bp, url_prefix = f"/{MODULE_BASE}/mail")
|
||||||
# app.register_blueprint(sms_auth_bp, url_prefix = f"/{MODULE_BASE}/sms")
|
# app.register_blueprint(sms_auth_bp, url_prefix = f"/{MODULE_BASE}/sms")
|
||||||
# app.register_blueprint(sms_send_bp, url_prefix = f"/{MODULE_BASE}/sms")
|
# app.register_blueprint(sms_send_bp, url_prefix = f"/{MODULE_BASE}/sms")
|
||||||
# app.register_blueprint(chat_auth_bp, url_prefix = f"/{MODULE_BASE}/chat")
|
# app.register_blueprint(chat_auth_bp, url_prefix = f"/{MODULE_BASE}/chat")
|
||||||
@@ -329,7 +323,7 @@ async def app_startup(**kwargs):
|
|||||||
# ┃ ┏┓┏┓┏┓ ┃┃┃┏┓┏┫┏┓┃┏
|
# ┃ ┏┓┏┓┏┓ ┃┃┃┏┓┏┫┏┓┃┏
|
||||||
# ┗┛┗┛┛ ┗ ┛ ┗┗┛┗┻┗ ┗┛
|
# ┗┛┗┛┛ ┗ ┛ ┗┗┛┗┻┗ ┗┛
|
||||||
|
|
||||||
current_app.core_message_controller = MessageController(
|
current_app.core_auth_token_controller = AuthTokenController(
|
||||||
cache = current_app.module_cache,
|
cache = current_app.module_cache,
|
||||||
alert_url = current_app.script_data["alerts"]["url"],
|
alert_url = current_app.script_data["alerts"]["url"],
|
||||||
http_client = current_app.http_client,
|
http_client = current_app.http_client,
|
||||||
@@ -337,7 +331,7 @@ async def app_startup(**kwargs):
|
|||||||
debug_prefix = "Message (CM) | ",
|
debug_prefix = "Message (CM) | ",
|
||||||
debug_only_errors = True
|
debug_only_errors = True
|
||||||
)
|
)
|
||||||
current_app.core_auth_token_controller = AuthTokenController(
|
current_app.core_message_controller = MessageController(
|
||||||
cache = current_app.module_cache,
|
cache = current_app.module_cache,
|
||||||
alert_url = current_app.script_data["alerts"]["url"],
|
alert_url = current_app.script_data["alerts"]["url"],
|
||||||
http_client = current_app.http_client,
|
http_client = current_app.http_client,
|
||||||
|
|||||||
+78
-5
@@ -283,14 +283,12 @@ class MailController:
|
|||||||
async def get_token(
|
async def get_token(
|
||||||
mongo_conn: AsyncMongo,
|
mongo_conn: AsyncMongo,
|
||||||
token_id: ObjectId | str = None,
|
token_id: ObjectId | str = None,
|
||||||
**kwargs
|
|
||||||
) -> CoreAuthTokenModel | None:
|
) -> CoreAuthTokenModel | None:
|
||||||
|
|
||||||
# Simply call the core model:
|
# Simply call the core model:
|
||||||
return await current_app.core_auth_token_controller.get_token(
|
return await current_app.core_auth_token_controller.get_token(
|
||||||
mongo_conn = mongo_conn,
|
mongo_conn = mongo_conn,
|
||||||
token_id = token_id,
|
token_id = token_id
|
||||||
**kwargs
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# ┏┓ ┳┳┓
|
# ┏┓ ┳┳┓
|
||||||
@@ -364,8 +362,15 @@ class MailController:
|
|||||||
client = auth_token.client,
|
client = auth_token.client,
|
||||||
clientMessageId = message_id,
|
clientMessageId = message_id,
|
||||||
clientThreadId = client_response.data["threadId"],
|
clientThreadId = client_response.data["threadId"],
|
||||||
preview = client_response.data["subject"],
|
isSent = False,
|
||||||
message = client_response.data
|
isBroadcast = False,
|
||||||
|
sentSuccessfully = False,
|
||||||
|
sender = client_response.data["from"][0]["name"],
|
||||||
|
chat = None,
|
||||||
|
message = client_response.data,
|
||||||
|
snippet = client_response.data["subject"],
|
||||||
|
aiSnippet = None,
|
||||||
|
tags = ["email", "gmail"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Give a quick indicator of whether this mail is an inbox mail or sent mail:
|
# Give a quick indicator of whether this mail is an inbox mail or sent mail:
|
||||||
@@ -564,6 +569,74 @@ class MailController:
|
|||||||
sync_results.message = f"no such mail client '{auth_token.client}'"
|
sync_results.message = f"no such mail client '{auth_token.client}'"
|
||||||
return sync_results
|
return sync_results
|
||||||
|
|
||||||
|
# ┓ • ┏┓ ┏┓ ┳┳┓
|
||||||
|
# ┃ ┓┏╋ ┣╋ ┃┓┏┓╋ ┃┃┃┏┓┏┏┏┓┏┓┏┓┏
|
||||||
|
# ┗┛┗┛┗ ┗┻ ┗┛┗ ┗ ┛ ┗┗ ┛┛┗┻┗┫┗ ┛
|
||||||
|
# ┛
|
||||||
|
|
||||||
|
# These are simply for retrieving mails. You need to already have them sync'd to the database. These methods don't
|
||||||
|
# fetch the mails from the third-party clients.
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def list_mails(
|
||||||
|
mongo_conn: AsyncMongo,
|
||||||
|
token_ids: List[ObjectId | str],
|
||||||
|
limit: int = 100,
|
||||||
|
skip: int = 0,
|
||||||
|
additional_filter: dict = None
|
||||||
|
) -> List[CoreMessageModel] | None:
|
||||||
|
|
||||||
|
# regardless of what additional filter is provided from outside,
|
||||||
|
# we add a mail-selecting filter here:
|
||||||
|
if additional_filter is None: additional_filter = {}
|
||||||
|
additional_filter["serviceType"] = "email"
|
||||||
|
|
||||||
|
# Simply call the core model:
|
||||||
|
return await current_app.core_message_controller.get_previews(
|
||||||
|
mongo_conn = mongo_conn,
|
||||||
|
token_ids = token_ids,
|
||||||
|
limit = limit,
|
||||||
|
skip = skip,
|
||||||
|
additional_filter = additional_filter
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def get_one_mail(
|
||||||
|
mongo_conn: AsyncMongo,
|
||||||
|
token_id: ObjectId | str,
|
||||||
|
message_id: ObjectId | str
|
||||||
|
) -> CoreMessageModel | None:
|
||||||
|
|
||||||
|
# Simply call the core model:
|
||||||
|
return await current_app.core_message_controller.get_message(
|
||||||
|
mongo_conn = mongo_conn,
|
||||||
|
token_id = token_id,
|
||||||
|
message_id = message_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# ┳┳ ┓
|
||||||
|
# ┃┃┏┓┏┫┏┓╋┏┓
|
||||||
|
# ┗┛┣┛┗┻┗┻┗┗
|
||||||
|
# ┛
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def update_tags(
|
||||||
|
mongo_conn: AsyncMongo,
|
||||||
|
token_id: ObjectId | str,
|
||||||
|
message_id: ObjectId | str,
|
||||||
|
unset_tags: List[str] = None,
|
||||||
|
set_tags: List[str] = None
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
# Simply call the core model:
|
||||||
|
return await current_app.core_message_controller.update_tags(
|
||||||
|
mongo_conn = mongo_conn,
|
||||||
|
token_id = token_id,
|
||||||
|
message_id = message_id,
|
||||||
|
unset_tags = unset_tags,
|
||||||
|
set_tags = set_tags
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
# ***** ****
|
# ***** ****
|
||||||
|
|||||||
@@ -272,30 +272,20 @@ class AuthTokenController(BaseModel):
|
|||||||
self,
|
self,
|
||||||
mongo_conn: AsyncMongo,
|
mongo_conn: AsyncMongo,
|
||||||
token_id: ObjectId | str = None,
|
token_id: ObjectId | str = None,
|
||||||
**kwargs
|
|
||||||
) -> CoreAuthTokenModel | None:
|
) -> CoreAuthTokenModel | None:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
To retrieve stored tokens from the database.
|
To retrieve stored tokens from the database.
|
||||||
:param mongo_conn: The database connection (MongoDB) 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 token_id: The identifier granted by the 'get_token_id' method.
|
||||||
: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
|
: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.
|
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:
|
# If there is some filtering possible, we fetch the token:
|
||||||
token = await mongo_conn.find_one(
|
token = await mongo_conn.find_one(
|
||||||
collection = self.AUTH_COLLECTION,
|
collection = self.AUTH_COLLECTION,
|
||||||
filter = filter_json,
|
filter = {"_id": ObjectId(token_id)},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Done here:
|
# Done here:
|
||||||
|
|||||||
+85
-65
@@ -129,6 +129,13 @@ class MessageController(BaseModel):
|
|||||||
message: CoreMessageModel
|
message: CoreMessageModel
|
||||||
) -> ObjectId:
|
) -> ObjectId:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Simply insert one message document into the database.
|
||||||
|
:param mongo_conn: The instance of the database connector to use for the operation.
|
||||||
|
:param message: The message to save into the database.
|
||||||
|
:return: The object id of the inserted document.
|
||||||
|
"""
|
||||||
|
|
||||||
# Simply insert the document:
|
# Simply insert the document:
|
||||||
return await mongo_conn.insert_one(
|
return await mongo_conn.insert_one(
|
||||||
collection = self.MESSAGES_COLLECTION,
|
collection = self.MESSAGES_COLLECTION,
|
||||||
@@ -139,9 +146,17 @@ class MessageController(BaseModel):
|
|||||||
async def bulk_write(
|
async def bulk_write(
|
||||||
self,
|
self,
|
||||||
mongo_conn: AsyncMongo,
|
mongo_conn: AsyncMongo,
|
||||||
mongo_operations
|
mongo_operations: list
|
||||||
) -> int:
|
) -> int:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Needed in cases like forcing re-sync of mails where you need to perform actions like bulk replacements of
|
||||||
|
existing documents. Not recommended to use. Please use very carefully to ensure document integrity.
|
||||||
|
:param mongo_conn: The instance of the database connector to use for the operation.
|
||||||
|
:param mongo_operations: The list operations that are supported by MongoDB's Bulk Write system.
|
||||||
|
:return: The no. of documents affected.
|
||||||
|
"""
|
||||||
|
|
||||||
return await mongo_conn.bulk_write(
|
return await mongo_conn.bulk_write(
|
||||||
collection = self.MESSAGES_COLLECTION,
|
collection = self.MESSAGES_COLLECTION,
|
||||||
requests = mongo_operations,
|
requests = mongo_operations,
|
||||||
@@ -195,7 +210,7 @@ class MessageController(BaseModel):
|
|||||||
) -> List[CoreMessageModel] | None:
|
) -> List[CoreMessageModel] | None:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Fetches many messages in one call, but leaves out the full payloads. This does not mark messages as read.
|
Fetches many messages in one call, but leaves out the full payloads.
|
||||||
:param mongo_conn: The instance of the database connector to use for the operation.
|
:param mongo_conn: The instance of the database connector to use for the operation.
|
||||||
:param token_ids: The token ids of the accounts from which these messages must be fetched.
|
:param token_ids: The token ids of the accounts from which these messages must be fetched.
|
||||||
:param limit: The max. no. of messages to retrieve in this call.
|
:param limit: The max. no. of messages to retrieve in this call.
|
||||||
@@ -223,8 +238,8 @@ class MessageController(BaseModel):
|
|||||||
projection = {
|
projection = {
|
||||||
"_id": True,
|
"_id": True,
|
||||||
"ts": True,
|
"ts": True,
|
||||||
|
"syncTs": True,
|
||||||
"tokenId": True,
|
"tokenId": True,
|
||||||
"markedAsUnread": True,
|
|
||||||
"serviceType": True,
|
"serviceType": True,
|
||||||
"client": True,
|
"client": True,
|
||||||
"clientMessageId": True,
|
"clientMessageId": True,
|
||||||
@@ -232,10 +247,11 @@ class MessageController(BaseModel):
|
|||||||
"isSent": True,
|
"isSent": True,
|
||||||
"isBroadcast": True,
|
"isBroadcast": True,
|
||||||
"sentSuccessfully": True,
|
"sentSuccessfully": True,
|
||||||
|
"sender": True,
|
||||||
|
"chat": True,
|
||||||
|
"snippet": True,
|
||||||
"aiSnippet": True,
|
"aiSnippet": True,
|
||||||
"preview": True,
|
"tags": True
|
||||||
"tags": True,
|
|
||||||
"usedAi": True
|
|
||||||
},
|
},
|
||||||
raise_exception = True
|
raise_exception = True
|
||||||
)
|
)
|
||||||
@@ -263,9 +279,6 @@ class MessageController(BaseModel):
|
|||||||
:return: The list of messages (as the message model). This list can be empty.
|
:return: The list of messages (as the message model). This list can be empty.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Note down the timestamp at which this event occurred:
|
|
||||||
request_ts = date_time.get_current_utc_date_time(as_string = False)
|
|
||||||
|
|
||||||
# Prepare the filter:
|
# Prepare the filter:
|
||||||
if not isinstance(token_ids, list): token_ids = [token_ids]
|
if not isinstance(token_ids, list): token_ids = [token_ids]
|
||||||
token_ids = [ObjectId(t) for t in token_ids]
|
token_ids = [ObjectId(t) for t in token_ids]
|
||||||
@@ -285,75 +298,31 @@ class MessageController(BaseModel):
|
|||||||
raise_exception = True
|
raise_exception = True
|
||||||
)
|
)
|
||||||
|
|
||||||
# We now mark these fetched messages as read through a bulk-write operation:
|
|
||||||
operations = [
|
|
||||||
UpdateOne(
|
|
||||||
filter = {"_id": record["_id"]},
|
|
||||||
update = [{
|
|
||||||
"$set": {
|
|
||||||
"readTs": {
|
|
||||||
"$cond": {
|
|
||||||
"if": {
|
|
||||||
"$or": [
|
|
||||||
{"$eq": ["$readTs", None]},
|
|
||||||
{"$eq": [{"$type": "$readTs"}, "missing"]}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"then": request_ts,
|
|
||||||
"else": "$readTs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
upsert = False
|
|
||||||
) for record in records
|
|
||||||
]
|
|
||||||
updated_count = await mongo_conn.bulk_write(
|
|
||||||
collection = self.MESSAGES_COLLECTION,
|
|
||||||
requests = operations,
|
|
||||||
raise_exception = True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert the fetched records to instances of the data model and return:
|
# Convert the fetched records to instances of the data model and return:
|
||||||
return [CoreMessageModel(**record) for record in records]
|
return [CoreMessageModel(**record) for record in records]
|
||||||
|
|
||||||
async def get_message(
|
async def get_message(
|
||||||
self,
|
self,
|
||||||
mongo_conn: AsyncMongo,
|
mongo_conn: AsyncMongo,
|
||||||
|
token_id: ObjectId | str,
|
||||||
message_id: ObjectId | str,
|
message_id: ObjectId | str,
|
||||||
) -> CoreMessageModel | None:
|
) -> CoreMessageModel | None:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Gets one message if you know its message id. Marks that message as read.
|
Gets one message if you know its message id.
|
||||||
:param mongo_conn:
|
:param mongo_conn: The instance of the database connector to use for the operation.
|
||||||
:param message_id:
|
:param token_id: The id of the auth-token associated with the message. Needed for security.
|
||||||
:return:
|
:param message_id: The id of the message that needs to be read.
|
||||||
|
:return: The contents of that one message in a structured format.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Note down the timestamp at which this event occurred:
|
# We fetch the whole payload of that one message:
|
||||||
request_ts = date_time.get_current_utc_date_time(as_string = False)
|
record = await mongo_conn.find_one(
|
||||||
|
|
||||||
# We fetch the whole payload of that one message
|
|
||||||
# while also marking it as read if not already marked:
|
|
||||||
record = await mongo_conn.find_one_and_update(
|
|
||||||
collection = self.MESSAGES_COLLECTION,
|
collection = self.MESSAGES_COLLECTION,
|
||||||
filter = {"_id": ObjectId(message_id)},
|
filter = {
|
||||||
update = [{
|
"_id": ObjectId(message_id),
|
||||||
"$set": {
|
"tokenId": ObjectId(token_id)
|
||||||
"readTs": {
|
},
|
||||||
"$cond": {
|
|
||||||
"if": {
|
|
||||||
"$or": [
|
|
||||||
{"$eq": ["$readTs", None]},
|
|
||||||
{"$eq": [{"$type": "$readTs"}, "missing"]}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"then": request_ts,
|
|
||||||
"else": "$readTs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
raise_exception = True
|
raise_exception = True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -372,6 +341,57 @@ class MessageController(BaseModel):
|
|||||||
# We don't support updating messages themselves,
|
# We don't support updating messages themselves,
|
||||||
# but we will allow updating fields like tags, marking as read or unread, etc.
|
# but we will allow updating fields like tags, marking as read or unread, etc.
|
||||||
|
|
||||||
|
async def update_tags(
|
||||||
|
self,
|
||||||
|
mongo_conn: AsyncMongo,
|
||||||
|
token_id: ObjectId | str,
|
||||||
|
message_id: ObjectId | str,
|
||||||
|
unset_tags: List[str] = None,
|
||||||
|
set_tags: List[str] = None
|
||||||
|
) -> bool:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Updates the tags on one message. The tags to remove are processed first, the ones to add are processed later.
|
||||||
|
:param mongo_conn: The instance of the database connector to use for the operation.
|
||||||
|
:param token_id: The id of the auth-token associated with the message. Needed for security.
|
||||||
|
:param message_id: The id of the message that needs to be read.
|
||||||
|
:param unset_tags: The tags to remove from the message.
|
||||||
|
:param set_tags: The tags to add to the message.
|
||||||
|
:return: True if the update was successful, else False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Update the tags:
|
||||||
|
return await mongo_conn.update_one(
|
||||||
|
collection = self.MESSAGES_COLLECTION,
|
||||||
|
filter = {
|
||||||
|
"_id": ObjectId(message_id),
|
||||||
|
"tokenId": ObjectId(token_id)
|
||||||
|
},
|
||||||
|
update = [{
|
||||||
|
"$set": {
|
||||||
|
"tags": {
|
||||||
|
"$let": {
|
||||||
|
"vars": {
|
||||||
|
"removed_tags": {
|
||||||
|
"$setDifference": [
|
||||||
|
"$tags",
|
||||||
|
unset_tags
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"in": {
|
||||||
|
"$setUnion": [
|
||||||
|
"$$removed_tags",
|
||||||
|
set_tags
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
raise_exception = True
|
||||||
|
)
|
||||||
|
|
||||||
# ┏┓┳┓┳┳┳┓ ┳┓ ┓
|
# ┏┓┳┓┳┳┳┓ ┳┓ ┓
|
||||||
# ┃ ┣┫┃┃┃┃ ━━ ┃┃┏┓┃┏┓╋┏┓
|
# ┃ ┣┫┃┃┃┃ ━━ ┃┃┏┓┃┏┓╋┏┓
|
||||||
# ┗┛┛┗┗┛┻┛ ┻┛┗ ┗┗ ┗┗
|
# ┗┛┛┗┗┛┻┛ ┻┛┗ ┗┗ ┗┗
|
||||||
|
|||||||
+10
-4
@@ -37,7 +37,7 @@ sys.path.append("..")
|
|||||||
|
|
||||||
# For making data behaviour_models:
|
# For making data behaviour_models:
|
||||||
from pydantic import BaseModel, Field, field_validator, PastDatetime
|
from pydantic import BaseModel, Field, field_validator, PastDatetime
|
||||||
from typing import Optional, Literal, List
|
from typing import Optional, Literal, List, Any
|
||||||
|
|
||||||
# My utils:
|
# My utils:
|
||||||
from utils_v2.string import regex
|
from utils_v2.string import regex
|
||||||
@@ -99,10 +99,10 @@ class MailListRequestHeaders(BaseModel):
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class MailListByAccountIdRequestData(BaseModel):
|
class MailListRequestData(BaseModel):
|
||||||
|
|
||||||
tokenId: str | List[str] = Field(
|
tokenIds: str | List[str] = Field(
|
||||||
description = "the account identifier(s) (Mongo ObjectId) granted by 'MailOAuthModel.get_account_identifier'",
|
description = "the token identifier(s) that tell you which auth-tokens were used for fetching those messages",
|
||||||
frozen = True
|
frozen = True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -116,10 +116,16 @@ class MailListByAccountIdRequestData(BaseModel):
|
|||||||
|
|
||||||
fromCount: int = Field(
|
fromCount: int = Field(
|
||||||
description = "the no. of mails to skip before picking mails to list; useful for pagination",
|
description = "the no. of mails to skip before picking mails to list; useful for pagination",
|
||||||
|
ge = 0,
|
||||||
default = 0,
|
default = 0,
|
||||||
frozen = True
|
frozen = True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tags: List[Any] | None = Field(
|
||||||
|
description = "any no. of tags that you want to filter by",
|
||||||
|
default = None
|
||||||
|
)
|
||||||
|
|
||||||
# ┏┓ ┏•
|
# ┏┓ ┏•
|
||||||
# ┃ ┏┓┏┓╋┓┏┓
|
# ┃ ┏┓┏┓╋┓┏┓
|
||||||
# ┗┛┗┛┛┗┛┗┗┫
|
# ┗┛┗┛┛┗┛┗┗┫
|
||||||
|
|||||||
+17
-5
@@ -6,11 +6,11 @@
|
|||||||
|
|
||||||
DATE:
|
DATE:
|
||||||
|
|
||||||
Tuesday, 3rd Dec., 2024.
|
Friday, 13th Dec., 2024.
|
||||||
|
|
||||||
OBJECTIVE:
|
OBJECTIVE:
|
||||||
|
|
||||||
To provide a structure to query the full payload of an email.
|
To provide a structure to work with the tags on mail messages.
|
||||||
|
|
||||||
REFERENCES:
|
REFERENCES:
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ sys.path.append("..")
|
|||||||
|
|
||||||
# For making data behaviour_models:
|
# For making data behaviour_models:
|
||||||
from pydantic import BaseModel, Field, field_validator, PastDatetime
|
from pydantic import BaseModel, Field, field_validator, PastDatetime
|
||||||
from typing import Optional, Literal
|
from typing import Optional, Literal, List, Any
|
||||||
|
|
||||||
# My utils:
|
# My utils:
|
||||||
from utils_v2.string import regex
|
from utils_v2.string import regex
|
||||||
@@ -75,7 +75,7 @@ REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]
|
|||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
class MailGetRequestHeaders(BaseModel):
|
class MailUpdateTagsRequestHeaders(BaseModel):
|
||||||
|
|
||||||
sessionToken: str = Field(
|
sessionToken: str = Field(
|
||||||
description = "the session token of the user who is requesting the service",
|
description = "the session token of the user who is requesting the service",
|
||||||
@@ -99,7 +99,7 @@ class MailGetRequestHeaders(BaseModel):
|
|||||||
# ---------------------------------------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class MailGetRequestData(BaseModel):
|
class MailUpdateTagsRequestData(BaseModel):
|
||||||
|
|
||||||
tokenId: str = Field(
|
tokenId: str = Field(
|
||||||
description = "the id of the token associated with the mail; needed for security",
|
description = "the id of the token associated with the mail; needed for security",
|
||||||
@@ -111,6 +111,18 @@ class MailGetRequestData(BaseModel):
|
|||||||
frozen = True
|
frozen = True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unsetTags: List[Any] | None = Field(
|
||||||
|
description = "the list of tags to remove from the mail",
|
||||||
|
frozen = True,
|
||||||
|
default = None
|
||||||
|
)
|
||||||
|
|
||||||
|
setTags: List[Any] | None = Field(
|
||||||
|
description = "the list of tags to add to the mail",
|
||||||
|
frozen = True,
|
||||||
|
default = None
|
||||||
|
)
|
||||||
|
|
||||||
# ┏┓ ┏•
|
# ┏┓ ┏•
|
||||||
# ┃ ┏┓┏┓╋┓┏┓
|
# ┃ ┏┓┏┓╋┓┏┓
|
||||||
# ┗┛┗┛┛┗┛┗┗┫
|
# ┗┛┗┛┛┗┛┗┗┫
|
||||||
|
|||||||
+70
-30
@@ -36,7 +36,7 @@ sys.path.append(".")
|
|||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
|
|
||||||
# For making data behaviour_models:
|
# For making data behaviour_models:
|
||||||
from pydantic import BaseModel, Field, field_validator, PastDatetime, AwareDatetime
|
from pydantic import BaseModel, Field, field_validator, PastDatetime, AwareDatetime, constr
|
||||||
from typing import Optional, Literal, Union, List, Any
|
from typing import Optional, Literal, Union, List, Any
|
||||||
|
|
||||||
# My utils:
|
# My utils:
|
||||||
@@ -100,18 +100,6 @@ class CoreMessageModel(BaseModel):
|
|||||||
default_factory = lambda: date_time.get_current_utc_date_time(as_string = False)
|
default_factory = lambda: date_time.get_current_utc_date_time(as_string = False)
|
||||||
)
|
)
|
||||||
|
|
||||||
readTs: AwareDatetime | None = Field(
|
|
||||||
description = "the time (utc) at which this message was read by the user",
|
|
||||||
frozen = True,
|
|
||||||
default = None
|
|
||||||
)
|
|
||||||
|
|
||||||
markedAsUnread: bool = Field(
|
|
||||||
description = "to note when the user has marked this message as unread",
|
|
||||||
frozen = False,
|
|
||||||
default = False
|
|
||||||
)
|
|
||||||
|
|
||||||
tokenId: ObjectId = Field(
|
tokenId: ObjectId = Field(
|
||||||
description = "the id of the auth token that is associated with this message",
|
description = "the id of the auth token that is associated with this message",
|
||||||
frozen = True
|
frozen = True
|
||||||
@@ -160,20 +148,30 @@ class CoreMessageModel(BaseModel):
|
|||||||
default = False
|
default = False
|
||||||
)
|
)
|
||||||
|
|
||||||
aiSnippet: LLMOutput | dict | None = Field(
|
sender: str | None = Field(
|
||||||
description = "holds a short summary generated by ",
|
description = "the name of the sender; null if you are the sender",
|
||||||
frozen = False,
|
frozen = True
|
||||||
default = None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
preview: str = Field(
|
chat: str | None = Field(
|
||||||
|
description = "the name of the chat where the message was exchanged; relevant in chat apps like telegram",
|
||||||
|
frozen = True
|
||||||
|
)
|
||||||
|
|
||||||
|
message: dict = Field(
|
||||||
|
description = "the actual content(s) of the message; will differ for each service/client",
|
||||||
|
frozen = True
|
||||||
|
)
|
||||||
|
|
||||||
|
snippet: str = Field(
|
||||||
description = "a truncated version of the actual textual content of the message",
|
description = "a truncated version of the actual textual content of the message",
|
||||||
frozen = False
|
frozen = False
|
||||||
)
|
)
|
||||||
|
|
||||||
message: dict = Field(
|
aiSnippet: LLMOutput | dict | None = Field(
|
||||||
description = "the actual contents of the message; will differ for each client",
|
description = "holds a short summary generated by an llm",
|
||||||
frozen = True
|
frozen = False,
|
||||||
|
default = None
|
||||||
)
|
)
|
||||||
|
|
||||||
tags: List[Any] = Field(
|
tags: List[Any] = Field(
|
||||||
@@ -183,29 +181,67 @@ class CoreMessageModel(BaseModel):
|
|||||||
examples = ["urgent", "otp", "GST"]
|
examples = ["urgent", "otp", "GST"]
|
||||||
)
|
)
|
||||||
|
|
||||||
usedAi: bool | None = Field(
|
|
||||||
description = "to mark when a sent message was generated by ai; null means the status is not known",
|
|
||||||
frozen = True,
|
|
||||||
default = None
|
|
||||||
)
|
|
||||||
|
|
||||||
# ┏┓ ┏•
|
# ┏┓ ┏•
|
||||||
# ┃ ┏┓┏┓╋┓┏┓
|
# ┃ ┏┓┏┓╋┓┏┓
|
||||||
# ┗┛┗┛┛┗┛┗┗┫
|
# ┗┛┗┛┛┗┛┗┗┫
|
||||||
# ┛
|
# ┛
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
extra = "allow"
|
extra = "forbid"
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
def model_dump(self, *args, **kwargs):
|
def model_dump(self, *args, **kwargs):
|
||||||
return super().model_dump(*args, by_alias = True, **kwargs)
|
return super().model_dump(*args, by_alias = True, **kwargs)
|
||||||
|
|
||||||
|
# ┏┓ •
|
||||||
|
# ┃┃┏┓┏┓┏┓┏┓┏┓╋┓┏┓┏
|
||||||
|
# ┣┛┛ ┗┛┣┛┗ ┛ ┗┗┗ ┛
|
||||||
|
# ┛
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full(self):
|
||||||
|
return {
|
||||||
|
"messageId": str(self.messageId),
|
||||||
|
"ts": self.ts.isoformat(),
|
||||||
|
"serviceType": self.serviceType,
|
||||||
|
"client": self.client,
|
||||||
|
"clientMessageId": self.clientMessageId,
|
||||||
|
"clientThreadId": self.clientThreadId,
|
||||||
|
"isSent": self.isSent,
|
||||||
|
"isBroadcast": self.isBroadcast,
|
||||||
|
"sentSuccessfully": self.sentSuccessfully,
|
||||||
|
"sender": self.sender,
|
||||||
|
"chat": self.chat,
|
||||||
|
"message": self.message,
|
||||||
|
"snippet": self.snippet,
|
||||||
|
"aiSnippet": self.aiSnippet,
|
||||||
|
"tags": self.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def preview(self):
|
||||||
|
return {
|
||||||
|
"messageId": str(self.messageId),
|
||||||
|
"ts": self.ts.isoformat(),
|
||||||
|
"serviceType": self.serviceType,
|
||||||
|
"client": self.client,
|
||||||
|
"clientMessageId": self.clientMessageId,
|
||||||
|
"clientThreadId": self.clientThreadId,
|
||||||
|
"isSent": self.isSent,
|
||||||
|
"isBroadcast": self.isBroadcast,
|
||||||
|
"sentSuccessfully": self.sentSuccessfully,
|
||||||
|
"sender": self.sender,
|
||||||
|
"chat": self.chat,
|
||||||
|
"snippet": self.snippet,
|
||||||
|
"aiSnippet": self.aiSnippet,
|
||||||
|
"tags": self.tags
|
||||||
|
}
|
||||||
|
|
||||||
# ┓┏ ┓• ┓ •
|
# ┓┏ ┓• ┓ •
|
||||||
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
|
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
|
||||||
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
|
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
|
||||||
|
|
||||||
@field_validator("ts", "syncTs", "readTs", mode = "before")
|
@field_validator("ts", "syncTs", mode = "before")
|
||||||
def parse_date_time(cls, value):
|
def parse_date_time(cls, value):
|
||||||
return date_time.parse_date_time(input_value = value, timezone = date_time.TIMEZONE_UTC)
|
return date_time.parse_date_time(input_value = value, timezone = date_time.TIMEZONE_UTC)
|
||||||
|
|
||||||
@@ -231,6 +267,7 @@ class CoreMessageModel(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
from utils_v2.string import json
|
from utils_v2.string import json
|
||||||
|
|
||||||
message = CoreMessageModel(
|
message = CoreMessageModel(
|
||||||
@@ -245,7 +282,10 @@ if __name__ == "__main__":
|
|||||||
"from": "bhopli@gmil.com",
|
"from": "bhopli@gmil.com",
|
||||||
"to": "hello@thecaoffice.com",
|
"to": "hello@thecaoffice.com",
|
||||||
"message": "Hello, World!"
|
"message": "Hello, World!"
|
||||||
}
|
},
|
||||||
|
sender = "Polki",
|
||||||
|
chat = "T6 Cats",
|
||||||
|
preview = "Hi, there! How do you do?"
|
||||||
)
|
)
|
||||||
|
|
||||||
print("MESSAGE MODEL:", json.to_string(message.model_dump(), default = str))
|
print("MESSAGE MODEL:", json.to_string(message.model_dump(), default = str))
|
||||||
|
|||||||
Reference in New Issue
Block a user