Skip to content

Commit

Permalink
feat: fix connection
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisgera committed Nov 4, 2024
1 parent c3de4fe commit 3bd4978
Show file tree
Hide file tree
Showing 14 changed files with 391 additions and 170 deletions.
5 changes: 2 additions & 3 deletions .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
DATABASE_URL=postgresql://user:password@db:5432/inventory
ENVIRONMENT=development
SENTRY_DSN=your-sentry-dsn
DATABASE_URL=
ENVIRONMENT=
8 changes: 5 additions & 3 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# backend/Dockerfile
FROM python:3.11-slim

WORKDIR /app

# Install system dependencies
# Install system dependencies including curl
RUN apt-get update && apt-get install -y \
gcc \
postgresql-client \
curl \
&& rm -rf /var/lib/apt/lists/*

# Copy requirements file
Expand All @@ -23,5 +25,5 @@ ENV PYTHONPATH=/app
# Expose port
EXPOSE 8000

# Command to run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
# Default command uses development settings with reload
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
1 change: 1 addition & 0 deletions backend/app/database.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# backend/app/database.py
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
import os
Expand Down
234 changes: 132 additions & 102 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
from datetime import datetime
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException
# backend/app/main.py
from fastapi import FastAPI, Depends, HTTPException, APIRouter
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
from datetime import datetime
from requests import Request
from sqlalchemy.orm import Session
from sqlalchemy import text
import logging
import os
from . import schemas, crud
from .database import SessionLocal, init_db
from .database import SessionLocal
from typing import List, Type, TypeVar

# Configure logging
logging.basicConfig(
Expand All @@ -17,27 +20,31 @@
)
logger = logging.getLogger(__name__)

@asynccontextmanager
async def lifespan(app: FastAPI):
# Create type variables for generic type hints
SchemaType = TypeVar("SchemaType")

def serialize_sqlalchemy(db_model: any, schema_model: Type[SchemaType]) -> dict:
"""
Lifespan context manager for startup and shutdown events
Generic function to convert SQLAlchemy model to Pydantic model and then to dict
"""
try:
# Startup
logger.info("Starting up FastAPI application...")
init_db() # Initialize database tables
logger.info("Database initialized successfully")
yield
except Exception as e:
logger.error(f"Startup failed: {str(e)}")
raise
finally:
logger.info("Shutting down FastAPI application...")
# First convert to Pydantic model
pydantic_model = schema_model.model_validate(db_model)

# Then convert to dict with datetime handling
return jsonable_encoder(pydantic_model)

app = FastAPI(
title="Stackr API",
lifespan=lifespan
)
def create_json_response(content: any, status_code: int = 200) -> JSONResponse:
"""
Create a consistent JSON response with proper datetime handling
"""
return JSONResponse(
content=jsonable_encoder(content),
status_code=status_code,
media_type="application/json"
)

# Create an API router
api_router = APIRouter()

def get_db():
db = SessionLocal()
Expand All @@ -46,107 +53,130 @@ def get_db():
finally:
db.close()

# Configure CORS
origins = []
if os.getenv("ENVIRONMENT") == "development":
origins = [
"http://localhost",
"http://localhost:8501",
"http://127.0.0.1:8501",
]
else:
origins = [
"https://stackr-cold-violet-2349.fly.dev",
os.getenv("API_HOST", "https://stackr-cold-violet-2349.fly.dev"),
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Add error handler for all exceptions
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
logger.error(f"Global error handler caught: {str(exc)}")
return JSONResponse(
status_code=500,
content={"detail": str(exc)}
)

@app.get("/health")
async def health_check():
"""
Health check endpoint with database connection test
"""
try:
db = SessionLocal()
try:
# Test database connection using text()
result = db.execute(text("SELECT 1")).scalar()
db.commit()

return {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"database": "connected",
"check_value": result
}
finally:
db.close()
except Exception as e:
logger.error(f"Health check failed: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"Service unhealthy: {str(e)}"
)


# Update the items endpoint to always return JSON
@app.get("/items/")
@api_router.get("/items/", response_model=List[schemas.Item])
async def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
logger.info(f"Handling GET /items/ request with skip={skip}, limit={limit}")
try:
items = crud.get_items(db, skip=skip, limit=limit)
return JSONResponse(
content=[item.dict() for item in items],
status_code=200
)
result = [serialize_sqlalchemy(item, schemas.Item) for item in items]
logger.info(f"Successfully retrieved {len(items)} items")
logger.info(f"Returning items: {result}") # Add this for debugging
return create_json_response(result)
except Exception as e:
logger.error(f"Error fetching items: {str(e)}")
return JSONResponse(
content={"detail": str(e)},
return create_json_response(
{"detail": str(e)},
status_code=500
)

@app.post("/items/", response_model=schemas.Item)
@api_router.post("/items/", response_model=schemas.Item)
def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)):
try:
return crud.create_item(db=db, item=item)
except HTTPException:
raise
db_item = crud.create_item(db=db, item=item)
return create_json_response(
serialize_sqlalchemy(db_item, schemas.Item)
)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error creating item: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

@app.get("/inventory/{item_id}", response_model=list[schemas.InventoryRecord])
@api_router.get("/inventory/{item_id}", response_model=List[schemas.InventoryRecord])
def read_item_inventory(item_id: int, db: Session = Depends(get_db)):
try:
return crud.get_item_inventory_history(db, item_id=item_id)
except HTTPException:
raise
records = crud.get_item_inventory_history(db, item_id=item_id)
return create_json_response([
serialize_sqlalchemy(record, schemas.InventoryRecord)
for record in records
])
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error fetching inventory history: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

@app.post("/inventory/", response_model=schemas.InventoryRecord)
@api_router.post("/inventory/", response_model=schemas.InventoryRecord)
def update_inventory(record: schemas.InventoryRecordCreate, db: Session = Depends(get_db)):
try:
return crud.create_inventory_record(db=db, record=record)
except HTTPException:
raise
db_record = crud.create_inventory_record(db=db, record=record)
return create_json_response(
serialize_sqlalchemy(db_record, schemas.InventoryRecord)
)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error updating inventory: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
raise HTTPException(status_code=500, detail=str(e))

# Create the FastAPI application
app = FastAPI(
title="Stackr API",
docs_url="/docs",
openapi_url="/openapi.json"
)

# Add health check endpoints that don't use the /api prefix
@app.get("/health")
async def health_check():
try:
db = SessionLocal()
try:
logger.info("Running health check...")
result = db.execute(text("SELECT 1")).scalar()
db.commit()
response = {
"status": "healthy",
"timestamp": datetime.now().isoformat(),
"database": "connected",
"check_value": result
}
logger.info(f"Health check response: {response}")
return create_json_response(response)
finally:
db.close()
except Exception as e:
logger.error(f"Health check failed: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))

@app.middleware("http")
async def log_requests(request: Request, call_next):
logger.info("====== Request Start ======")
logger.info(f"Method: {request.method}")
logger.info(f"URL: {request.url}")
logger.info(f"Path: {request.url.path}")
logger.info(f"Headers: {dict(request.headers)}")
logger.info(f"Query Params: {dict(request.query_params)}")

response = await call_next(request)

logger.info(f"Response Status: {response.status_code}")
logger.info("====== Request End ======")
return response



# Configure CORS
origins = []
if os.getenv("ENVIRONMENT") == "development":
origins = [
"http://localhost",
"http://localhost:8501",
"http://127.0.0.1:8501",
]
else:
origins = [
"https://stackr-cold-violet-2349.fly.dev",
os.getenv("API_HOST", "https://stackr-cold-violet-2349.fly.dev"),
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Include the API router
app.include_router(api_router)
Loading

0 comments on commit 3bd4978

Please sign in to comment.