Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supafunc #2762

Merged
merged 2 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 14 additions & 34 deletions octobot/community/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,55 +631,35 @@ async def fetch_private_data(self, reset=False):
await self._refresh_products()

async def _fetch_package_urls(self, mqtt_uuid: typing.Optional[str]) -> (list[str], str):
self.logger.debug(f"Fetching package")
resp = await self.supabase_client.http_get(
constants.COMMUNITY_EXTENSIONS_CHECK_ENDPOINT,
headers={
"Content-Type": "application/json",
"X-Auth-Token": constants.COMMUNITY_EXTENSIONS_CHECK_ENDPOINT_KEY
},
params={"mqtt_id": mqtt_uuid} if mqtt_uuid else {},
timeout=constants.COMMUNITY_FETCH_TIMEOUT
)
self.logger.debug("Fetched package")
resp.raise_for_status()
json_resp = json.loads(resp.json().get("message", {}))
if not json_resp:
self.logger.debug(f"Fetching extension package details")
extensions_details = await self.supabase_client.fetch_extensions(mqtt_uuid)
self.logger.debug("Fetched extension package details")
if not extensions_details:
return None, None, None
packages = [
package
for package in json_resp["paid_package_slugs"]
for package in extensions_details["paid_package_slugs"]
if package
]
urls = [
url
for url in json_resp["package_urls"]
for url in extensions_details["package_urls"]
if url
]
mqtt_id = json_resp["mqtt_id"]
mqtt_id = extensions_details["mqtt_id"]
return packages, urls, mqtt_id

async def fetch_checkout_url(self, payment_method, redirect_url):
async def fetch_checkout_url(self, payment_method: str, redirect_url: str):
try:
if not self.is_logged_in():
self.logger.info(f"Can't fetch checkout url: no authenticated user")
return None
self.logger.debug(f"Fetching {payment_method} checkout url")
resp = await self.supabase_client.http_post(
constants.COMMUNITY_EXTENSIONS_CHECK_ENDPOINT,
json={
"payment_method": payment_method,
"success_url": redirect_url,
},
headers={
"Content-Type": "application/json",
"X-Auth-Token": constants.COMMUNITY_EXTENSIONS_CHECK_ENDPOINT_KEY
},
timeout=constants.COMMUNITY_FETCH_TIMEOUT
)
resp.raise_for_status()
json_resp = json.loads(resp.json().get("message", {}))
if not json_resp:
url_details = await self.supabase_client.fetch_checkout_url(payment_method, redirect_url)
if not url_details:
# valid error code but no content: user already has this product
return None
url = json_resp["checkout_url"]
url = url_details["checkout_url"]
self.logger.info(
f"Here is your {constants.OCTOBOT_EXTENSION_PACKAGE_1_NAME} checkout url {url} "
f"paste it into a web browser to proceed to payment if your browser did to automatically "
Expand Down
84 changes: 25 additions & 59 deletions octobot/community/supabase_backend/community_supabase_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@
# You should have received a copy of the GNU General Public
# License along with OctoBot. If not, see <https://www.gnu.org/licenses/>.
import asyncio
import base64
import datetime
import time
import typing
import logging
import httpx
import uuid
import json
import contextlib
Expand Down Expand Up @@ -66,41 +64,6 @@ def error_describer():
raise errors.SessionTokenExpiredError(err) from err
raise


def _httpx_retrier(f):
async def httpx_retrier_wrapper(*args, **kwargs):
resp = None
for i in range(0, HTTP_RETRY_COUNT):
error = None
try:
resp: httpx.Response = await f(*args, **kwargs)
if resp.status_code in (502, 503, 520):
# waking up or SLA issue, retry
error = f"{resp.status_code} error {resp.reason_phrase}"
commons_logging.get_logger(__name__).debug(
f"{f.__name__}(args={args[1:]}) failed with {error} after {i+1} attempts, retrying."
)
else:
if i > 0:
commons_logging.get_logger(__name__).debug(
f"{f.__name__}(args={args[1:]}) succeeded after {i+1} attempts"
)
return resp
except httpx.ReadTimeout as err:
error = f"{err} ({err.__class__.__name__})"
# retry
commons_logging.get_logger(__name__).debug(
f"Error on {f.__name__}(args={args[1:]}) "
f"request, retrying now. Attempt {i+1} / {HTTP_RETRY_COUNT} ({error})."
)
# no more attempts
if resp:
resp.raise_for_status()
return resp
else:
raise errors.RequestError(f"Failed to execute {f.__name__}(args={args[1:]} kwargs={kwargs})")
return httpx_retrier_wrapper

class CommunitySupabaseClient(supabase_client.AuthenticatedAsyncSupabaseClient):
"""
Octobot Community layer added to supabase_client.AuthenticatedSupabaseClient
Expand Down Expand Up @@ -240,6 +203,31 @@ async def get_otp_with_auth_key(self, user_email: str, auth_key: str) -> str:
except Exception:
raise authentication.AuthenticationError(f"Invalid auth key authentication details")

async def fetch_extensions(self, mqtt_uuid: typing.Optional[str]) -> dict:
resp = await self.functions.invoke(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"os-paid-package-api",
{
"body": {
"action": "get_extension_details",
"mqtt_id": mqtt_uuid
},
}
)
return json.loads(json.loads(resp)["message"])

async def fetch_checkout_url(self, payment_method: str, redirect_url: str) -> dict:
resp = await self.functions.invoke(
"os-paid-package-api",
{
"body": {
"action": "get_checkout_url",
"payment_method": payment_method,
"success_url": redirect_url,
},
}
)
return json.loads(json.loads(resp)["message"])

async def fetch_bot(self, bot_id) -> dict:
try:
# https://postgrest.org/en/stable/references/api/resource_embedding.html#hint-disambiguation
Expand Down Expand Up @@ -804,28 +792,6 @@ def _get_auth_key(self):
return session.access_token
return self.supabase_key

@_httpx_retrier
async def http_get(self, url: str, *args, params=None, headers=None, **kwargs) -> httpx.Response:
"""
Perform http get using the current supabase auth token
"""
params = params or {}
params["access_token"] = params.get("access_token", base64.b64encode(self._get_auth_key().encode()).decode())
return await self.postgrest.session.get(url, *args, params=params, headers=headers, **kwargs)

@_httpx_retrier
async def http_post(
self, url: str, *args, json=None, params=None, headers=None, **kwargs
) -> httpx.Response:
"""
Perform http get using the current supabase auth token
"""
json_body = json or {}
json_body["access_token"] = json_body.get("access_token", self._get_auth_key())
return await self.postgrest.session.post(
url, *args, json=json_body, params=params, headers=headers, **kwargs
)

@staticmethod
def get_formatted_time(timestamp: float) -> str:
return datetime.datetime.utcfromtimestamp(timestamp).isoformat('T')
Expand Down
Loading