Files
fastapi-todo-api/app/main.py
2026-05-19 10:00:21 +02:00

126 lines
4.7 KiB
Python

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"}