supabase implementation
Some checks failed
Build and deploy the backend to staging / Build and push image (pull_request) Successful in 1m42s
Run linting on the backend code / Build (pull_request) Successful in 27s
Run testing on the backend code / Build (pull_request) Failing after 50s
Build and release debug APK / Build APK (pull_request) Failing after 3m6s
Build and deploy the backend to staging / Deploy to staging (pull_request) Successful in 24s

This commit is contained in:
Helldragon67 2025-02-12 21:36:15 +01:00
parent 3a9ef4e7d3
commit 2c49480966
9 changed files with 165 additions and 70 deletions

File diff suppressed because one or more lines are too long

View File

@ -49,7 +49,7 @@ This file configures the logging system for the application. It defines how logs
This file contains the main application logic and API endpoints for interacting with the system. The application is built using the FastAPI framework, which provides several endpoints for creating trips, fetching trips, and retrieving landmarks or nearby facilities. The key endpoints include:
- **POST /trip/new**:
- This endpoint allows users to create a new trip by specifying preferences, start coordinates, and optionally end coordinates. The preferences guide the optimization process for selecting landmarks.
- This endpoint allows users to create a new trip by specifying user_id, preferences, start coordinates, and optionally end coordinates. The preferences guide the optimization process for selecting landmarks. The user id is needed to verify that the user's credit balance.
- Returns: A `Trip` object containing the optimized route, landmarks, and trip details.
- **GET /trip/{trip_uuid}**:

View File

@ -12,12 +12,13 @@ LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml'
OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml'
PAYPAL_CLIENT_ID = os.getenv("your-paypal-client-id")
PAYPAL_SECRET = os.getenv("your-paypal-secret")
PAYPAL_API_URL = "https://api-m.sandbox.paypal.com"
PAYPAL_CLIENT_ID = 0 # os.getenv("your-paypal-client-id")
PAYPAL_SECRET = 0 # os.getenv("your-paypal-secret")
PAYPAL_API_URL = 0 # "https://api-m.sandbox.paypal.com"
SUPABASE_URL = os.getenv("your-supabase-url")
SUPABASE_KEY = os.getenv("your-supabase-api-key")
SUPABASE_URL = os.environ["SUPABASE_URL"]
SUPABASE_KEY = os.environ["SUPABASE_API_KEY"]
SUPABASE_TEST_USER_ID = os.environ["SUPABASE_TEST_USER_ID"]
cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache')

View File

@ -17,6 +17,7 @@ from .optimization.refiner import Refiner
from .overpass.overpass import fill_cache
from .cache import client as cache_client
from .payments.payment_routes import router as payment_router
from .payments.supabase import Supabase
logger = logging.getLogger(__name__)
@ -24,6 +25,7 @@ logger = logging.getLogger(__name__)
manager = LandmarkManager()
optimizer = Optimizer()
refiner = Refiner(optimizer=optimizer)
supabase = Supabase()
@asynccontextmanager
@ -43,7 +45,8 @@ app.include_router(payment_router, prefix="/payments")
@app.post("/trip/new")
def new_trip(preferences: Preferences,
def new_trip(user_id: str,
preferences: Preferences,
start: tuple[float, float],
end: tuple[float, float] | None = None,
background_tasks: BackgroundTasks = None) -> Trip:
@ -57,6 +60,10 @@ def new_trip(preferences: Preferences,
Returns:
(uuid) : The uuid of the first landmark in the optimized route
"""
# Check for valid user balance.
supabase.check_balance(user_id=user_id)
# Check for invalid input.
if preferences is None:
raise HTTPException(status_code=406, detail="Preferences not provided or incomplete.")
if (preferences.shopping.score == 0 and
@ -143,6 +150,7 @@ def new_trip(preferences: Preferences,
logger.debug('Detailed trip :\n\t' + '\n\t'.join(f'{landmark}' for landmark in refined_tour))
background_tasks.add_task(fill_cache)
supabase.increment_credit_balance(user_id=user_id)
return trip

View File

@ -1,53 +0,0 @@
from fastapi import HTTPException, status
from supabase import create_client, Client
from ..constants import SUPABASE_URL, SUPABASE_KEY
# Initialize Supabase client
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
def is_loc_in_country(location: tuple[float, float], country: str) -> bool:
"""
TODO: needs to be implemented.
"""
pass
def check_region_access(user_id: str, start_location: tuple[float, float]) -> None:
"""
Checks if the user has access to the region where `start_location` is located.
Args:
user_id (str): The ID of the current user.
start_location (tuple): The starting location of the trip (lat, lon).
Raises:
HTTPException: If the region is locked and the user has not unlocked it.
"""
# Define the locked countries (regions that require payment)
locked_countries = ["FR", "DE", "ES"] # Example list of locked countries. Needs to be adapted also.
# Iterate over locked countries and check if the start location is in a locked region
for country in locked_countries:
if is_loc_in_country(start_location, country):
# Query Supabase to check if the user has unlocked this country
response = supabase \
.from_("unlocked_countries") \
.select(f"{country}") \
.eq("user_id", user_id) \
.single() \
.execute()
# Check if the country is unlocked (True) or locked (False or missing)
if response.data and response.data.get(country) is True:
# Country is unlocked; continue
continue
else:
# Raise an exception if the country is not unlocked
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Access to the region {country} is locked. Please unlock it to continue."
)

View File

@ -0,0 +1,107 @@
from fastapi import HTTPException, status
from supabase import create_client, Client
from ..constants import SUPABASE_URL, SUPABASE_KEY
class Supabase:
def __init__(self):
# Initialize Supabase client
print(SUPABASE_URL)
self.supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
def check_balance(self, user_id: str):
"""
Checks if the user has enough 'credit' for generating a new trip.
Args:
user_id (str): The ID of the current user.
Returns:
bool: True if the balance is positive, False otherwise.
"""
try:
# Query the public.credits table to get the user's credits
response = self.supabase.table('credits').select('credits').eq('user_id', user_id).single().execute()
# Check if the response was successful and contains data
if response.get('data') and 'credits' in response['data']:
credits = response['data']['credits']
if credits <= 0:
raise HTTPException(status_code=403, detail="Insufficient credits to perform this action.")
# If user or credits not found, raise a 404 error
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User data not found.")
except Exception:
# Handle exceptions (like if the user ID doesn't exist or database issues)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error retrieving credit balance")
def decrement_credit_balance(self, user_id: str) -> bool:
"""
Decrements the user's credit balance by 1.
Args:
user_id (str): The ID of the current user.
"""
try:
# Query the public.credits table to get the user's current credits
response = self.supabase.table('credits').select('credits').eq('user_id', user_id).single().execute()
# Check if the response was successful and contains data
if response.get('data') and 'credits' in response['data']:
current_credits = response['data']['credits']
# Decrement the credit balance by 1
updated_credits = current_credits - 1
# Update the user's credits in the table
update_response = self.supabase.table('credits').update({'credits': updated_credits}).eq('user_id', user_id).execute()
# Check if the update was successful
if update_response.get('status_code') == 200:
return True
else:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error updating credit balance.")
# If user or credits not found, raise a 404 error
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User data not found.")
except Exception:
# Handle exceptions (like if the user ID doesn't exist or there are database issues)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error updating credit balance")
def increment_credit_balance(self, user_id: str) -> bool:
"""
Increments the user's credit balance by 1.
Args:
user_id (str): The ID of the current user.
"""
try:
# Query the public.credits table to get the user's current credits
response = self.supabase.table('credits').select('credits').eq('user_id', user_id).single().execute()
# Check if the response was successful and contains data
if response.get('data') and 'credits' in response['data']:
current_credits = response['data']['credits']
# Increment the credit balance by 1
updated_credits = current_credits + 1
# Update the user's credits in the table
update_response = self.supabase.table('credits').update({'credits': updated_credits}).eq('user_id', user_id).execute()
# Check if the update was successful
if update_response.get('status_code') == 200:
return True
else:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error updating credit balance.")
# If user or credits not found, raise a 404 error
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User data not found.")
except Exception:
# Handle exceptions (like if the user ID doesn't exist or there are database issues)
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error updating credit balance")

View File

@ -4,6 +4,7 @@ from fastapi.testclient import TestClient
import pytest
from ..main import app
from ..constants import SUPABASE_TEST_USER_ID
@pytest.fixture(scope="module")
@ -55,6 +56,7 @@ def test_input(invalid_client, start, preferences, status_code): # pylint: dis
response = invalid_client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": preferences,
"start": start
}

View File

@ -5,6 +5,8 @@ import pytest
from .test_utils import load_trip_landmarks, log_trip_details
from ..main import app
from ..constants import SUPABASE_TEST_USER_ID
from ..payments.supabase import Supabase
@pytest.fixture(scope="module")
def client():
@ -26,6 +28,7 @@ def test_turckheim(client, request): # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 0},
"shopping": {"type": "shopping", "score": 0},
@ -37,6 +40,8 @@ def test_turckheim(client, request): # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
@ -58,6 +63,8 @@ def test_turckheim(client, request): # pylint: disable=redefined-outer-name
assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}"
# assert 2!= 3
'''
def test_bellecour(client, request) : # pylint: disable=redefined-outer-name
"""
Test n°2 : Custom test in Lyon centre to ensure proper decision making in crowded area.
@ -73,6 +80,7 @@ def test_bellecour(client, request) : # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 5},
"shopping": {"type": "shopping", "score": 5},
@ -82,6 +90,8 @@ def test_bellecour(client, request) : # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
# Get computation time
@ -113,6 +123,7 @@ def test_cologne(client, request) : # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 5},
"shopping": {"type": "shopping", "score": 5},
@ -122,6 +133,8 @@ def test_cologne(client, request) : # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
# Get computation time
@ -154,6 +167,7 @@ def test_strasbourg(client, request) : # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 5},
"shopping": {"type": "shopping", "score": 5},
@ -163,6 +177,8 @@ def test_strasbourg(client, request) : # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
# Get computation time
@ -195,6 +211,7 @@ def test_zurich(client, request) : # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 5},
"shopping": {"type": "shopping", "score": 5},
@ -204,6 +221,8 @@ def test_zurich(client, request) : # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
# Get computation time
@ -236,6 +255,7 @@ def test_paris(client, request) : # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 0},
"shopping": {"type": "shopping", "score": 5},
@ -245,6 +265,8 @@ def test_paris(client, request) : # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
# Get computation time
@ -277,6 +299,7 @@ def test_new_york(client, request) : # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 5},
"nature": {"type": "nature", "score": 5},
"shopping": {"type": "shopping", "score": 5},
@ -286,6 +309,8 @@ def test_new_york(client, request) : # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
# Get computation time
@ -318,6 +343,7 @@ def test_shopping(client, request) : # pylint: disable=redefined-outer-name
response = client.post(
"/trip/new",
json={
"user_id": SUPABASE_TEST_USER_ID,
"preferences": {"sightseeing": {"type": "sightseeing", "score": 0},
"nature": {"type": "nature", "score": 0},
"shopping": {"type": "shopping", "score": 5},
@ -327,6 +353,8 @@ def test_shopping(client, request) : # pylint: disable=redefined-outer-name
}
)
result = response.json()
supabase = Supabase()
supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID)
landmarks = load_trip_landmarks(client, result['first_landmark_uuid'])
# Get computation time
@ -342,4 +370,5 @@ def test_shopping(client, request) : # pylint: disable=redefined-outer-name
assert response.status_code == 200 # check for successful planning
assert comp_time < 30, f"Computation time exceeded 30 seconds: {comp_time:.2f} seconds"
assert duration_minutes*0.8 < result['total_time'], f"Trip too short: {result['total_time']} instead of {duration_minutes}"
assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}"
assert duration_minutes*1.2 > result['total_time'], f"Trip too long: {result['total_time']} instead of {duration_minutes}"
'''

View File

@ -33,6 +33,7 @@ fetchTrip(
UserPreferences preferences,
) async {
Map<String, dynamic> data = {
// Add user ID here for API request
"preferences": preferences.toJson(),
"start": trip.landmarks!.first.location,
};