From ac4354716b6a8352a425ec9c9071abc46adecb94 Mon Sep 17 00:00:00 2001 From: Edwin Eames Date: Wed, 28 Jan 2026 21:54:58 -0500 Subject: [PATCH] major claude changes --- app/auth.py | 42 ++++++++++++++++++ app/models/auth.py | 20 +++++++++ app/routers/confirm.py | 5 ++- app/routers/delivery.py | 48 ++++++++++++-------- app/routers/fixstuff_auto.py | 5 +++ app/routers/fixstuff_customer.py | 7 +++ app/routers/main.py | 7 ++- app/script/fuel_estimator.py | 29 ++++++------ app/script/fuel_estimator_customer.py | 29 ++++++------ app/script/temp_getter.py | 11 +++-- config.py | 3 +- main.py | 64 ++++++++++++++++++++++++++- requirements.txt | 16 ++++--- settings_local.py | 42 +++++++++--------- settings_prod.py | 36 ++++++++------- 15 files changed, 269 insertions(+), 95 deletions(-) create mode 100644 app/auth.py create mode 100644 app/models/auth.py diff --git a/app/auth.py b/app/auth.py new file mode 100644 index 0000000..c2e821c --- /dev/null +++ b/app/auth.py @@ -0,0 +1,42 @@ +import re +import logging +from fastapi import Depends, HTTPException, status +from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from sqlalchemy.orm import Session +from database import get_db +from app.models.auth import Auth_User + +logger = logging.getLogger(__name__) + +security = HTTPBearer() + + +def get_current_user( + credentials: HTTPAuthorizationCredentials = Depends(security), + db: Session = Depends(get_db) +) -> Auth_User: + """ + Validates the Bearer token and returns the authenticated user. + Raises HTTPException 401 if token is invalid or user not found. + """ + token = credentials.credentials + + user = db.query(Auth_User).filter(Auth_User.api_key == token).first() + + if not user: + logger.warning("Authentication failed: invalid API key") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid authentication token", + headers={"WWW-Authenticate": "Bearer"}, + ) + + if user.active != 1: + logger.warning(f"Authentication failed: user {user.username} is inactive") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User account is inactive", + headers={"WWW-Authenticate": "Bearer"}, + ) + + return user diff --git a/app/models/auth.py b/app/models/auth.py new file mode 100644 index 0000000..3cbd840 --- /dev/null +++ b/app/models/auth.py @@ -0,0 +1,20 @@ +from sqlalchemy import Column, Integer, String, Text, TIMESTAMP +from database import Base + + +class Auth_User(Base): + __tablename__ = 'auth_users' + __table_args__ = {"schema": "public"} + + id = Column(Integer, primary_key=True, autoincrement=True) + uuid = Column(String(32)) + api_key = Column(Text) + username = Column(String(40)) + password_hash = Column(Text) + member_since = Column(TIMESTAMP) + email = Column(String(350)) + last_seen = Column(TIMESTAMP) + admin = Column(Integer) + admin_role = Column(Integer) + confirmed = Column(Integer) + active = Column(Integer, default=1) diff --git a/app/routers/confirm.py b/app/routers/confirm.py index f1398b2..b037ed1 100644 --- a/app/routers/confirm.py +++ b/app/routers/confirm.py @@ -1,5 +1,8 @@ +import logging from fastapi import APIRouter, Request, HTTPException from datetime import date + +logger = logging.getLogger(__name__) from database import session from decimal import Decimal @@ -52,7 +55,7 @@ async def update_auto(autoid: int, request: Request): return {"ok": True, "message": "Delivery confirmed and customer factor refined successfully."} except Exception as e: - print(str(e)) + logger.error(str(e)) session.rollback() raise HTTPException(status_code=500, detail=f"An internal error occurred: {str(e)}") diff --git a/app/routers/delivery.py b/app/routers/delivery.py index b384092..8a909b6 100644 --- a/app/routers/delivery.py +++ b/app/routers/delivery.py @@ -1,10 +1,15 @@ -from fastapi import APIRouter +import logging +from fastapi import APIRouter, Depends from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder from database import session from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery from app.models.delivery import Delivery +from app.models.auth import Auth_User +from app.auth import get_current_user + +logger = logging.getLogger(__name__) @@ -17,8 +22,8 @@ router = APIRouter( @router.get("/all/customers", status_code=200) -def get_delivery_customers(): - +def get_delivery_customers(current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/all/customers - User: {current_user.username}") automatics = ( session.query(Auto_Delivery) .filter(Auto_Delivery.auto_status.in_([1, 3])) @@ -27,10 +32,11 @@ def get_delivery_customers(): ) return JSONResponse(content=jsonable_encoder(automatics), status_code=200) - + @router.get("/driver/{driver_employee_id}", status_code=200) -def get_delivery_for_specific_driver(driver_employee_id: int): +def get_delivery_for_specific_driver(driver_employee_id: int, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/driver/{driver_employee_id} - User: {current_user.username}") automatics = ( session.query(Delivery) .filter(Delivery.driver_employee_id == driver_employee_id) @@ -40,9 +46,10 @@ def get_delivery_for_specific_driver(driver_employee_id: int): ) return JSONResponse(content=jsonable_encoder(automatics), status_code=200) - + @router.get("/delivery/{ticket_id}", status_code=200) -def get_delivery_by_openticket(ticket_id): +def get_delivery_by_openticket(ticket_id, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/delivery/{ticket_id} - User: {current_user.username}") get_delivery = ( session.query(Auto_Delivery) .filter(Auto_Delivery.id == ticket_id) @@ -50,10 +57,11 @@ def get_delivery_by_openticket(ticket_id): ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) - + @router.get("/finddelivery/{ticket_id}", status_code=200) -def get_delivery_by_openticket(ticket_id): +def get_delivery_by_findticket(ticket_id, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/finddelivery/{ticket_id} - User: {current_user.username}") get_delivery = ( session.query(Auto_Delivery) .filter(Auto_Delivery.open_ticket_id == ticket_id) @@ -61,11 +69,12 @@ def get_delivery_by_openticket(ticket_id): ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) - + @router.get("/autoticket/{delivery_id_order}", status_code=200) -def get_auto_by_ticket(delivery_id_order): +def get_auto_by_ticket(delivery_id_order, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/autoticket/{delivery_id_order} - User: {current_user.username}") get_delivery = ( session.query(Tickets_Auto_Delivery) .filter(Tickets_Auto_Delivery.id == delivery_id_order) @@ -73,11 +82,11 @@ def get_auto_by_ticket(delivery_id_order): ) return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) - + @router.get("/all/profile/{customer_id}", status_code=200) -def get_autos_customers(customer_id): - +def get_autos_customers(customer_id, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/all/profile/{customer_id} - User: {current_user.username}") get_delivery = ( session.query(Tickets_Auto_Delivery) .filter(Tickets_Auto_Delivery.customer_id == customer_id) @@ -89,8 +98,8 @@ def get_autos_customers(customer_id): return JSONResponse(content=jsonable_encoder(get_delivery), status_code=200) @router.get("/all/profile/profile/{customer_id}", status_code=200) -def get_autos_customers(customer_id): - +def get_autos_customers_extended(customer_id, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/all/profile/profile/{customer_id} - User: {current_user.username}") get_delivery = ( session.query(Tickets_Auto_Delivery) .filter(Tickets_Auto_Delivery.customer_id == customer_id) @@ -103,8 +112,8 @@ def get_autos_customers(customer_id): @router.get("/auto/customer/{customer_id}", status_code=200) -def get_auto_delivery_by_customer(customer_id): - +def get_auto_delivery_by_customer(customer_id, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"GET /delivery/auto/customer/{customer_id} - User: {current_user.username}") get_auto_delivery = ( session.query(Auto_Delivery) .filter(Auto_Delivery.customer_id == customer_id) @@ -115,7 +124,8 @@ def get_auto_delivery_by_customer(customer_id): @router.put("/update_status/{auto_id}", status_code=200) -def update_auto_status(auto_id: int): +def update_auto_status(auto_id: int, current_user: Auth_User = Depends(get_current_user)): + logger.info(f"PUT /delivery/update_status/{auto_id} - User: {current_user.username}") update_status = ( session.query(Auto_Delivery) .filter(Auto_Delivery.id == auto_id) diff --git a/app/routers/fixstuff_auto.py b/app/routers/fixstuff_auto.py index ffb9c62..6035bed 100644 --- a/app/routers/fixstuff_auto.py +++ b/app/routers/fixstuff_auto.py @@ -1,3 +1,4 @@ +import logging from fastapi import APIRouter from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder @@ -9,6 +10,8 @@ from decimal import Decimal from app.models.auto import Auto_Delivery, Tickets_Auto_Delivery, Auto_Temp from app.models.delivery import Delivery +logger = logging.getLogger(__name__) + # Constants from fuel_estimator HOT_WATER_DAILY_USAGE = Decimal('1.0') K_FACTOR_SMOOTHING_WEIGHT = Decimal('0.7') @@ -33,6 +36,7 @@ def fix_customer_last_delivered(): Returns statistics and a list of changes made. """ + logger.info("GET /fixstuff/lastdelivered - Fixing customer last delivered dates") auto_deliveries = session.query(Auto_Delivery).all() changes = [] total_customers = len(auto_deliveries) @@ -76,6 +80,7 @@ def estimate_customer_gallons(update_db: int): Multiple deliveries: use historical average. Includes address and scaling factor. When update_db=1, updates estimated_gallons_left and house_factor in database. """ + logger.info(f"GET /fixstuff/estimate_gallons/{update_db} - Estimating customer gallons (update_db={update_db})") auto_deliveries = session.query(Auto_Delivery).all() estimates = [] for ad in auto_deliveries: diff --git a/app/routers/fixstuff_customer.py b/app/routers/fixstuff_customer.py index 4355c2d..8302e4f 100644 --- a/app/routers/fixstuff_customer.py +++ b/app/routers/fixstuff_customer.py @@ -1,3 +1,4 @@ +import logging from fastapi import APIRouter from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder @@ -10,6 +11,8 @@ from app.models.customer import Customer_Customer, Customer_estimate_gallons from app.models.delivery import Delivery from app.models.auto import Auto_Temp +logger = logging.getLogger(__name__) + # Constants from fuel_estimator HOT_WATER_DAILY_USAGE = Decimal('1.0') K_FACTOR_SMOOTHING_WEIGHT = Decimal('0.7') @@ -42,6 +45,7 @@ def fix_customer_last_delivered(): Returns statistics and a list of changes made. """ + logger.info("GET /fixstuff_customer/lastdelivered - Fixing customer last delivered dates") session.rollback() # Reset any aborted transaction state customer_estimates = session.query(Customer_estimate_gallons).all() changes = [] @@ -87,6 +91,7 @@ def estimate_customer_gallons(update_db: int): Multiple deliveries: use historical average. Includes address and scaling factor. When update_db=1, updates estimated_gallons_left and house_factor in database. """ + logger.info(f"GET /fixstuff_customer/estimate_gallons/{update_db} - Estimating customer gallons (update_db={update_db})") session.rollback() # Reset any aborted transaction state # Check if weather data is available @@ -235,6 +240,7 @@ def estimate_customer_gallons_specific(customer_id: int): Estimates current gallons for a specific regular customer based on delivery history and weather. Returns estimation data for the specified customer only. """ + logger.info(f"GET /fixstuff_customer/estimate_gallons/customer/{customer_id} - Estimating gallons for specific customer") session.rollback() # Reset any aborted transaction state # Check if weather data is available @@ -386,6 +392,7 @@ def populate_customer_estimates(): Returns statistics on records created. """ + logger.info("GET /fixstuff_customer/populate_estimates - Populating customer estimates") session.rollback() # Reset any aborted transaction state # Get all regular customers (customer_automatic == 0) diff --git a/app/routers/main.py b/app/routers/main.py index 0dc5581..e427836 100644 --- a/app/routers/main.py +++ b/app/routers/main.py @@ -1,6 +1,9 @@ +import logging from fastapi import APIRouter, HTTPException from database import session +logger = logging.getLogger(__name__) + from app.script.fuel_estimator import FuelEstimator from app.script.temp_getter import fetch_and_store_daily_temp from app.script.fuel_estimator_customer import FuelEstimatorCustomer @@ -45,7 +48,7 @@ def update_all_customer_fuel_levels_auto(): except Exception as e: session.rollback() # Log the exception e - print(str(e)) + logger.error(str(e)) return {"ok": False, "message": "An internal error occurred."} @@ -63,5 +66,5 @@ def update_all_customer_fuel_levels_normal(): except Exception as e: session.rollback() # Log the exception e - print(str(e)) + logger.error(str(e)) return {"ok": False, "message": "An internal error occurred."} diff --git a/app/script/fuel_estimator.py b/app/script/fuel_estimator.py index 84df9b7..90dea37 100644 --- a/app/script/fuel_estimator.py +++ b/app/script/fuel_estimator.py @@ -1,8 +1,11 @@ +import logging from sqlalchemy.orm import Session from sqlalchemy import func from datetime import date, timedelta from decimal import Decimal +logger = logging.getLogger(__name__) + # Import your existing database models from app.models.auto import Auto_Delivery, Auto_Temp, Auto_Update, Tickets_Auto_Delivery @@ -58,7 +61,7 @@ class FuelEstimator: if delivery_count <= 1: # Customers with 0 or 1 delivery should have house_factor = 0.12 (initial average) if customer.house_factor != Decimal('0.12'): - print(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)") + logger.info(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)") customer.house_factor = Decimal('0.12') corrected = True # For customers with 2+ deliveries, keep their calculated factor (no correction needed) @@ -74,13 +77,13 @@ class FuelEstimator: # 1. Check if the update has already run today if self.session.query(Auto_Update).filter(Auto_Update.last_updated == today).first(): - print(f"Daily update for {today} has already been completed.") + logger.info(f"Daily update for {today} has already been completed.") return {"ok": True, "message": "Update already run today."} # 2. Get today's weather data (specifically the Heating Degree Days) todays_weather = self._get_weather_for_date(today) if not todays_weather: - print(f"Error: Weather data for {today} not found. Cannot run update.") + logger.info(f"Error: Weather data for {today} not found. Cannot run update.") return {"ok": False, "message": f"Weather data for {today} not found."} # Degree days can't be negative for this calculation. If it's warm, HDD = 0. @@ -92,10 +95,10 @@ class FuelEstimator: ).all() if not auto_customers: - print("No active automatic delivery customers found.") + logger.info("No active automatic delivery customers found.") return {"ok": True, "message": "No active customers to update."} - print(f"Staging daily fuel update for {len(auto_customers)} customers...") + logger.info(f"Staging daily fuel update for {len(auto_customers)} customers...") corrections_made = 0 @@ -124,7 +127,7 @@ class FuelEstimator: new_update_log = Auto_Update(last_updated=today) self.session.add(new_update_log) - print("Daily update staged. Awaiting commit.") + logger.info("Daily update staged. Awaiting commit.") message = f"Successfully staged updates for {len(auto_customers)} customers." if corrections_made > 0: message += f" Corrected house factors for {corrections_made} customers." @@ -141,11 +144,11 @@ class FuelEstimator: ).first() if not customer: - print(f"Customer {ticket.customer_id} not found.") + logger.info(f"Customer {ticket.customer_id} not found.") return if not customer.last_fill: - print(f"Setting initial K-Factor for new customer {ticket.customer_id} with only one delivery.") + logger.info(f"Setting initial K-Factor for new customer {ticket.customer_id} with only one delivery.") customer.house_factor = self._estimate_initial_house_factor(customer) self._update_tank_after_fill(customer, ticket) return @@ -154,7 +157,7 @@ class FuelEstimator: end_date = ticket.fill_date if start_date >= end_date: - print(f"Cannot refine K-Factor for customer {ticket.customer_id}: New fill date is not after the last one. Resetting tank only.") + logger.info(f"Cannot refine K-Factor for customer {ticket.customer_id}: New fill date is not after the last one. Resetting tank only.") self._update_tank_after_fill(customer, ticket) return @@ -172,7 +175,7 @@ class FuelEstimator: gallons_for_heating = ticket.gallons_delivered - total_hot_water_usage if gallons_for_heating <= 0 or total_hdd == 0: - print(f"Cannot calculate new K-Factor for customer {ticket.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.") + logger.info(f"Cannot calculate new K-Factor for customer {ticket.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.") self._update_tank_after_fill(customer, ticket) return @@ -181,13 +184,13 @@ class FuelEstimator: current_k_factor = customer.house_factor smoothed_k_factor = (current_k_factor * K_FACTOR_SMOOTHING_WEIGHT) + (new_k_factor * (Decimal('1.0') - K_FACTOR_SMOOTHING_WEIGHT)) - print(f"Refining K-Factor for Customer ID {customer.customer_id}:") - print(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}") + logger.info(f"Refining K-Factor for Customer ID {customer.customer_id}:") + logger.info(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}") customer.house_factor = smoothed_k_factor self._update_tank_after_fill(customer, ticket) - print(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.") + logger.info(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.") def _update_tank_after_fill(self, customer: Auto_Delivery, ticket: Tickets_Auto_Delivery): """Helper to update customer tank status after a fill-up or partial delivery.""" diff --git a/app/script/fuel_estimator_customer.py b/app/script/fuel_estimator_customer.py index e904219..430c426 100644 --- a/app/script/fuel_estimator_customer.py +++ b/app/script/fuel_estimator_customer.py @@ -1,8 +1,11 @@ +import logging from sqlalchemy.orm import Session from sqlalchemy import func from datetime import date, timedelta from decimal import Decimal +logger = logging.getLogger(__name__) + # Import your existing database models from app.models.customer import Customer_estimate_gallons, Customer_Update from app.models.delivery import Delivery @@ -60,7 +63,7 @@ class FuelEstimatorCustomer: if delivery_count <= 1: # Customers with 0 or 1 delivery should have house_factor = 0.12 (initial average) if customer.house_factor != Decimal('0.12'): - print(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)") + logger.info(f"Correcting house_factor for customer {customer.customer_id} from {customer.house_factor} to 0.12 (1 or fewer deliveries)") customer.house_factor = Decimal('0.12') corrected = True # For customers with 2+ deliveries, keep their calculated factor (no correction needed) @@ -76,13 +79,13 @@ class FuelEstimatorCustomer: # 1. Check if the update has already run today if self.session.query(Customer_Update).filter(Customer_Update.last_updated == today).first(): - print(f"Daily update for {today} has already been completed.") + logger.info(f"Daily update for {today} has already been completed.") return {"ok": True, "message": "Update already run today."} # 2. Get today's weather data (specifically the Heating Degree Days) todays_weather = self._get_weather_for_date(today) if not todays_weather: - print(f"Error: Weather data for {today} not found. Cannot run update.") + logger.info(f"Error: Weather data for {today} not found. Cannot run update.") return {"ok": False, "message": f"Weather data for {today} not found."} # Degree days can't be negative for this calculation. If it's warm, HDD = 0. @@ -94,10 +97,10 @@ class FuelEstimatorCustomer: ).all() if not customer_estimates: - print("No active regular delivery customers found.") + logger.info("No active regular delivery customers found.") return {"ok": True, "message": "No active customers to update."} - print(f"Staging daily fuel update for {len(customer_estimates)} customers...") + logger.info(f"Staging daily fuel update for {len(customer_estimates)} customers...") corrections_made = 0 @@ -126,7 +129,7 @@ class FuelEstimatorCustomer: new_update_log = Customer_Update(last_updated=today) self.session.add(new_update_log) - print("Daily update staged. Awaiting commit.") + logger.info("Daily update staged. Awaiting commit.") message = f"Successfully staged updates for {len(customer_estimates)} customers." if corrections_made > 0: message += f" Corrected house factors for {corrections_made} customers." @@ -143,11 +146,11 @@ class FuelEstimatorCustomer: ).first() if not customer: - print(f"Customer {delivery.customer_id} not found.") + logger.info(f"Customer {delivery.customer_id} not found.") return if not customer.last_fill: - print(f"Setting initial K-Factor for new customer {delivery.customer_id} with only one delivery.") + logger.info(f"Setting initial K-Factor for new customer {delivery.customer_id} with only one delivery.") customer.house_factor = self._estimate_initial_house_factor(customer) self._update_tank_after_fill(customer, delivery) return @@ -156,7 +159,7 @@ class FuelEstimatorCustomer: end_date = delivery.when_delivered if start_date >= end_date: - print(f"Cannot refine K-Factor for customer {delivery.customer_id}: New fill date is not after the last one. Resetting tank only.") + logger.info(f"Cannot refine K-Factor for customer {delivery.customer_id}: New fill date is not after the last one. Resetting tank only.") self._update_tank_after_fill(customer, delivery) return @@ -174,7 +177,7 @@ class FuelEstimatorCustomer: gallons_for_heating = delivery.gallons_delivered - total_hot_water_usage if gallons_for_heating <= 0 or total_hdd == 0: - print(f"Cannot calculate new K-Factor for customer {delivery.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.") + logger.info(f"Cannot calculate new K-Factor for customer {delivery.customer_id}. (HDD: {total_hdd}, Heating Gallons: {gallons_for_heating}). Resetting tank only.") self._update_tank_after_fill(customer, delivery) return @@ -183,13 +186,13 @@ class FuelEstimatorCustomer: current_k_factor = customer.house_factor smoothed_k_factor = (current_k_factor * K_FACTOR_SMOOTHING_WEIGHT) + (new_k_factor * (Decimal('1.0') - K_FACTOR_SMOOTHING_WEIGHT)) - print(f"Refining K-Factor for Customer ID {customer.customer_id}:") - print(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}") + logger.info(f"Refining K-Factor for Customer ID {customer.customer_id}:") + logger.info(f" - Old K-Factor: {current_k_factor:.4f}, New Smoothed K-Factor: {smoothed_k_factor:.4f}") customer.house_factor = smoothed_k_factor self._update_tank_after_fill(customer, delivery) - print(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.") + logger.info(f"K-Factor and tank status for Customer {customer.customer_id} staged for update.") def _update_tank_after_fill(self, customer: Customer_estimate_gallons, delivery: Delivery): """Helper to update customer tank status after a fill-up or partial delivery.""" diff --git a/app/script/temp_getter.py b/app/script/temp_getter.py index b0765f7..d503148 100644 --- a/app/script/temp_getter.py +++ b/app/script/temp_getter.py @@ -1,7 +1,10 @@ +import logging from datetime import date import requests from decimal import Decimal +logger = logging.getLogger(__name__) + # Import your database model and the session from your database configuration from app.models.auto import Auto_Temp from database import session @@ -22,11 +25,11 @@ def fetch_and_store_daily_temp() -> bool: # 1. Check if the temperature for today already exists in the database today = date.today() if session.query(Auto_Temp).filter(Auto_Temp.todays_date == today).first(): - print(f"Temperature for {today} already exists in the database. Skipping fetch.") + logger.info(f"Temperature for {today} already exists in the database. Skipping fetch.") return True # 2. If it doesn't exist, fetch it from the API - print(f"Fetching temperature for {today} from OpenWeatherMap...") + logger.info(f"Fetching temperature for {today} from OpenWeatherMap...") try: # API key and location api_key = '21648d8c8d1a4ae495ace0b7810b4d36' @@ -63,11 +66,11 @@ def fetch_and_store_daily_temp() -> bool: # 4. Add the new record to the session (it will be committed by the calling function) session.add(add_new_temp) - print(f"Successfully fetched and staged temperature for {today}.") + logger.info(f"Successfully fetched and staged temperature for {today}.") return True except Exception as e: - print(f"An error occurred while fetching weather data: {e}") + logger.info(f"An error occurred while fetching weather data: {e}") # Make sure to rollback the session in case of a partial failure session.rollback() return False diff --git a/config.py b/config.py index 2b79c46..ca77eb0 100644 --- a/config.py +++ b/config.py @@ -3,7 +3,6 @@ import os def load_config(mode=os.environ.get('MODE')): try: - print(f"mode is {mode}") if mode == 'PRODUCTION': from settings_prod import ApplicationConfig return ApplicationConfig @@ -20,4 +19,4 @@ def load_config(mode=os.environ.get('MODE')): except ImportError: from settings_local import ApplicationConfig - return ApplicationConfig \ No newline at end of file + return ApplicationConfig diff --git a/main.py b/main.py index d74c06b..6a580b0 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,57 @@ +import logging +import sys from app.routers import fixstuff_auto from fastapi import FastAPI from app.routers import main, delivery, confirm, fixstuff_customer from fastapi.middleware.cors import CORSMiddleware -import os +import os from config import load_config +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker ApplicationConfig = load_config() +# Configure logging +def setup_logging(): + """Configure structured logging for the application.""" + log_level = logging.DEBUG if ApplicationConfig.CURRENT_SETTINGS != 'PRODUCTION' else logging.INFO + + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + root_logger = logging.getLogger() + root_logger.setLevel(log_level) + root_logger.handlers.clear() + + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(log_level) + console_handler.setFormatter(formatter) + root_logger.addHandler(console_handler) + + logging.getLogger('uvicorn.access').setLevel(logging.WARNING) + + return logging.getLogger('eamco_auto_api') + +logger = setup_logging() + +# Database setup +engine = create_engine(ApplicationConfig.SQLALCHEMY_DATABASE_URI) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +def check_db_connection(): + """ + Test database connectivity. + """ + try: + db = SessionLocal() + db.execute(text("SELECT 1")) + db.close() + return True + except Exception: + return False app = FastAPI() @@ -33,3 +77,21 @@ app.add_middleware( @app.get("/") def read_root(): return {"Status": "Auto Service is online"} + +@app.on_event("startup") +async def startup_event(): + """Application startup - log configuration and test DB connection.""" + logger.info("🚀 eamco_auto_api STARTING") + mode = ApplicationConfig.CURRENT_SETTINGS.upper() + if mode in ['DEVELOPMENT', 'DEV']: + logger.info("🤖🤖🤖🤖🤖 Mode: Development 🤖🤖🤖🤖🤖") + elif mode in ['PRODUCTION', 'PROD']: + logger.info("💀💀💀💀💀💀💀💀💀💀 ⚠️ WARNING PRODUCTION 💀💀💀💀💀💀💀💀💀💀") + logger.info(f"DB: {ApplicationConfig.SQLALCHEMY_DATABASE_URI[:30]}...") + logger.info(f"CORS: {len(ApplicationConfig.origins)} origins configured") + + # Test database connection + if check_db_connection(): + logger.info("DB Connection: ✅ OK") + else: + logger.info("DB Connection: ❌ FAILED") diff --git a/requirements.txt b/requirements.txt index 4f04b95..58fc2b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,11 @@ -fastapi -uvicorn[standard] -psycopg2-binary -sqlalchemy -requests +# eamco_auto_api dependencies +# FastAPI web framework and server +fastapi==0.115.6 +uvicorn[standard]==0.34.0 + +# Database +SQLAlchemy==2.0.40 +psycopg2-binary==2.9.10 + +# HTTP client +requests==2.32.3 diff --git a/settings_local.py b/settings_local.py index 6adacf0..1439075 100644 --- a/settings_local.py +++ b/settings_local.py @@ -1,29 +1,31 @@ +import os class ApplicationConfig: """ - Basic Configuration for a generic User + Local Configuration (LAN deployment) """ CURRENT_SETTINGS = 'LOCAL' - # databases info - POSTGRES_USERNAME = 'postgres' - POSTGRES_PW = 'password' - POSTGRES_SERVER = '192.168.1.204' - POSTGRES_PORT = '5432' - POSTGRES_DBNAME00 = 'auburnoil' - SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME, - POSTGRES_PW, - POSTGRES_SERVER, - POSTGRES_DBNAME00 - ) - + + # Database credentials from environment variables + POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME', 'postgres') + POSTGRES_PW = os.environ.get('POSTGRES_PW') + POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER', '192.168.1.204') + POSTGRES_PORT = os.environ.get('POSTGRES_PORT', '5432') + POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME', 'auburnoil') + + SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format( + POSTGRES_USERNAME, + POSTGRES_PW, + POSTGRES_SERVER, + POSTGRES_DBNAME00 + ) SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI} - origins = [ - "http://192.168.1.204:9000", - "http://192.168.1.204:9613", - "http://192.168.1.204:9614", - "http://192.168.1.204:9612", - "http://192.168.1.204:9611", -] \ No newline at end of file + "http://192.168.1.204:9000", + "http://192.168.1.204:9613", + "http://192.168.1.204:9614", + "http://192.168.1.204:9612", + "http://192.168.1.204:9611", + ] diff --git a/settings_prod.py b/settings_prod.py index 42e1f8e..ace6192 100644 --- a/settings_prod.py +++ b/settings_prod.py @@ -1,22 +1,28 @@ +import os + + class ApplicationConfig: """ - Basic Configuration for a generic User + Production Configuration """ CURRENT_SETTINGS = 'PRODUCTION' - # databases info - POSTGRES_USERNAME = 'postgres' - POSTGRES_PW = 'password' - POSTGRES_SERVER = '192.168.1.204' - POSTGRES_PORT = '5432' - POSTGRES_DBNAME00 = 'auburnoil' - SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME, - POSTGRES_PW, - POSTGRES_SERVER, - POSTGRES_DBNAME00 - ) + + # Database credentials from environment variables + POSTGRES_USERNAME = os.environ.get('POSTGRES_USERNAME', 'postgres') + POSTGRES_PW = os.environ.get('POSTGRES_PW') + POSTGRES_SERVER = os.environ.get('POSTGRES_SERVER', '192.168.1.204') + POSTGRES_PORT = os.environ.get('POSTGRES_PORT', '5432') + POSTGRES_DBNAME00 = os.environ.get('POSTGRES_DBNAME', 'auburnoil') + + SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format( + POSTGRES_USERNAME, + POSTGRES_PW, + POSTGRES_SERVER, + POSTGRES_DBNAME00 + ) SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI} origins = [ - "https://oil.edwineames.com", - "https://apiauto.edwineames.com", -] \ No newline at end of file + "https://oil.edwineames.com", + "https://apiauto.edwineames.com", + ]