Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
482 changes: 482 additions & 0 deletions README.md

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,56 @@
"""
Database configuration and session management.

This module handles:
- Database connection setup using SQLAlchemy
- Session management for database operations
- Base class for ORM models
"""

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
from dotenv import load_dotenv

# Load environment variables from .env file
# This includes DATABASE_URL with PostgreSQL connection details
load_dotenv()

# Get database URL from environment variables
# Format: postgresql://username:password@host:port/database_name
DATABASE_URL = os.getenv("DATABASE_URL")

# Create SQLAlchemy engine
# This manages the connection pool to the PostgreSQL database
engine = create_engine(DATABASE_URL)

# Create SessionLocal class for database sessions
# autocommit=False: Transactions must be explicitly committed
# autoflush=False: Changes are not automatically flushed to DB
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Base class for all ORM models
# All model classes (Student, Course, etc.) inherit from this
Base = declarative_base()

def get_db():
"""
Dependency function that provides database session to route handlers.

This function is used with FastAPI's Depends() to inject a database session
into route handlers. It ensures the session is properly closed after use.

Usage in routes:
def my_route(db: Session = Depends(get_db)):
# Use db here

Yields:
Session: SQLAlchemy database session
"""
db = SessionLocal()
try:
yield db
finally:
# Always close the session, even if an error occurs
db.close()
34 changes: 26 additions & 8 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
"""
Main application file for Course Enrollment API.

This file initializes the FastAPI application and sets up:
- Database connection and table creation
- API routers for different modules
- Health check endpoint
"""

from fastapi import FastAPI
from app.database import engine, Base
from app.routers import students, courses, enrollments, grades
from app.routers import students_router, courses_router, enrollments_router, grades_router
from app import models

# Create database tables
# Create all database tables on startup
# This reads the SQLAlchemy models and creates corresponding tables in PostgreSQL
Base.metadata.create_all(bind=engine)

# Initialize FastAPI application with metadata
app = FastAPI(
title="Course Enrollment API",
description="API for managing student course enrollments and grades",
version="1.0.0"
)

# Health check endpoint
# Health check endpoint - used to verify API is running
@app.get("/health")
def health_check():
"""
Health check endpoint to verify API status.
Returns a simple JSON response indicating the API is operational.
"""
return {"status": "healthy", "message": "Course Enrollment API is running"}

# Include routers
app.include_router(students.router, prefix="/students", tags=["Students"])
app.include_router(courses.router, prefix="/courses", tags=["Courses"])
app.include_router(enrollments.router, prefix="/enrollments", tags=["Enrollments"])
app.include_router(grades.router, prefix="/grades", tags=["Grades"])
# Include routers for different modules
# Each router handles a specific domain (students, courses, enrollments, grades)
# prefix: URL prefix for all routes in this router
# tags: Used for grouping endpoints in Swagger UI documentation
app.include_router(students_router.router, prefix="/students", tags=["Students"])
app.include_router(courses_router.router, prefix="/courses", tags=["Courses"])
app.include_router(enrollments_router.router, prefix="/enrollments", tags=["Enrollments"])
app.include_router(grades_router.router, prefix="/grades", tags=["Grades"])
73 changes: 70 additions & 3 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,111 @@
"""
SQLAlchemy ORM models for database tables.

This module defines the database schema using SQLAlchemy ORM:
- Student: Stores student information
- Course: Stores course details
- Enrollment: Links students to courses (many-to-many relationship)
- Grade: Stores grades for each enrollment

Relationships:
- One Student can have many Enrollments
- One Course can have many Enrollments
- One Enrollment belongs to one Student and one Course
- One Enrollment has one Grade
"""

from sqlalchemy import Column, Integer, String, Date, Float, ForeignKey
from sqlalchemy.orm import relationship
from app.database import Base

class Student(Base):
"""
Student model representing students in the system.

Attributes:
id: Primary key, auto-incremented
name: Student's full name
email: Student's email (must be unique)
enrollments: List of courses this student is enrolled in
"""
__tablename__ = "students"

id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
email = Column(String, unique=True, nullable=False)
email = Column(String, unique=True, nullable=False) # Unique constraint ensures no duplicate emails

# Relationship: One student can have many enrollments
enrollments = relationship("Enrollment", back_populates="student")

class Course(Base):
"""
Course model representing courses offered.

Attributes:
id: Primary key, auto-incremented
course_name: Full name of the course
course_code: Unique code identifying the course (e.g., CS101)
credits: Number of credits for this course
enrollments: List of students enrolled in this course
"""
__tablename__ = "courses"

id = Column(Integer, primary_key=True, index=True)
course_name = Column(String, nullable=False)
course_code = Column(String, unique=True, nullable=False)
course_code = Column(String, unique=True, nullable=False) # Unique constraint ensures no duplicate codes
credits = Column(Integer, nullable=False)

# Relationship: One course can have many enrollments
enrollments = relationship("Enrollment", back_populates="course")

class Enrollment(Base):
"""
Enrollment model linking students to courses.

This is the junction table for the many-to-many relationship between
students and courses. Each enrollment represents one student taking one course.

Attributes:
id: Primary key, auto-incremented
student_id: Foreign key to students table
course_id: Foreign key to courses table
enrollment_date: Date when student enrolled in the course
student: Reference to the Student object
course: Reference to the Course object
grade: Reference to the Grade object for this enrollment
"""
__tablename__ = "enrollments"

id = Column(Integer, primary_key=True, index=True)
student_id = Column(Integer, ForeignKey("students.id"), nullable=False)
course_id = Column(Integer, ForeignKey("courses.id"), nullable=False)
enrollment_date = Column(Date, nullable=False)

# Relationships: Links to Student and Course
student = relationship("Student", back_populates="enrollments")
course = relationship("Course", back_populates="enrollments")
# uselist=False means one-to-one relationship (one enrollment has one grade)
grade = relationship("Grade", back_populates="enrollment", uselist=False)

class Grade(Base):
"""
Grade model storing grades for enrollments.

Each enrollment can have one grade record containing marks and calculated letter grade.

Attributes:
id: Primary key, auto-incremented
enrollment_id: Foreign key to enrollments table
marks: Numerical marks (0-100)
final_grade: Calculated letter grade (A, B, C, D, F)
enrollment: Reference to the Enrollment object
"""
__tablename__ = "grades"

id = Column(Integer, primary_key=True, index=True)
enrollment_id = Column(Integer, ForeignKey("enrollments.id"), nullable=False)
marks = Column(Float, nullable=False)
final_grade = Column(String, nullable=True)
final_grade = Column(String, nullable=True) # Calculated automatically based on marks

# Relationship: Links back to Enrollment
enrollment = relationship("Enrollment", back_populates="grade")
2 changes: 0 additions & 2 deletions app/repositories/courses.py

This file was deleted.

111 changes: 111 additions & 0 deletions app/repositories/courses_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
Course Repository - Database access layer for Course operations.

This module handles all direct database interactions for courses.
Similar to students_repository, but for course-related queries.

Layer Architecture:
Router -> Service -> Repository -> Database
"""

from sqlalchemy.orm import Session
from app.models import Course
from app.schemas import CourseCreate

def create_course(db: Session, course: CourseCreate):
"""
Create a new course record in the database.

Args:
db: Database session from get_db()
course: CourseCreate schema with course details

Returns:
Course: Created course object with generated id
"""
db_course = Course(**course.model_dump())
db.add(db_course)
db.commit()
db.refresh(db_course)
return db_course

def get_all_courses(db: Session, skip: int = 0, limit: int = 100):
"""
Retrieve all courses with pagination.

Args:
db: Database session
skip: Number of records to skip
limit: Maximum number of records to return

Returns:
List[Course]: List of course objects
"""
return db.query(Course).offset(skip).limit(limit).all()

def get_course_by_id(db: Session, course_id: int):
"""
Find a course by its ID.

Args:
db: Database session
course_id: Course's primary key

Returns:
Course | None: Course object if found, None otherwise
"""
return db.query(Course).filter(Course.id == course_id).first()

def get_course_by_code(db: Session, course_code: str):
"""
Find a course by its unique course code.

Used for checking if course code already exists.

Args:
db: Database session
course_code: Unique course identifier (e.g., CS101)

Returns:
Course | None: Course object if found, None otherwise
"""
return db.query(Course).filter(Course.course_code == course_code).first()

def update_course(db: Session, course_id: int, course: CourseCreate):
"""
Update an existing course's information.

Args:
db: Database session
course_id: ID of course to update
course: CourseCreate schema with new data

Returns:
Course | None: Updated course object if found, None otherwise
"""
db_course = db.query(Course).filter(Course.id == course_id).first()
if db_course:
db_course.course_name = course.course_name
db_course.course_code = course.course_code
db_course.credits = course.credits
db.commit()
db.refresh(db_course)
return db_course

def delete_course(db: Session, course_id: int):
"""
Delete a course from the database (hard delete).

Args:
db: Database session
course_id: ID of course to delete

Returns:
bool: True if deleted successfully, False if not found
"""
db_course = db.query(Course).filter(Course.id == course_id).first()
if db_course:
db.delete(db_course)
db.commit()
return True
return False
2 changes: 0 additions & 2 deletions app/repositories/enrollments.py

This file was deleted.

Loading