Spaces:
Running
Running
fix: database connection string normalization and improved error logging for cloud deployment
Browse files- app/api/routes/users.py +19 -4
- app/core/settings.py +9 -0
- app/main.py +5 -2
app/api/routes/users.py
CHANGED
|
@@ -116,11 +116,21 @@ async def register_and_login(user: UserCreate, db: Session = Depends(get_db)):
|
|
| 116 |
)
|
| 117 |
return {"access_token": token, "token_type": "bearer", "user": {"id": str(new_user.id), "name": new_user.name, "email": new_user.email}}
|
| 118 |
except Exception as e:
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
# Fallback to Demo Mode if DB is down (Dev only)
|
| 123 |
-
if
|
|
|
|
| 124 |
mock_id = str(uuid.uuid4())
|
| 125 |
token = create_access_token(
|
| 126 |
data={"sub": mock_id, "demo": True},
|
|
@@ -132,7 +142,12 @@ async def register_and_login(user: UserCreate, db: Session = Depends(get_db)):
|
|
| 132 |
"user": {"id": mock_id, "name": user.name, "email": user.email},
|
| 133 |
"mode": "demo"
|
| 134 |
}
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
|
| 138 |
@router.get("/me", response_model=UserResponse)
|
|
|
|
| 116 |
)
|
| 117 |
return {"access_token": token, "token_type": "bearer", "user": {"id": str(new_user.id), "name": new_user.name, "email": new_user.email}}
|
| 118 |
except Exception as e:
|
| 119 |
+
logger.error(f"Registration error: {str(e)}")
|
| 120 |
+
|
| 121 |
+
# Capture conflict errors (user already exists)
|
| 122 |
+
if isinstance(e, ValueError) or "unique constraint" in str(e).lower() or "already exists" in str(e).lower():
|
| 123 |
+
raise HTTPException(
|
| 124 |
+
status_code=status.HTTP_409_CONFLICT,
|
| 125 |
+
detail="User with this email already exists."
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
# Determine if it's a database-related error
|
| 129 |
+
is_db_error = any(term in str(e).lower() for term in ["operationalerror", "connection", "refused", "psycopg2", "target machine actively refused"])
|
| 130 |
|
| 131 |
# Fallback to Demo Mode if DB is down (Dev only)
|
| 132 |
+
if is_db_error and settings.ENVIRONMENT == "development":
|
| 133 |
+
logger.warning("Database unavailable. Falling back to Demo Mode for registration.")
|
| 134 |
mock_id = str(uuid.uuid4())
|
| 135 |
token = create_access_token(
|
| 136 |
data={"sub": mock_id, "demo": True},
|
|
|
|
| 142 |
"user": {"id": mock_id, "name": user.name, "email": user.email},
|
| 143 |
"mode": "demo"
|
| 144 |
}
|
| 145 |
+
|
| 146 |
+
logger.exception("Unexpected error during registration")
|
| 147 |
+
raise HTTPException(
|
| 148 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 149 |
+
detail=f"Registration failed: {str(e)}"
|
| 150 |
+
)
|
| 151 |
|
| 152 |
|
| 153 |
@router.get("/me", response_model=UserResponse)
|
app/core/settings.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
Core Settings & Configuration
|
| 3 |
"""
|
|
|
|
| 4 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 5 |
from typing import List, Optional
|
| 6 |
|
|
@@ -19,6 +20,14 @@ class Settings(BaseSettings):
|
|
| 19 |
|
| 20 |
# Database — REQUIRED: must be set in .env
|
| 21 |
DATABASE_URL: str = "postgresql://user:onion123@localhost:5432/scoinvestigator"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
DB_ECHO: bool = False
|
| 23 |
|
| 24 |
# Security — REQUIRED: must be set in .env
|
|
|
|
| 1 |
"""
|
| 2 |
Core Settings & Configuration
|
| 3 |
"""
|
| 4 |
+
from pydantic import field_validator
|
| 5 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 6 |
from typing import List, Optional
|
| 7 |
|
|
|
|
| 20 |
|
| 21 |
# Database — REQUIRED: must be set in .env
|
| 22 |
DATABASE_URL: str = "postgresql://user:onion123@localhost:5432/scoinvestigator"
|
| 23 |
+
|
| 24 |
+
@field_validator("DATABASE_URL", mode="before")
|
| 25 |
+
@classmethod
|
| 26 |
+
def validate_database_url(cls, v: str) -> str:
|
| 27 |
+
if v and v.startswith("postgres://"):
|
| 28 |
+
return v.replace("postgres://", "postgresql://", 1)
|
| 29 |
+
return v
|
| 30 |
+
|
| 31 |
DB_ECHO: bool = False
|
| 32 |
|
| 33 |
# Security — REQUIRED: must be set in .env
|
app/main.py
CHANGED
|
@@ -36,8 +36,11 @@ async def lifespan(app: FastAPI):
|
|
| 36 |
Base.metadata.create_all(bind=engine)
|
| 37 |
logger.info("Database tables created / verified successfully")
|
| 38 |
except Exception as e:
|
| 39 |
-
logger.error(f"Could not connect to database: {e}")
|
| 40 |
-
logger.error("
|
|
|
|
|
|
|
|
|
|
| 41 |
logger.info("Application startup")
|
| 42 |
yield
|
| 43 |
logger.info("Application shutdown")
|
|
|
|
| 36 |
Base.metadata.create_all(bind=engine)
|
| 37 |
logger.info("Database tables created / verified successfully")
|
| 38 |
except Exception as e:
|
| 39 |
+
logger.error(f"CRITICAL: Could not connect to database or create tables: {str(e)}")
|
| 40 |
+
logger.error(f"Error type: {type(e).__name__}")
|
| 41 |
+
logger.error(f"Check your DATABASE_URL and ensure the database is reachable.")
|
| 42 |
+
if "ssl" in str(e).lower():
|
| 43 |
+
logger.error("SSL Error detected. Try adding ?sslmode=require to your DATABASE_URL.")
|
| 44 |
logger.info("Application startup")
|
| 45 |
yield
|
| 46 |
logger.info("Application shutdown")
|