(20250825) -Added new google places API Integration system, no more oauth system for places api integration,
added multiple files for and correction and changes as well.
This commit is contained in:
@@ -259,6 +259,29 @@ async def authorize_software_client(
|
||||
else:
|
||||
auth_url_failed = f"https://api.thecaoffice.com/shopify/auth/template?status=0&storeName={inbound_data.auth.storeName}&storeUrl={inbound_data.auth.storeUrl}"
|
||||
|
||||
elif inbound_data.softwareClient == "googlePlaces":
|
||||
# Make the client controller test and save the auth:
|
||||
response = await current_app.places_controller.save_auth(
|
||||
sql_conn=current_app.sql_writer,
|
||||
mongo_data_conn=current_app.data_mongo,
|
||||
auth=inbound_data.auth,
|
||||
user=kwargs.get("session_info"),
|
||||
session_token=inbound_headers.get("X-Session-Token")
|
||||
)
|
||||
|
||||
# Note down the results:
|
||||
success = response.success
|
||||
message = response.message
|
||||
token_id = response.token_id
|
||||
|
||||
auth_url_success = ""
|
||||
auth_url_failed = ""
|
||||
|
||||
if token_id is not None:
|
||||
auth_url_success = f"https://api.thecaoffice.com/user/auth/template?status=1&client=googlePlaces"
|
||||
else:
|
||||
auth_url_failed = f"https://api.thecaoffice.com/user/auth/template?status=0&client=googlePlaces&message={message}"
|
||||
|
||||
# ┳┓
|
||||
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
|
||||
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
|
||||
|
||||
@@ -71,11 +71,10 @@ from utils_v2.goog.models.auth_tokens import GoogleAuthTokens
|
||||
from shared import constants
|
||||
|
||||
# Data Models:
|
||||
# from models.api.message.mail.send import MailSendRequestHeaders, MailSendRequestData
|
||||
from models.api.message.mail.send import MailSendRequestHeaders, MailSendRequestData
|
||||
from models.api.software.places.places import PlacesRequestHeaders, PlacesTextSearchRequestData, PlacesNearbyRequestData
|
||||
|
||||
from models.message.mail.send import MailSendOneResult
|
||||
from models.core.user import CoreUserInfoModel
|
||||
from models.core.auth_token import CoreAuthTokenModel
|
||||
|
||||
# To work with datatypes:
|
||||
from typing import Literal
|
||||
@@ -129,7 +128,7 @@ def init(blueprint_setup_state):
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@google_places_bp.route("/<search_type>", methods = ["POST"])
|
||||
@google_places_bp.route("/nearby", methods = ["POST"])
|
||||
@set_api_version(api_version = "1.0.0")
|
||||
@read_input(sanitize_headers = False, sanitize_data = False)
|
||||
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
|
||||
@@ -145,13 +144,13 @@ def init(blueprint_setup_state):
|
||||
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||
@validate_input(
|
||||
header_validator = lambda x: MailSendRequestHeaders(**x).model_dump(),
|
||||
data_validator = lambda x: MailSendRequestData(**x)
|
||||
header_validator = lambda x: PlacesRequestHeaders(**x).model_dump(),
|
||||
data_validator = lambda x: PlacesNearbyRequestData(**x)
|
||||
)
|
||||
@handle_cancelled_request()
|
||||
async def find_nearby(
|
||||
inbound_headers: dict | MailSendRequestHeaders = None,
|
||||
inbound_data: dict | MailSendRequestData = None,
|
||||
inbound_headers: dict | PlacesRequestHeaders = None,
|
||||
inbound_data: dict | PlacesNearbyRequestData = None,
|
||||
inbound_files: dict = None,
|
||||
**kwargs
|
||||
):
|
||||
@@ -267,13 +266,13 @@ async def find_nearby(
|
||||
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||
@validate_input(
|
||||
header_validator = lambda x: MailSendRequestHeaders(**x).model_dump(),
|
||||
data_validator = lambda x: MailSendRequestData(**x)
|
||||
header_validator = lambda x: PlacesRequestHeaders(**x).model_dump(),
|
||||
data_validator = lambda x: PlacesTextSearchRequestData(**x)
|
||||
)
|
||||
@handle_cancelled_request()
|
||||
async def text_search(
|
||||
inbound_headers: dict = None,
|
||||
inbound_data: dict = None,
|
||||
inbound_headers: dict | PlacesRequestHeaders = None,
|
||||
inbound_data: dict | PlacesTextSearchRequestData = None,
|
||||
inbound_files: dict = None,
|
||||
**kwargs
|
||||
):
|
||||
@@ -303,11 +302,11 @@ async def text_search(
|
||||
# ┃┃┓┏┏┏┓┏┓┏┓┏┣┓┓┏┓ ┃ ┣┓┏┓┏┃┏
|
||||
# ┗┛┗┻┛┛┗┗ ┛ ┛┛┗┗┣┛ ┗┛┛┗┗ ┗┛┗
|
||||
# ┛
|
||||
|
||||
print(inbound_data)
|
||||
# Get the token based on the key:
|
||||
auth_token = await current_app.places_controller.get_token_from_key(
|
||||
mongo_data_conn = current_app.data_mongo,
|
||||
token_key = inbound_data["tokenKey"],
|
||||
token_key = inbound_data.tokenKey,
|
||||
must_be_active = True
|
||||
)
|
||||
print("AUTH:", auth_token)
|
||||
@@ -334,10 +333,10 @@ async def text_search(
|
||||
|
||||
# If the controller and connector were matched:
|
||||
if client_controller is not None and client_connector is not None:
|
||||
send_result = await client_controller.text_query_search(
|
||||
send_result = await client_connector.text_query_search(
|
||||
tokens=auth_token,
|
||||
text_query=inbound_data["textQuery"],
|
||||
max_count=inbound_data["maxCount"]
|
||||
text_query=inbound_data.textQuery,
|
||||
max_count=inbound_data.maxCount
|
||||
)
|
||||
|
||||
# ┳┓
|
||||
|
||||
@@ -52,20 +52,12 @@ from utils_v2.rest.models.api_call import ApiResponse
|
||||
from models.core.user import CoreUserInfoModel
|
||||
from models.core.auth_token import CoreAuthTokenModel
|
||||
from models.core.user import CoreUserInfoModel
|
||||
from models.api.message.mail.oauth import (
|
||||
OAuthMailAuthorizationRequestHeaders,
|
||||
OAuthMailAuthorizationRequestData
|
||||
)
|
||||
from models.message.mail.oauth import OAuthMailGetAuthorizationURLResponse, OAuthMailHandleCallbackResponse
|
||||
from models.core.message import CoreMessageModel
|
||||
from models.api.software.places.oauth import PlacesAuthResponse
|
||||
|
||||
# To make HTTP requests:
|
||||
import httpx
|
||||
|
||||
# to work with MongoDB:
|
||||
from bson.objectid import ObjectId
|
||||
|
||||
# Mail Client(s):
|
||||
from utils_v2.goog.controllers.places.places_client import AsyncPlacesClient
|
||||
|
||||
# To make abstract classes:
|
||||
from abc import ABC, abstractmethod
|
||||
@@ -173,80 +165,22 @@ class GooglePlacesController(CoreSoftwareController, ABC):
|
||||
debug_only_errors = debug_only_errors
|
||||
)
|
||||
|
||||
# ┏┓┏┓ ┓ ┏┓ ┏┓
|
||||
# ┃┃┣┫┓┏╋┣┓┏┛ ┃┫
|
||||
# ┗┛┛┗┗┻┗┛┗┗━•┗┛
|
||||
|
||||
@abstractmethod
|
||||
async def get_authorization_url(
|
||||
async def save_auth(
|
||||
self,
|
||||
sql_conn: AsyncMySQL,
|
||||
mongo_data_conn: AsyncMongo,
|
||||
mail_client: AsyncPlacesClient,
|
||||
user_info: CoreUserInfoModel,
|
||||
inbound_data: OAuthMailAuthorizationRequestData,
|
||||
auth: CoreAuthTokenModel,
|
||||
user: CoreUserInfoModel,
|
||||
session_token: str
|
||||
) -> OAuthMailGetAuthorizationURLResponse:
|
||||
) -> PlacesAuthResponse:
|
||||
"""
|
||||
To accept an incoming request for mail integration and provide a URL that the user can use to authorize your
|
||||
service to access his mail inbox.
|
||||
Checks if a particular set of incoming credentials give access to a valid server and then stores the
|
||||
credentials.
|
||||
:param sql_conn: The database connection to use to perform this task.
|
||||
:param mongo_data_conn: The database connection to use to perform this task.
|
||||
:param mail_client: The instance of the third-party mail client that will be used to get the URL.
|
||||
:param user_info: The information about your user who is trying to use this system.
|
||||
:param inbound_data: The data that came in with the request (API call).
|
||||
:param session_token: The session token of the user.
|
||||
:return: A structure response with details about the URL generation process.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def handle_authorization_callback(
|
||||
self,
|
||||
sql_conn: AsyncMySQL,
|
||||
mongo_data_conn: AsyncMongo,
|
||||
mail_client: AsyncPlacesClient,
|
||||
request_url: str,
|
||||
inbound_data: dict,
|
||||
session_token: str = None
|
||||
) -> OAuthMailHandleCallbackResponse:
|
||||
"""
|
||||
To handle the authorization callback for the mail client. The user may grant or deny authorization.
|
||||
:param sql_conn: The database connection to use to perform this task.
|
||||
:param mongo_data_conn: The database connection to use to perform this task.
|
||||
:param mail_client: The instance of the third-party mail client that will be used to get the URL.
|
||||
:param request_url: The full callback URL invoked by the third-party client.
|
||||
:param inbound_data: The data that came in with the request (API call).
|
||||
:param session_token: The session token of the user. It is expected that this will be null in all cases.
|
||||
:return: A structured response of the process of handling the mail callback.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def refresh_authorization(
|
||||
self,
|
||||
sql_conn: AsyncMySQL,
|
||||
mongo_data_conn: AsyncMongo,
|
||||
mail_client: AsyncPlacesClient,
|
||||
http_client: httpx.AsyncClient,
|
||||
auth_token: CoreAuthTokenModel,
|
||||
force_refresh: bool = False,
|
||||
session_token: str = None
|
||||
) -> CoreAuthTokenModel:
|
||||
"""
|
||||
To refresh the third-party client's access/authorization token(s) before use.
|
||||
:param sql_conn: The database connection to use when storing the refreshed tokens.
|
||||
:param mongo_data_conn: The database connection to use when storing the refreshed tokens.
|
||||
:param mail_client: The connection of the third-party mail client.
|
||||
:param http_client: The HTTP client to use to make the token refresh request.
|
||||
:param auth_token: The auth-token model of the existing integration. This may get updated if a refresh is needed
|
||||
(or forced).
|
||||
:param force_refresh: Whether, or not, you would like to force a refresh even if the token hasn't expired yet.
|
||||
:param session_token: The session token of the user. This will be null if this method is invoked by a cron
|
||||
script in the background. Needed only to identify the user in case of a failure to send a timely alert.
|
||||
:return: The same auth-token model instance, but maybe with updated tokens.
|
||||
:param auth: The set of credentials as received from the UI/API.
|
||||
:return: A structured response to indicate what happened during authorization.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
import sys
|
||||
sys.path.append(".")
|
||||
sys.path.append("..")
|
||||
|
||||
import aiohttp
|
||||
# My async utils:
|
||||
from utils_v2.string import json
|
||||
from utils_v2.mail import mail_parser
|
||||
@@ -53,7 +53,8 @@ from models.core.auth_token import CoreAuthTokenModel
|
||||
from models.core.message import CoreMessageModel
|
||||
from models.api.software.places.oauth import (
|
||||
OAuthPlacesAuthorizationRequestHeaders,
|
||||
OAuthPlacesAuthorizationRequestData
|
||||
OAuthPlacesAuthorizationRequestData,
|
||||
PlacesAuthResponse
|
||||
)
|
||||
from models.software.places.oauth import OAuthPlacesGetAuthorizationURLResponse, OAuthPlacesHandleCallbackResponse
|
||||
|
||||
@@ -168,6 +169,81 @@ class PlacesController(GooglePlacesController):
|
||||
# Init a variable in a parent:
|
||||
self._client = self.CLIENT_NAME
|
||||
|
||||
async def is_google_places_api_key_valid(api_key: str) -> bool:
|
||||
url = "https://places.googleapis.com/v1/places:searchText"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Goog-Api-Key": api_key,
|
||||
"X-Goog-FieldMask": "places.displayName" # minimal required field
|
||||
}
|
||||
payload = {
|
||||
"textQuery": "Google Sydney" # dummy search text
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, headers=headers, json=payload, timeout=10) as response:
|
||||
if response.status != 200:
|
||||
return False
|
||||
data = await response.json()
|
||||
# If key invalid, response will contain "error"
|
||||
if "error" in data:
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def save_auth(
|
||||
self,
|
||||
sql_conn: AsyncMySQL,
|
||||
mongo_data_conn: AsyncMongo,
|
||||
auth: CoreAuthTokenModel ,
|
||||
user: CoreUserInfoModel,
|
||||
session_token: str
|
||||
) -> PlacesAuthResponse:
|
||||
|
||||
# CHECK -- API KEY IS VALID -
|
||||
valid_api = await PlacesController.is_google_places_api_key_valid(api_key=auth.apiKey)
|
||||
if valid_api:
|
||||
success, object_id = await self.set_token_direct_with_return_id(
|
||||
sql_conn=sql_conn,
|
||||
mongo_data_conn=mongo_data_conn,
|
||||
auth_token=CoreAuthTokenModel(
|
||||
serviceType=self.SERVICE_TYPE,
|
||||
client=self.CLIENT_NAME,
|
||||
authType="auth",
|
||||
auth=auth.model_dump(),
|
||||
user=user,
|
||||
clientUserId={
|
||||
"email": auth.email,
|
||||
"apiKey": auth.apiKey
|
||||
},
|
||||
status="active",
|
||||
syncFreq=60
|
||||
),
|
||||
token_notes={
|
||||
"email": auth.email,
|
||||
"apiKey": auth.apiKey
|
||||
},
|
||||
display_name=auth.displayName,
|
||||
display_picture=None,
|
||||
session_token=session_token
|
||||
)
|
||||
|
||||
# Done here:
|
||||
return PlacesAuthResponse(
|
||||
success=success,
|
||||
token_id=str(object_id),
|
||||
message="Places Account Added successfully." if success else "Places Account Added failed."
|
||||
)
|
||||
else:
|
||||
return PlacesAuthResponse(
|
||||
success=False,
|
||||
token_id="",
|
||||
message="Invalid Google Places API KEY, please provide valid API KEY. Thank You"
|
||||
)
|
||||
|
||||
|
||||
# ┏┓┏┓ ┓ ┏┓ ┏┓
|
||||
# ┃┃┣┫┓┏╋┣┓┏┛ ┃┫
|
||||
# ┗┛┛┗┗┻┗┛┗┗━•┗┛
|
||||
|
||||
@@ -43,6 +43,7 @@ from typing import Optional, Literal, Union
|
||||
from models.software.tcaoff_ai.auth import TheCAOfficeAIAuth
|
||||
from models.software.mikrotik.auth import MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth
|
||||
from models.software.ecommerce.auth import ShopifyAuth
|
||||
from models.software.places.oauth import GooglePlacesAuth
|
||||
# My utils:
|
||||
from utils_v2.string import regex
|
||||
from utils_v2.date_time import date_time
|
||||
@@ -105,8 +106,8 @@ class SoftwareAuthRequestHeaders(BaseModel):
|
||||
|
||||
class SoftwareAuthRequestData(BaseModel):
|
||||
|
||||
softwareClient: Literal["theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000", "shopify"] = Field(alias = "client")
|
||||
auth: Union[TheCAOfficeAIAuth, MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth, ShopifyAuth]
|
||||
softwareClient: Literal["theCaOfficeAi", "mikrotikPPPoE1000", "mikrotikHotspot1000", "shopify", "googlePlaces"] = Field(alias = "client")
|
||||
auth: Union[TheCAOfficeAIAuth, MikroTikPPPoE1000Auth, MikroTikHotspot1000Auth, ShopifyAuth, GooglePlacesAuth]
|
||||
|
||||
# ┏┓ ┏•
|
||||
# ┃ ┏┓┏┓╋┓┏┓
|
||||
@@ -128,7 +129,8 @@ class SoftwareAuthRequestData(BaseModel):
|
||||
"theCaOfficeAi": TheCAOfficeAIAuth,
|
||||
"mikrotikPPPoE1000": MikroTikPPPoE1000Auth,
|
||||
"mikrotikHotspot1000": MikroTikHotspot1000Auth,
|
||||
"shopify": ShopifyAuth
|
||||
"shopify": ShopifyAuth,
|
||||
"googlePlaces": GooglePlacesAuth
|
||||
}
|
||||
if not isinstance(auth, harmony_map[client]):
|
||||
raise ValueError(f"incorrect 'auth' for selected client '{client}'")
|
||||
|
||||
@@ -38,7 +38,7 @@ sys.path.append("..")
|
||||
|
||||
# For making data behaviour_models:
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from typing import Optional, Literal
|
||||
from typing import Optional, Literal, Any
|
||||
|
||||
# My utils:
|
||||
from utils_v2.string import regex
|
||||
@@ -123,6 +123,56 @@ class OAuthPlacesAuthorizationRequestData(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
class PlacesAuthResponse(BaseModel):
|
||||
|
||||
success: bool = Field(
|
||||
description = "To indicate whether or not, the action was a success",
|
||||
frozen = False,
|
||||
default = False
|
||||
)
|
||||
|
||||
token_id : str = Field(
|
||||
description="MongoDb object id",
|
||||
frozen=False,
|
||||
default=False
|
||||
)
|
||||
|
||||
message: str = Field(
|
||||
description = "To explain what happened in the process of handling the OAuth callback.",
|
||||
frozen = False,
|
||||
default = "ERR: Message not captured."
|
||||
)
|
||||
|
||||
exception: Any = Field(
|
||||
description = "To pass on any exception that occurred in the process.",
|
||||
frozen = False,
|
||||
default = None
|
||||
)
|
||||
|
||||
# ┏┓ ┏•
|
||||
# ┃ ┏┓┏┓╋┓┏┓
|
||||
# ┗┛┗┛┛┗┛┗┗┫
|
||||
# ┛
|
||||
|
||||
class Config:
|
||||
extra = "forbid"
|
||||
|
||||
# ┏┓ ┏┓
|
||||
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
|
||||
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
|
||||
|
||||
pass
|
||||
|
||||
# ┓┏ ┓• ┓ •
|
||||
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
|
||||
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
|
||||
|
||||
pass
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
|
||||
@@ -345,15 +345,15 @@ class PlacesTextSearchRequestData(BaseModel):
|
||||
frozen = True
|
||||
)
|
||||
|
||||
textQuery: float = Field(
|
||||
textQuery: str = Field(
|
||||
description= "Text Query for find places",
|
||||
frozen= True
|
||||
)
|
||||
|
||||
max_count: int = Field(
|
||||
maxCount: int = Field(
|
||||
description= "Max count for get result counts",
|
||||
frozen=True,
|
||||
default=20
|
||||
default=5
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -173,6 +173,41 @@ class OAuthPlacesHandleCallbackResponse(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
class GooglePlacesAuth(BaseModel):
|
||||
email: str = Field(
|
||||
description="Email for google places API",
|
||||
frozen=False
|
||||
)
|
||||
|
||||
apiKey: str = Field(
|
||||
description="API KEY of Places API",
|
||||
frozen=True,
|
||||
)
|
||||
|
||||
displayName: str = Field(
|
||||
description="Display Name",
|
||||
frozen=True,
|
||||
)
|
||||
|
||||
# ┏┓ ┏•
|
||||
# ┃ ┏┓┏┓╋┓┏┓
|
||||
# ┗┛┗┛┛┗┛┗┗┫
|
||||
# ┛
|
||||
|
||||
class Config:
|
||||
extra = "forbid"
|
||||
|
||||
# ┏┓ ┏┓
|
||||
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
|
||||
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
|
||||
|
||||
pass
|
||||
|
||||
# ┓┏ ┓• ┓ •
|
||||
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
|
||||
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
|
||||
|
||||
pass
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
|
||||
Reference in New Issue
Block a user