backend/feature/supabase #60
							
								
								
									
										3
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								backend/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,9 @@ | |||||||
| # osm-cache | # osm-cache | ||||||
| cache_XML/ | cache_XML/ | ||||||
|  |  | ||||||
|  | # secrets | ||||||
|  | *secrets.yaml | ||||||
|  |  | ||||||
| # Byte-compiled / optimized / DLL files | # Byte-compiled / optimized / DLL files | ||||||
| __pycache__/ | __pycache__/ | ||||||
| *.py[cod] | *.py[cod] | ||||||
|   | |||||||
| @@ -328,7 +328,7 @@ div.media { | |||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <h1 id="title">Backend Testing Report</h1> |     <h1 id="title">Backend Testing Report</h1> | ||||||
|     <p>Report generated on 12-Feb-2025 at 21:34:08 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a> |     <p>Report generated on 14-Feb-2025 at 10:33:15 by <a href="https://pypi.python.org/pypi/pytest-html">pytest-html</a> | ||||||
|         v4.1.1</p> |         v4.1.1</p> | ||||||
|     <div id="environment-header"> |     <div id="environment-header"> | ||||||
|       <h2>Environment</h2> |       <h2>Environment</h2> | ||||||
| @@ -382,7 +382,7 @@ div.media { | |||||||
|         <h2>Summary</h2> |         <h2>Summary</h2> | ||||||
|         <div class="additional-summary prefix"> |         <div class="additional-summary prefix"> | ||||||
|         </div> |         </div> | ||||||
|         <p class="run-count">0 test took 0 ms.</p> |         <p class="run-count">1 test took 681 ms.</p> | ||||||
|         <p class="filter">(Un)check the boxes to filter the results.</p> |         <p class="filter">(Un)check the boxes to filter the results.</p> | ||||||
|         <div class="summary__reload"> |         <div class="summary__reload"> | ||||||
|           <div class="summary__reload__button hidden" onclick="location.reload()"> |           <div class="summary__reload__button hidden" onclick="location.reload()"> | ||||||
| @@ -394,16 +394,16 @@ div.media { | |||||||
|           <div class="filters"> |           <div class="filters"> | ||||||
|             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="failed" disabled/> |             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="failed" disabled/> | ||||||
|             <span class="failed">0 Failed,</span> |             <span class="failed">0 Failed,</span> | ||||||
|             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="passed" disabled/> |             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="passed" /> | ||||||
|             <span class="passed">0 Passed,</span> |             <span class="passed">1 Passed,</span> | ||||||
|             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="skipped" disabled/> |             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="skipped" disabled/> | ||||||
|             <span class="skipped">0 Skipped,</span> |             <span class="skipped">0 Skipped,</span> | ||||||
|             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xfailed" disabled/> |             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xfailed" disabled/> | ||||||
|             <span class="xfailed">0 Expected failures,</span> |             <span class="xfailed">0 Expected failures,</span> | ||||||
|             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xpassed" disabled/> |             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="xpassed" disabled/> | ||||||
|             <span class="xpassed">0 Unexpected passes,</span> |             <span class="xpassed">0 Unexpected passes,</span> | ||||||
|             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="error" /> |             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="error" disabled/> | ||||||
|             <span class="error">1 Errors,</span> |             <span class="error">0 Errors,</span> | ||||||
|             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="rerun" disabled/> |             <input checked="true" class="filter" name="filter_checkbox" type="checkbox" data-test-result="rerun" disabled/> | ||||||
|             <span class="rerun">0 Reruns</span> |             <span class="rerun">0 Reruns</span> | ||||||
|           </div> |           </div> | ||||||
| @@ -432,7 +432,7 @@ div.media { | |||||||
|     </table> |     </table> | ||||||
|   </body> |   </body> | ||||||
|   <footer> |   <footer> | ||||||
|     <div id="data-container" data-jsonblob="{"environment": {"Python": "3.12.3", "Platform": "Linux-6.8.0-52-generic-x86_64-with-glibc2.39", "Packages": {"pytest": "8.3.4", "pluggy": "1.5.0"}, "Plugins": {"html": "4.1.1", "anyio": "4.8.0", "metadata": "3.1.1"}}, "tests": {"src/tests/test_main.py": [{"extras": [], "result": "Error", "testId": "src/tests/test_main.py::collect", "resultsTableRow": ["<td class=\"col-result\">Error</td>", "<td class=\"col-testId\">src/tests/test_main.py::collect</td>", "<td>N/A</td>", "<td>N/A</td>", "<td>N/A</td>", "<td class=\"col-duration\">0 ms</td>", "<td class=\"col-links\"></td>"], "log": "src/tests/test_main.py:6: in &lt;module&gt;\n    from .test_utils import load_trip_landmarks, log_trip_details\n&lt;frozen importlib._bootstrap&gt;:1360: in _find_and_load\n    ???\n&lt;frozen importlib._bootstrap&gt;:1331: in _find_and_load_unlocked\n    ???\n&lt;frozen importlib._bootstrap&gt;:935: in _load_unlocked\n    ???\n../../../../.local/share/virtualenvs/backend-rQjUbAgS/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:184: in exec_module\n    exec(co, module.__dict__)\nsrc/tests/test_utils.py:7: in &lt;module&gt;\n    from ..cache import client as cache_client\nsrc/cache.py:5: in &lt;module&gt;\n    from .constants import MEMCACHED_HOST_PATH\nsrc/constants.py:19: in &lt;module&gt;\n    SUPABASE_URL = os.environ[&quot;SUPABASE_URL&quot;]\n&lt;frozen os&gt;:685: in __getitem__\n    ???\nE   KeyError: &#x27;SUPABASE_URL&#x27;\n"}]}, "renderCollapsed": ["passed"], "initialSort": "result", "title": "Backend Testing Report"}"></div> |     <div id="data-container" data-jsonblob="{"environment": {"Python": "3.12.3", "Platform": "Linux-6.8.0-53-generic-x86_64-with-glibc2.39", "Packages": {"pytest": "8.3.4", "pluggy": "1.5.0"}, "Plugins": {"html": "4.1.1", "anyio": "4.8.0", "metadata": "3.1.1"}}, "tests": {"src/tests/test_main.py::test_turckheim": [{"extras": [], "result": "Passed", "testId": "src/tests/test_main.py::test_turckheim", "resultsTableRow": ["<td class=\"col-result\">Passed</td>", "<td class=\"col-testId\">src/tests/test_main.py::test_turckheim</td>", "<td>start (0 | 0) - 6 - H\u00f4tel de ville (238 | 5) - 1 - H\u00f4tel des Deux-Clefs (217 | 5) - 6 - finish (0 | 0) - 0</td>", "<td>23 min</td>", "<td>20 min</td>", "<td class=\"col-duration\">681 ms</td>", "<td class=\"col-links\"></td>"], "log": "------------------------------ Captured log call -------------------------------\nDEBUG    src.tests.test_main:test_main.py:30 Running test in Turckheim\nDEBUG    asyncio:selector_events.py:64 Using selector: EpollSelector\nDEBUG    src.main:main.py:65 user id: e6dae20d-50ed-4241-8e16-af1ad1f3572f\nDEBUG    src.payments.supabase:supabase.py:53 Credits of user e6dae20d-50ed-4241-8e16-af1ad1f3572f: 999999\nINFO     src.payments.supabase:supabase.py:56 Credit balance is positive. Proceeding with trip generation\nINFO     src.main:main.py:88 No end coordinates provided. Using start=end.\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:75 Starting to fetch landmarks...\nDEBUG    src.utils.landmarks_manager:landmarks_manager.py:87 Fetching sightseeing landmarks...\nDEBUG    src.overpass.overpass:overpass.py:55 Cache hit for 1/1 quadrants.\nDEBUG    src.overpass.overpass:overpass.py:55 Cache hit for 1/1 quadrants.\nDEBUG    src.overpass.overpass:overpass.py:55 Cache hit for 1/1 quadrants.\nDEBUG    src.overpass.overpass:overpass.py:55 Cache hit for 1/1 quadrants.\nDEBUG    src.overpass.overpass:overpass.py:55 Cache hit for 1/1 quadrants.\nDEBUG    src.overpass.overpass:overpass.py:55 Cache hit for 1/1 quadrants.\nINFO     src.utils.landmarks_manager:landmarks_manager.py:90 Found 9 sightseeing landmarks\nDEBUG    src.overpass.overpass:overpass.py:55 Cache hit for 1/1 quadrants.\nINFO     src.utils.cluster_manager:cluster_manager.py:145 Found 0 sightseeing clusters.\nINFO     src.main:main.py:125 Fetched 7 landmarks in  \t: 0.004 seconds\nDEBUG    src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting...\nINFO     src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 455\nDEBUG    src.optimization.refiner:refiner.py:345 Using 4 minor landmarks around the predicted path\nDEBUG    src.optimization.optimizer:optimizer.py:597 First results are out. Looking out for circles and correcting...\nINFO     src.optimization.optimizer:optimizer.py:637 Re-optimized 0 times, objective value : 455\nDEBUG    src.main:main.py:151 First stage optimization\t: 0.023 seconds\nDEBUG    src.main:main.py:152 Second stage optimization\t: 0.024 seconds\nINFO     src.main:main.py:153 Total computation time\t: 0.048 seconds\nINFO     src.main:main.py:158 Generated a trip of 23 minutes with 4 landmarks in 0.052 seconds.\nDEBUG    src.main:main.py:159 Detailed trip :\n\tLandmark(start): [start @(48.084588, 7.280405), score=0, time_to_next=6]\n\tLandmark(sightseeing): [H\u00f4tel de ville @(48.0874108, 7.2779686), score=238, time_to_next=1]\n\tLandmark(sightseeing): [H\u00f4tel des Deux-Clefs @(48.0871764, 7.2776559), score=217, time_to_next=6]\n\tLandmark(finish): [finish @(48.084588, 7.280405), score=0]\nDEBUG    src.payments.supabase:supabase.py:100 Credit balance successfully decremented.\nDEBUG    src.tests.test_main:test_main.py:47 API response : {&#x27;uuid&#x27;: &#x27;e139ea9c-a290-46df-b30f-7c7530ac7480&#x27;, &#x27;total_time&#x27;: 23, &#x27;first_landmark_uuid&#x27;: &#x27;efc6ecc0-345b-4b43-9d47-fd2ec1897074&#x27;}\nDEBUG    src.payments.supabase:supabase.py:143 Credit balance successfully incremented.\n\n"}]}, "renderCollapsed": ["passed"], "initialSort": "result", "title": "Backend Testing Report"}"></div> | ||||||
|     <script> |     <script> | ||||||
|       (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ |       (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||||||
| const { getCollapsedCategory, setCollapsedIds } = require('./storage.js') | const { getCollapsedCategory, setCollapsedIds } = require('./storage.js') | ||||||
|   | |||||||
| @@ -12,13 +12,13 @@ LANDMARK_PARAMETERS_PATH = PARAMETERS_DIR / 'landmark_parameters.yaml' | |||||||
| OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml' | OPTIMIZER_PARAMETERS_PATH = PARAMETERS_DIR / 'optimizer_parameters.yaml' | ||||||
|  |  | ||||||
|  |  | ||||||
| PAYPAL_CLIENT_ID = 0 # os.getenv("your-paypal-client-id") | PAYPAL_CLIENT_ID = os.getenv("future-paypal-client-id", None) | ||||||
| PAYPAL_SECRET = 0 # os.getenv("your-paypal-secret") | PAYPAL_SECRET = os.getenv("future-paypal-secret", None) | ||||||
| PAYPAL_API_URL = 0 # "https://api-m.sandbox.paypal.com" | PAYPAL_API_URL = "https://api-m.sandbox.paypal.com" | ||||||
|  |  | ||||||
| SUPABASE_URL = os.environ["SUPABASE_URL"] | SUPABASE_URL = os.getenv("SUPABASE_URL", None) | ||||||
| SUPABASE_KEY = os.environ["SUPABASE_API_KEY"] | SUPABASE_KEY = os.getenv("SUPABASE_API_KEY", None) | ||||||
| SUPABASE_TEST_USER_ID = os.environ["SUPABASE_TEST_USER_ID"] | SUPABASE_TEST_USER_ID = os.getenv("SUPABASE_TEST_USER_ID", None) | ||||||
|  |  | ||||||
|  |  | ||||||
| cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache') | cache_dir_string = os.getenv('OSM_CACHE_DIR', './cache') | ||||||
|   | |||||||
| @@ -1,9 +1,10 @@ | |||||||
| """Main app for backend api""" | """Main app for backend api""" | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  | import traceback | ||||||
| import time | import time | ||||||
| from contextlib import asynccontextmanager | from contextlib import asynccontextmanager | ||||||
| from fastapi import FastAPI, HTTPException, BackgroundTasks, Query | from fastapi import FastAPI, HTTPException, BackgroundTasks, Query, Body | ||||||
|  |  | ||||||
| from .logging_config import configure_logging | from .logging_config import configure_logging | ||||||
| from .structs.landmark import Landmark, Toilets | from .structs.landmark import Landmark, Toilets | ||||||
| @@ -16,7 +17,7 @@ from .optimization.optimizer import Optimizer | |||||||
| from .optimization.refiner import Refiner | from .optimization.refiner import Refiner | ||||||
| from .overpass.overpass import fill_cache | from .overpass.overpass import fill_cache | ||||||
| from .cache import client as cache_client | from .cache import client as cache_client | ||||||
| from .payments.payment_routes import router as payment_router | # from .payments.payment_routes import router as payment_router | ||||||
| from .payments.supabase import Supabase | from .payments.supabase import Supabase | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -41,14 +42,14 @@ app = FastAPI(lifespan=lifespan) | |||||||
|  |  | ||||||
|  |  | ||||||
| # Include the payment routes | # Include the payment routes | ||||||
| app.include_router(payment_router, prefix="/payments") | # app.include_router(payment_router, prefix="/payments") | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.post("/trip/new") | @app.post("/trip/new") | ||||||
| def new_trip(user_id: str, | def new_trip(user_id: str = Body(...), | ||||||
|              preferences: Preferences, |              preferences: Preferences = Body(...), | ||||||
|              start: tuple[float, float], |              start: tuple[float, float] = Body(...), | ||||||
|              end: tuple[float, float] | None = None,  |              end: tuple[float, float] | None = Body(None),  | ||||||
|              background_tasks: BackgroundTasks = None) -> Trip: |              background_tasks: BackgroundTasks = None) -> Trip: | ||||||
|     """ |     """ | ||||||
|     Main function to call the optimizer. |     Main function to call the optimizer. | ||||||
| @@ -61,7 +62,15 @@ def new_trip(user_id: str, | |||||||
|         (uuid) : The uuid of the first landmark in the optimized route |         (uuid) : The uuid of the first landmark in the optimized route | ||||||
|     """ |     """ | ||||||
|     # Check for valid user balance. |     # Check for valid user balance. | ||||||
|     supabase.check_balance(user_id=user_id) |     logger.debug(f'user id: {user_id}') | ||||||
|  |  | ||||||
|  |     try :  | ||||||
|  |         if not supabase.check_balance(user_id=user_id) : | ||||||
|  |             logger.warning('Insufficient credits to perform this action.') | ||||||
|  |             return None | ||||||
|  |     except Exception as exc : | ||||||
|  |         traceback.print_exc()   | ||||||
|  |         raise HTTPException(status_code=500, detail=str(exc)) from exc | ||||||
|  |  | ||||||
|     # Check for invalid input. |     # Check for invalid input. | ||||||
|     if preferences is None: |     if preferences is None: | ||||||
| @@ -150,7 +159,7 @@ def new_trip(user_id: str, | |||||||
|     logger.debug('Detailed trip :\n\t' + '\n\t'.join(f'{landmark}' for landmark in refined_tour)) |     logger.debug('Detailed trip :\n\t' + '\n\t'.join(f'{landmark}' for landmark in refined_tour)) | ||||||
|  |  | ||||||
|     background_tasks.add_task(fill_cache) |     background_tasks.add_task(fill_cache) | ||||||
|     supabase.increment_credit_balance(user_id=user_id) |     supabase.decrement_credit_balance(user_id=user_id) | ||||||
|  |  | ||||||
|     return trip |     return trip | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,15 +1,34 @@ | |||||||
|  | import os | ||||||
|  | import logging | ||||||
|  | import yaml | ||||||
| from fastapi import HTTPException, status | from fastapi import HTTPException, status | ||||||
| from supabase import create_client, Client | from supabase import create_client, Client, ClientOptions | ||||||
| from ..constants import SUPABASE_URL, SUPABASE_KEY |  | ||||||
|  | from ..constants import PARAMETERS_DIR | ||||||
|  |  | ||||||
|  | # Silence the supabase logger | ||||||
|  | logging.getLogger("httpx").setLevel(logging.CRITICAL) | ||||||
|  | logging.getLogger("hpack").setLevel(logging.CRITICAL) | ||||||
|  | logging.getLogger("httpcore").setLevel(logging.CRITICAL) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Supabase: | class Supabase: | ||||||
|  |  | ||||||
|  |     logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         # Initialize Supabase client |  | ||||||
|         print(SUPABASE_URL) |         with open(os.path.join(PARAMETERS_DIR, 'secrets.yaml')) as f: | ||||||
|         self.supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) |             secrets = yaml.safe_load(f) | ||||||
|  |             self.SUPABASE_URL = secrets['SUPABASE_URL'] | ||||||
|  |             self.SUPABASE_KEY = secrets['SUPABASE_API_KEY'] | ||||||
|  |             self.SUPABASE_TEST_USER_ID = secrets['SUPABASE_TEST_USER_ID'] | ||||||
|  |  | ||||||
|  |         self.supabase: Client = create_client(self.SUPABASE_URL, self.SUPABASE_KEY, options=ClientOptions(schema='public')) | ||||||
|  |         self.logger.debug('Supabase client initialized.') | ||||||
|  |  | ||||||
|  |  | ||||||
|     def check_balance(self, user_id: str): |     def check_balance(self, user_id: str) -> bool: | ||||||
|         """ |         """ | ||||||
|         Checks if the user has enough 'credit' for generating a new trip. |         Checks if the user has enough 'credit' for generating a new trip. | ||||||
|  |  | ||||||
| @@ -21,22 +40,30 @@ class Supabase: | |||||||
|         """ |         """ | ||||||
|         try: |         try: | ||||||
|             # Query the public.credits table to get the user's credits |             # 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() |             response = ( | ||||||
|  |                 self.supabase.table("credits") | ||||||
|  |                 .select('*') | ||||||
|  |                 .eq('id', user_id) | ||||||
|  |                 .single() | ||||||
|  |                 .execute() | ||||||
|  |             ) | ||||||
|             # Check if the response was successful and contains data |             # Check if the response was successful and contains data | ||||||
|             if response.get('data') and 'credits' in response['data']: |             if response.data : | ||||||
|                 credits = response['data']['credits'] |                 credits = response.data['credit_amount'] | ||||||
|                  |                 self.logger.debug(f'Credits of user {user_id}: {credits}') | ||||||
|                 if credits <= 0:  |  | ||||||
|                     raise HTTPException(status_code=403, detail="Insufficient credits to perform this action.") |  | ||||||
|  |  | ||||||
|  |                 if credits > 0: | ||||||
|  |                     self.logger.info(f'Credit balance is positive. Proceeding with trip generation') | ||||||
|  |                     return True | ||||||
|  |  | ||||||
|  |                 self.logger.warning(f'Insufficient balance. Trip generation cannot be granted') | ||||||
|  |                 return False | ||||||
|  |  | ||||||
|             # If user or credits not found, raise a 404 error |             # If user or credits not found, raise a 404 error | ||||||
|             raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User data not found.") |             raise HTTPException(status_code=404, detail="User data not found.") | ||||||
|  |  | ||||||
|         except Exception: |         except Exception as exc: | ||||||
|             # Handle exceptions (like if the user ID doesn't exist or database issues) |             raise HTTPException(status_code=500, detail="Error retrieving credit balance") from exc | ||||||
|             raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error retrieving credit balance") |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def decrement_credit_balance(self, user_id: str) -> bool: |     def decrement_credit_balance(self, user_id: str) -> bool: | ||||||
| @@ -48,30 +75,39 @@ class Supabase: | |||||||
|         """ |         """ | ||||||
|         try: |         try: | ||||||
|             # Query the public.credits table to get the user's current credits |             # 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() |             response = ( | ||||||
|  |                 self.supabase.table("credits") | ||||||
|  |                 .select('*') | ||||||
|  |                 .eq('id', user_id) | ||||||
|  |                 .single() | ||||||
|  |                 .execute() | ||||||
|  |             ) | ||||||
|             # Check if the response was successful and contains data |             # Check if the response was successful and contains data | ||||||
|             if response.get('data') and 'credits' in response['data']: |             if response.data : | ||||||
|                 current_credits = response['data']['credits'] |                 current_credits = response.data['credit_amount'] | ||||||
|                 # Decrement the credit balance by 1 |  | ||||||
|                 updated_credits = current_credits - 1 |                 updated_credits = current_credits - 1 | ||||||
|  |  | ||||||
|                 # Update the user's credits in the table |                 # Update the user's credits in the table | ||||||
|                 update_response = self.supabase.table('credits').update({'credits': updated_credits}).eq('user_id', user_id).execute() |                 update_response = ( | ||||||
|  |                     self.supabase.table('credits') | ||||||
|  |                     .update({'credit_amount': updated_credits}) | ||||||
|  |                     .eq('id', user_id) | ||||||
|  |                     .execute() | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|                 # Check if the update was successful |                 # Check if the update was successful | ||||||
|                 if update_response.get('status_code') == 200: |                 if update_response.data: | ||||||
|  |                     self.logger.debug(f'Credit balance successfully decremented.') | ||||||
|                     return True |                     return True | ||||||
|                 else: |                 else: | ||||||
|                     raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error updating credit balance.") |                     raise HTTPException(status_code=500, detail="Error decrementing credit balance.") | ||||||
|  |  | ||||||
|             # If user or credits not found, raise a 404 error |             # If user or credits not found, raise a 404 error | ||||||
|             raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User data not found.") |             raise HTTPException(status_code=404, detail="User data not found.") | ||||||
|  |  | ||||||
|         except Exception: |         except Exception: | ||||||
|             # Handle exceptions (like if the user ID doesn't exist or there are database issues) |             # 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") |             raise HTTPException(status_code=500, detail="Error decrementing credit balance") | ||||||
|  |  | ||||||
|  |  | ||||||
|     def increment_credit_balance(self, user_id: str) -> bool: |     def increment_credit_balance(self, user_id: str) -> bool: | ||||||
|         """ |         """ | ||||||
| @@ -82,26 +118,36 @@ class Supabase: | |||||||
|         """ |         """ | ||||||
|         try: |         try: | ||||||
|             # Query the public.credits table to get the user's current credits |             # 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() |             response = ( | ||||||
|  |                 self.supabase.table("credits") | ||||||
|  |                 .select('*') | ||||||
|  |                 .eq('id', user_id) | ||||||
|  |                 .single() | ||||||
|  |                 .execute() | ||||||
|  |             ) | ||||||
|             # Check if the response was successful and contains data |             # Check if the response was successful and contains data | ||||||
|             if response.get('data') and 'credits' in response['data']: |             if response.data : | ||||||
|                 current_credits = response['data']['credits'] |                 current_credits = response.data['credit_amount'] | ||||||
|                 # Increment the credit balance by 1 |  | ||||||
|                 updated_credits = current_credits + 1 |                 updated_credits = current_credits + 1 | ||||||
|  |  | ||||||
|                 # Update the user's credits in the table |                 # Update the user's credits in the table | ||||||
|                 update_response = self.supabase.table('credits').update({'credits': updated_credits}).eq('user_id', user_id).execute() |                 update_response = ( | ||||||
|  |                     self.supabase.table('credits') | ||||||
|  |                     .update({'credit_amount': updated_credits}) | ||||||
|  |                     .eq('id', user_id) | ||||||
|  |                     .execute() | ||||||
|  |                 ) | ||||||
|  |  | ||||||
|                 # Check if the update was successful |                 # Check if the update was successful | ||||||
|                 if update_response.get('status_code') == 200: |                 if update_response.data: | ||||||
|  |                     self.logger.debug(f'Credit balance successfully incremented.') | ||||||
|                     return True |                     return True | ||||||
|                 else: |                 else: | ||||||
|                     raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error updating credit balance.") |                     raise HTTPException(status_code=500, detail="Error incrementing credit balance.") | ||||||
|  |  | ||||||
|             # If user or credits not found, raise a 404 error |             # If user or credits not found, raise a 404 error | ||||||
|             raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User data not found.") |             raise HTTPException(status_code=404, detail="User data not found.") | ||||||
|  |  | ||||||
|         except Exception: |         except Exception: | ||||||
|             # Handle exceptions (like if the user ID doesn't exist or there are database issues) |             # 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") |             raise HTTPException(status_code=500, detail="Error incrementing credit balance") | ||||||
|   | |||||||
| @@ -1,13 +1,16 @@ | |||||||
| """Collection of tests to ensure correct implementation and track progress. """ | """Collection of tests to ensure correct implementation and track progress. """ | ||||||
| import time | import time | ||||||
|  | import logging | ||||||
| from fastapi.testclient import TestClient | from fastapi.testclient import TestClient | ||||||
| import pytest | import pytest | ||||||
|  |  | ||||||
| from .test_utils import load_trip_landmarks, log_trip_details | from .test_utils import load_trip_landmarks, log_trip_details | ||||||
| from ..main import app | from ..main import app | ||||||
| from ..constants import SUPABASE_TEST_USER_ID |  | ||||||
| from ..payments.supabase import Supabase | from ..payments.supabase import Supabase | ||||||
|  |  | ||||||
|  | supabase = Supabase() | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
| @pytest.fixture(scope="module") | @pytest.fixture(scope="module") | ||||||
| def client(): | def client(): | ||||||
|     """Client used to call the app.""" |     """Client used to call the app.""" | ||||||
| @@ -24,24 +27,25 @@ def test_turckheim(client, request):    # pylint: disable=redefined-outer-name | |||||||
|     """ |     """ | ||||||
|     start_time = time.time()  # Start timer |     start_time = time.time()  # Start timer | ||||||
|     duration_minutes = 20 |     duration_minutes = 20 | ||||||
|  |     logger.debug('Running test in Turckheim') | ||||||
|  |  | ||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|             "nature": {"type": "nature", "score": 0}, |                 "nature": {"type": "nature", "score": 0}, | ||||||
|             "shopping": {"type": "shopping", "score": 0}, |                 "shopping": {"type": "shopping", "score": 0}, | ||||||
|             "max_time_minute": duration_minutes, |                 "max_time_minute": duration_minutes, | ||||||
|             "detour_tolerance_minute": 0}, |                 "detour_tolerance_minute": 0}, | ||||||
|             "start": [48.084588, 7.280405] |             "start": [48.084588, 7.280405] | ||||||
|             # "start": [45.74445023349939, 4.8222687890538865] |             # "start": [45.74445023349939, 4.8222687890538865] | ||||||
|             # "start": [45.75156398104873, 4.827154464827647] |             # "start": [45.75156398104873, 4.827154464827647] | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     logger.debug(f'API response : {result}') | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -80,7 +84,7 @@ def test_bellecour(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|                             "nature": {"type": "nature", "score": 5}, |                             "nature": {"type": "nature", "score": 5}, | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |                             "shopping": {"type": "shopping", "score": 5}, | ||||||
| @@ -90,8 +94,7 @@ def test_bellecour(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|     # Get computation time |     # Get computation time | ||||||
| @@ -123,7 +126,7 @@ def test_cologne(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|                             "nature": {"type": "nature", "score": 5}, |                             "nature": {"type": "nature", "score": 5}, | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |                             "shopping": {"type": "shopping", "score": 5}, | ||||||
| @@ -133,8 +136,7 @@ def test_cologne(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|     # Get computation time |     # Get computation time | ||||||
| @@ -167,7 +169,7 @@ def test_strasbourg(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|                             "nature": {"type": "nature", "score": 5}, |                             "nature": {"type": "nature", "score": 5}, | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |                             "shopping": {"type": "shopping", "score": 5}, | ||||||
| @@ -177,8 +179,7 @@ def test_strasbourg(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|     # Get computation time |     # Get computation time | ||||||
| @@ -211,7 +212,7 @@ def test_zurich(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|                             "nature": {"type": "nature", "score": 5}, |                             "nature": {"type": "nature", "score": 5}, | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |                             "shopping": {"type": "shopping", "score": 5}, | ||||||
| @@ -221,8 +222,7 @@ def test_zurich(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|     # Get computation time |     # Get computation time | ||||||
| @@ -255,7 +255,7 @@ def test_paris(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|                             "nature": {"type": "nature", "score": 0}, |                             "nature": {"type": "nature", "score": 0}, | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |                             "shopping": {"type": "shopping", "score": 5}, | ||||||
| @@ -265,8 +265,7 @@ def test_paris(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|     # Get computation time |     # Get computation time | ||||||
| @@ -299,7 +298,7 @@ def test_new_york(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 5}, | ||||||
|                             "nature": {"type": "nature", "score": 5}, |                             "nature": {"type": "nature", "score": 5}, | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |                             "shopping": {"type": "shopping", "score": 5}, | ||||||
| @@ -309,8 +308,7 @@ def test_new_york(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|     # Get computation time |     # Get computation time | ||||||
| @@ -343,7 +341,7 @@ def test_shopping(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|     response = client.post( |     response = client.post( | ||||||
|         "/trip/new", |         "/trip/new", | ||||||
|         json={ |         json={ | ||||||
|             "user_id": SUPABASE_TEST_USER_ID, |             "user_id": supabase.SUPABASE_TEST_USER_ID, | ||||||
|             "preferences": {"sightseeing": {"type": "sightseeing", "score": 0}, |             "preferences": {"sightseeing": {"type": "sightseeing", "score": 0}, | ||||||
|                             "nature": {"type": "nature", "score": 0}, |                             "nature": {"type": "nature", "score": 0}, | ||||||
|                             "shopping": {"type": "shopping", "score": 5}, |                             "shopping": {"type": "shopping", "score": 5}, | ||||||
| @@ -353,8 +351,7 @@ def test_shopping(client, request) :   # pylint: disable=redefined-outer-name | |||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     result = response.json() |     result = response.json() | ||||||
|     supabase = Supabase() |     supabase.increment_credit_balance(user_id=supabase.SUPABASE_TEST_USER_ID) | ||||||
|     supabase.increment_credit_balance(user_id=SUPABASE_TEST_USER_ID) |  | ||||||
|     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) |     landmarks = load_trip_landmarks(client, result['first_landmark_uuid']) | ||||||
|  |  | ||||||
|     # Get computation time |     # Get computation time | ||||||
|   | |||||||
							
								
								
									
										1091
									
								
								report.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1091
									
								
								report.html
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user