129 lines
4.8 KiB
Python
129 lines
4.8 KiB
Python
# app/config.py
|
|
import os
|
|
from pydantic_settings import BaseSettings
|
|
from dotenv import load_dotenv
|
|
import logging
|
|
import secrets
|
|
|
|
load_dotenv()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class Settings(BaseSettings):
|
|
DATABASE_URL: str | None = None
|
|
GEMINI_API_KEY: str | None = None
|
|
|
|
# --- JWT Settings ---
|
|
SECRET_KEY: str # Must be set via environment variable
|
|
ALGORITHM: str = "HS256"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 # Default token lifetime: 30 minutes
|
|
REFRESH_TOKEN_EXPIRE_MINUTES: int = 10080 # Default refresh token lifetime: 7 days
|
|
|
|
# --- OCR Settings ---
|
|
MAX_FILE_SIZE_MB: int = 10 # Maximum allowed file size for OCR processing
|
|
ALLOWED_IMAGE_TYPES: list[str] = ["image/jpeg", "image/png", "image/webp"] # Supported image formats
|
|
OCR_ITEM_EXTRACTION_PROMPT: str = """
|
|
Extract the shopping list items from this image.
|
|
List each distinct item on a new line.
|
|
Ignore prices, quantities, store names, discounts, taxes, totals, and other non-item text.
|
|
Focus only on the names of the products or items to be purchased.
|
|
Add 2 underscores before and after the item name, if it is struck through.
|
|
If the image does not appear to be a shopping list or receipt, state that clearly.
|
|
Example output for a grocery list:
|
|
Milk
|
|
Eggs
|
|
Bread
|
|
__Apples__
|
|
Organic Bananas
|
|
"""
|
|
|
|
# --- Gemini AI Settings ---
|
|
GEMINI_MODEL_NAME: str = "gemini-2.0-flash" # The model to use for OCR
|
|
GEMINI_SAFETY_SETTINGS: dict = {
|
|
"HARM_CATEGORY_HATE_SPEECH": "BLOCK_MEDIUM_AND_ABOVE",
|
|
"HARM_CATEGORY_DANGEROUS_CONTENT": "BLOCK_MEDIUM_AND_ABOVE",
|
|
"HARM_CATEGORY_HARASSMENT": "BLOCK_MEDIUM_AND_ABOVE",
|
|
"HARM_CATEGORY_SEXUALLY_EXPLICIT": "BLOCK_MEDIUM_AND_ABOVE",
|
|
}
|
|
GEMINI_GENERATION_CONFIG: dict = {
|
|
"candidate_count": 1,
|
|
"max_output_tokens": 2048,
|
|
"temperature": 0.9,
|
|
"top_p": 1,
|
|
"top_k": 1
|
|
}
|
|
|
|
# --- API Settings ---
|
|
API_PREFIX: str = "/api" # Base path for all API endpoints
|
|
API_OPENAPI_URL: str = "/api/openapi.json"
|
|
API_DOCS_URL: str = "/api/docs"
|
|
API_REDOC_URL: str = "/api/redoc"
|
|
CORS_ORIGINS: list[str] = [
|
|
"http://localhost:5173",
|
|
"http://localhost:8000",
|
|
# Add your deployed frontend URL here later
|
|
# "https://your-frontend-domain.com",
|
|
]
|
|
|
|
# --- API Metadata ---
|
|
API_TITLE: str = "Shared Lists API"
|
|
API_DESCRIPTION: str = "API for managing shared shopping lists, OCR, and cost splitting."
|
|
API_VERSION: str = "0.1.0"
|
|
ROOT_MESSAGE: str = "Welcome to the Shared Lists API! Docs available at /api/docs"
|
|
|
|
# --- Logging Settings ---
|
|
LOG_LEVEL: str = "INFO"
|
|
LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
|
|
# --- Health Check Settings ---
|
|
HEALTH_STATUS_OK: str = "ok"
|
|
HEALTH_STATUS_ERROR: str = "error"
|
|
|
|
# --- Auth Settings ---
|
|
OAUTH2_TOKEN_URL: str = "/api/v1/auth/login" # Path to login endpoint
|
|
TOKEN_TYPE: str = "bearer" # Default token type for OAuth2
|
|
AUTH_HEADER_PREFIX: str = "Bearer" # Prefix for Authorization header
|
|
AUTH_HEADER_NAME: str = "WWW-Authenticate" # Name of auth header
|
|
AUTH_CREDENTIALS_ERROR: str = "Could not validate credentials"
|
|
AUTH_INVALID_CREDENTIALS: str = "Incorrect email or password"
|
|
AUTH_NOT_AUTHENTICATED: str = "Not authenticated"
|
|
|
|
# --- HTTP Status Messages ---
|
|
HTTP_400_DETAIL: str = "Bad Request"
|
|
HTTP_401_DETAIL: str = "Unauthorized"
|
|
HTTP_403_DETAIL: str = "Forbidden"
|
|
HTTP_404_DETAIL: str = "Not Found"
|
|
HTTP_422_DETAIL: str = "Unprocessable Entity"
|
|
HTTP_429_DETAIL: str = "Too Many Requests"
|
|
HTTP_500_DETAIL: str = "Internal Server Error"
|
|
HTTP_503_DETAIL: str = "Service Unavailable"
|
|
|
|
# --- Database Error Messages ---
|
|
DB_CONNECTION_ERROR: str = "Database connection error"
|
|
DB_INTEGRITY_ERROR: str = "Database integrity error"
|
|
DB_TRANSACTION_ERROR: str = "Database transaction error"
|
|
DB_QUERY_ERROR: str = "Database query error"
|
|
|
|
class Config:
|
|
env_file = ".env"
|
|
env_file_encoding = 'utf-8'
|
|
extra = "ignore"
|
|
|
|
settings = Settings()
|
|
|
|
# Validation for critical settings
|
|
if settings.DATABASE_URL is None:
|
|
raise ValueError("DATABASE_URL environment variable must be set.")
|
|
|
|
# Enforce secure secret key
|
|
if not settings.SECRET_KEY:
|
|
raise ValueError("SECRET_KEY environment variable must be set. Generate a secure key using: openssl rand -hex 32")
|
|
|
|
# Validate secret key strength
|
|
if len(settings.SECRET_KEY) < 32:
|
|
raise ValueError("SECRET_KEY must be at least 32 characters long for security")
|
|
|
|
if settings.GEMINI_API_KEY is None:
|
|
logger.error("CRITICAL: GEMINI_API_KEY environment variable not set. Gemini features will be unavailable.")
|
|
else:
|
|
# Optional: Log partial key for confirmation (avoid logging full key)
|
|
logger.info(f"GEMINI_API_KEY loaded (starts with: {settings.GEMINI_API_KEY[:4]}...).") |