diff --git a/app/config.py b/app/config.py deleted file mode 100644 index de13bdb..0000000 --- a/app/config.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -from pydantic import BaseSettings, Field - - -class Settings(BaseSettings): - db_url: str = Field(..., env='DATABASE_URL') - -settings = Settings() diff --git a/app/db.py b/app/db.py deleted file mode 100644 index 90e7d99..0000000 --- a/app/db.py +++ /dev/null @@ -1,26 +0,0 @@ -import databases -import ormar -import sqlalchemy - -from .config import settings - -database = databases.Database(settings.db_url) -metadata = sqlalchemy.MetaData() - - -class BaseMeta(ormar.ModelMeta): - metadata = metadata - database = database - - -class User(ormar.Model): - class Meta(BaseMeta): - tablename = "users" - - id: int = ormar.Integer(primary_key=True) - email: str = ormar.String(max_length=128, unique=True, nullable=False) - active: bool = ormar.Boolean(default=True, nullable=False) - - -engine = sqlalchemy.create_engine(settings.db_url) -metadata.create_all(engine) diff --git a/app/main.py b/app/main.py index 930e5ea..7b0cb6f 100644 --- a/app/main.py +++ b/app/main.py @@ -1,29 +1,182 @@ -from fastapi import FastAPI - -from app.db import database, User +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from pydantic import BaseModel, Field +from pydantic_settings import BaseSettings +from sqlalchemy import create_engine, Column, Integer, String, ForeignKey +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker, relationship from prometheus_fastapi_instrumentator import Instrumentator -app = FastAPI(title="FastAPI, Docker, and Traefik") -instrumentator = Instrumentator().instrument(app) +# Database configuration +import os + +class Settings(BaseSettings): + db_url: str = Field(..., env='DATABASE_URL') + +settings = Settings(db_url=os.environ['DATABASE_URL']) + +#from .config import settings +#database = databases.Database(settings.db_url) +#DATABASE_URL = "postgresql://postgres:postgres123@192.168.1.30/db_app001" + +engine = create_engine(settings.db_url) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() + +# Define User model +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, index=True) + email = Column(String, index=True) + course_id = Column(Integer, ForeignKey("courses.id_cours")) + + course = relationship("Course", back_populates="users") + +# Define Course model +class Course(Base): + __tablename__ = "courses" + + id_cours = Column(Integer, primary_key=True, index=True) + course_name = Column(String, index=True) + + users = relationship("User", back_populates="course") + +# Create tables in the database +Base.metadata.create_all(bind=engine) + +# FastAPI app setup +app = FastAPI() + +# Prometheus monitoring setup +Instrumentator().instrument(app).expose(app) + +# CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Pydantic models for request and response +class UserCreate(BaseModel): + name: str + email: str + +class CourseCreate(BaseModel): + course_name: str + +class UserResponse(UserCreate): + id: int + +class CourseResponse(CourseCreate): + id_cours: int + +# Endpoints +@app.post("/users/", response_model=UserResponse) +def create_user(user: UserCreate): + db = SessionLocal() + db_user = User(**user.dict()) + db.add(db_user) + db.commit() + db.refresh(db_user) + db.close() + return db_user + +@app.put("/users/{user_id}", response_model=UserResponse) +def update_user(user_id: int, user: UserCreate): + db = SessionLocal() + db_user = db.query(User).filter(User.id == user_id).first() + if db_user: + db_user.name = user.name + db_user.email = user.email + db.commit() + db.refresh(db_user) + db.close() + return db_user + +@app.get("/users/{user_id}", response_model=UserResponse) +def read_user(user_id: int): + db = SessionLocal() + db_user = db.query(User).filter(User.id == user_id).first() + db.close() + if db_user: + return db_user + else: + raise HTTPException(status_code=404, detail="User not found") + +@app.get("/users/", response_model=list[UserResponse]) +def read_users(skip: int = 0, limit: int = 10): + db = SessionLocal() + users = db.query(User).offset(skip).limit(limit).all() + db.close() + return users +@app.delete("/users/{user_id}", response_model=UserResponse) +def delete_user(user_id: int): + db = SessionLocal() + db_user = db.query(User).filter(User.id == user_id).first() + if db_user: + db.delete(db_user) + db.commit() + db.close() + return db_user + else: + db.close() + raise HTTPException(status_code=404, detail="User not found") -@app.get("/") -async def read_root(): - return await User.objects.all() +@app.post("/courses/", response_model=CourseResponse) +def create_course(course: CourseCreate): + db = SessionLocal() + db_course = Course(**course.dict()) + db.add(db_course) + db.commit() + db.refresh(db_course) + db.close() + return db_course +@app.put("/courses/{course_id}", response_model=CourseResponse) +def update_course(course_id: int, course: CourseCreate): + db = SessionLocal() + db_course = db.query(Course).filter(Course.id_cours == course_id).first() + if db_course: + db_course.course_name = course.course_name + db.commit() + db.refresh(db_course) + db.close() + return db_course -@app.on_event("startup") -async def startup(): - # Expose prometheus metrics using the Instrumentator - # https://github.com/trallnag/prometheus-fastapi-instrumentator - instrumentator.expose(app) - if not database.is_connected: - await database.connect() - # create a dummy entry - await User.objects.get_or_create(email="test@test.com") +@app.get("/courses/{course_id}", response_model=CourseResponse) +def read_course(course_id: int): + db = SessionLocal() + db_course = db.query(Course).filter(Course.id_cours == course_id).first() + db.close() + if db_course: + return db_course + else: + raise HTTPException(status_code=404, detail="Course not found") +@app.get("/courses/", response_model=list[CourseResponse]) +def read_courses(skip: int = 0, limit: int = 10): + db = SessionLocal() + courses = db.query(Course).offset(skip).limit(limit).all() + db.close() + return courses -@app.on_event("shutdown") -async def shutdown(): - if database.is_connected: - await database.disconnect() +@app.delete("/courses/{course_id}", response_model=CourseResponse) +def delete_course(course_id: int): + db = SessionLocal() + db_course = db.query(Course).filter(Course.id_cours == course_id).first() + if db_course: + db.delete(db_course) + db.commit() + db.close() + return db_course + else: + db.close() + raise HTTPException(status_code=404, detail="Course not found") diff --git a/app/requirements.txt b/app/requirements.txt index 01fb4ea..580586a 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -1,6 +1,36 @@ -asyncpg==0.29.0 -fastapi==0.106.0 -ormar==0.12.2 +annotated-types==0.6.0 +anyio==3.7.1 +certifi==2023.11.17 +click==8.1.7 +colorama==0.4.6 +dnspython==2.4.2 +email-validator==2.1.0.post1 +fastapi==0.105.0 +greenlet==3.0.3 +h11==0.14.0 +httpcore==1.0.2 +httptools==0.6.1 +httpx==0.26.0 +idna==3.6 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +orjson==3.9.10 +prometheus-client==0.19.0 +prometheus-fastapi-instrumentator==6.1.0 psycopg2-binary==2.9.9 +pydantic==2.5.3 +pydantic-extra-types==2.2.0 +pydantic-settings==2.1.0 +pydantic_core==2.14.6 +python-dotenv==1.0.0 +python-multipart==0.0.6 +PyYAML==6.0.1 +sniffio==1.3.0 +SQLAlchemy==2.0.23 +starlette==0.27.0 +typing_extensions==4.9.0 +ujson==5.9.0 uvicorn==0.25.0 -prometheus-fastapi-instrumentator==6.1.0 \ No newline at end of file +watchfiles==0.21.0 +websockets==12.0 diff --git a/helm/fastapi-app/Chart.yaml b/helm/fastapi-app/Chart.yaml index 85cda8d..c3061be 100644 --- a/helm/fastapi-app/Chart.yaml +++ b/helm/fastapi-app/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.6 +version: 0.1.7 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/terraform/deployments/preprod/releases/fastapi_app.tf b/terraform/deployments/preprod/releases/fastapi_app.tf index e6bcfd3..a54ae67 100644 --- a/terraform/deployments/preprod/releases/fastapi_app.tf +++ b/terraform/deployments/preprod/releases/fastapi_app.tf @@ -2,7 +2,7 @@ resource "helm_release" "fastapi_app" { name = "fastapi-app" chart = "${path.module}/../../../../helm/fastapi-app" create_namespace = true - version = "0.1.6" + version = "0.1.7" values = [ file("${path.module}/../../../../helm/fastapi-app/values-preprod.yaml") ] diff --git a/terraform/deployments/prod/releases/fastapi_app.tf b/terraform/deployments/prod/releases/fastapi_app.tf index 5776d77..5b7dc95 100644 --- a/terraform/deployments/prod/releases/fastapi_app.tf +++ b/terraform/deployments/prod/releases/fastapi_app.tf @@ -2,7 +2,7 @@ resource "helm_release" "fastapi_app" { name = "fastapi-app" chart = "${path.module}/../../../../helm/fastapi-app" create_namespace = true - version = "0.1.6" + version = "0.1.7" values = [ file("${path.module}/../../../../helm/fastapi-app/values-prod.yaml") ]