from fastapi import FastAPI, HTTPException, Depends from sqlalchemy import create_engine, Column, Integer, String, Boolean, text from sqlalchemy.orm import declarative_base, Session, sessionmaker from pydantic import BaseModel from typing import Optional import os import time import logging logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) # ── Database connection ─────────────────────────────────────────────────────── DB_HOST = os.getenv("DATABASE_HOST", "localhost") DB_PORT = os.getenv("DATABASE_PORT", "5432") DB_USER = os.getenv("POSTGRES_USER", "fastapiuser") DB_PASS = os.getenv("POSTGRES_PASSWORD", "student123") DB_NAME = os.getenv("POSTGRES_DB", "fastapidb") DATABASE_URL = f"postgresql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}" # Retry logic — baza mozda nije odmah dostupna kad Pod starta engine = None for attempt in range(10): try: engine = create_engine(DATABASE_URL) with engine.connect() as conn: conn.execute(text("SELECT 1")) log.info(f"Baza dostupna (pokusaj {attempt + 1})") break except Exception as e: log.warning(f"Baza nije dostupna, cekam 3s... (pokusaj {attempt + 1}): {e}") time.sleep(3) if engine is None: raise RuntimeError("Ne mogu se spojiti na bazu nakon 10 pokusaja") SessionLocal = sessionmaker(bind=engine) Base = declarative_base() # ── Model ───────────────────────────────────────────────────────────────────── class Todo(Base): __tablename__ = "todos" id = Column(Integer, primary_key=True, index=True) title = Column(String(200), nullable=False) done = Column(Boolean, default=False) Base.metadata.create_all(bind=engine) # ── Schemas ─────────────────────────────────────────────────────────────────── class TodoCreate(BaseModel): title: str done: Optional[bool] = False class TodoUpdate(BaseModel): title: Optional[str] = None done: Optional[bool] = None class TodoOut(BaseModel): id: int title: str done: bool model_config = {"from_attributes": True} # ── App ─────────────────────────────────────────────────────────────────────── app = FastAPI(title="FastAPI Todo API", version="1.0.0") def get_db(): db = SessionLocal() try: yield db finally: db.close() # ── Routes ──────────────────────────────────────────────────────────────────── @app.get("/health") def health(): try: with engine.connect() as conn: conn.execute(text("SELECT 1")) return {"status": "ok", "database": "connected"} except Exception as e: raise HTTPException(status_code=503, detail=f"Baza nedostupna: {e}") @app.get("/todos", response_model=list[TodoOut]) def get_todos(db: Session = Depends(get_db)): return db.query(Todo).all() @app.get("/todos/{todo_id}", response_model=TodoOut) def get_todo(todo_id: int, db: Session = Depends(get_db)): todo = db.query(Todo).filter(Todo.id == todo_id).first() if not todo: raise HTTPException(status_code=404, detail="Todo nije pronaden") return todo @app.post("/todos", response_model=TodoOut, status_code=201) def create_todo(payload: TodoCreate, db: Session = Depends(get_db)): todo = Todo(title=payload.title, done=payload.done) db.add(todo) db.commit() db.refresh(todo) return todo @app.put("/todos/{todo_id}", response_model=TodoOut) def update_todo(todo_id: int, payload: TodoUpdate, db: Session = Depends(get_db)): todo = db.query(Todo).filter(Todo.id == todo_id).first() if not todo: raise HTTPException(status_code=404, detail="Todo nije pronaden") if payload.title is not None: todo.title = payload.title if payload.done is not None: todo.done = payload.done db.commit() db.refresh(todo) return todo @app.delete("/todos/{todo_id}") def delete_todo(todo_id: int, db: Session = Depends(get_db)): todo = db.query(Todo).filter(Todo.id == todo_id).first() if not todo: raise HTTPException(status_code=404, detail="Todo nije pronaden") db.delete(todo) db.commit() return {"message": f"Todo {todo_id} obrisan"}