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
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:
parent
3a9ef4e7d3
commit
2c49480966
File diff suppressed because one or more lines are too long
@ -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}**:
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
||||
|
@ -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."
|
||||
)
|
||||
|
107
backend/src/payments/supabase.py
Normal file
107
backend/src/payments/supabase.py
Normal 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")
|
@ -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
|
||||
}
|
||||
|
@ -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}"
|
||||
'''
|
@ -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,
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user