diff --git a/README.md b/README.md new file mode 100644 index 0000000..5323865 --- /dev/null +++ b/README.md @@ -0,0 +1,100 @@ +# Delphi Database + +A legal case management database application built with FastAPI, SQLAlchemy, and modern Python practices. + +## Database Configuration + +The application uses SQLAlchemy ORM with support for multiple database backends. + +### Environment Variables + +Configure your database connection using environment variables: + +```bash +# SQLite (default for development) +DATABASE_URL=sqlite:///./delphi.db + +# PostgreSQL (production example) +DATABASE_URL=postgresql://username:password@localhost:5432/delphi_db + +# MySQL (alternative) +DATABASE_URL=mysql://username:password@localhost:3306/delphi_db +``` + +### Database Models + +The application includes the following models: + +- **User**: Authentication and user management +- **Client**: Client/contact information +- **Phone**: Phone numbers linked to clients +- **Case**: Legal cases with unique file numbers +- **Transaction**: Financial transactions for cases +- **Document**: Case-related documents +- **Payment**: Payment records for cases + +### Database Connection Management + +The `app/database.py` module provides: + +- **Database engine configuration** with connection pooling and error handling +- **Session management** with automatic cleanup via FastAPI dependency injection +- **Table creation utilities** for application startup +- **Connection health monitoring** via the `/health` endpoint + +### Usage Examples + +#### Basic Application Startup + +```python +from fastapi import FastAPI, Depends +from sqlalchemy.orm import Session +from app.database import get_db + +@app.get("/clients/") +async def get_clients(db: Session = Depends(get_db)): + # Use the database session + return db.query(Client).all() +``` + +#### Health Check + +The `/health` endpoint verifies database connectivity: + +```bash +curl http://localhost:8000/health +# {"status": "healthy", "database": "connected", "users": 0} +``` + +## Getting Started + +1. Install dependencies: + ```bash + pip install -r requirements.txt + ``` + +2. Configure your database in environment variables (see `.env` file) + +3. Run the application: + ```bash + uvicorn app.main:app --reload + ``` + +4. Access the API at `http://localhost:8000` + +## Project Structure + +``` +app/ +├── main.py # FastAPI application entry point +├── database.py # Database configuration and session management +├── models.py # SQLAlchemy models +└── templates/ # HTML templates +``` + +## Development + +- Database tables are automatically created on application startup +- Use the health check endpoint to verify database connectivity +- Environment variables control database configuration (never hardcode credentials) +- SQLAlchemy provides type-safe database operations with relationship management diff --git a/app/__pycache__/database.cpython-313.pyc b/app/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000..cfc457d Binary files /dev/null and b/app/__pycache__/database.cpython-313.pyc differ diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..211c740 Binary files /dev/null and b/app/__pycache__/main.cpython-313.pyc differ diff --git a/app/__pycache__/models.cpython-313.pyc b/app/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..a089a71 Binary files /dev/null and b/app/__pycache__/models.cpython-313.pyc differ diff --git a/app/database.py b/app/database.py new file mode 100644 index 0000000..4b35001 --- /dev/null +++ b/app/database.py @@ -0,0 +1,91 @@ +""" +Database configuration and connection management for Delphi Database application. + +This module handles SQLAlchemy engine creation, session management, and provides +database connection utilities for the FastAPI application. +""" + +import os +from typing import Generator + +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker, Session +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +# Database configuration from environment variables +DATABASE_URL = os.getenv( + "DATABASE_URL", + "sqlite:///./delphi.db" # Default to SQLite for development +) + +# Create SQLAlchemy engine +engine = create_engine( + DATABASE_URL, + connect_args={"check_same_thread": False} if "sqlite" in DATABASE_URL else {}, + pool_pre_ping=True, # Verify connections before reuse + echo=False # Set to True for SQL query logging in development +) + +# Create session factory +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# Create declarative base for models +Base = declarative_base() + + +def get_db() -> Generator[Session, None, None]: + """ + Dependency function that provides a database session. + + Yields a database session and ensures it's properly closed after use. + Used as a FastAPI dependency in route handlers. + + Yields: + Session: SQLAlchemy database session + + Example: + @app.get("/items/") + async def read_items(db: Session = Depends(get_db)): + return db.query(Item).all() + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + + +def create_tables() -> None: + """ + Create all database tables defined in SQLAlchemy models. + + This function should be called during application startup to ensure + all tables exist in the database. + """ + Base.metadata.create_all(bind=engine) + + +def get_database_url() -> str: + """ + Get the current database URL (with sensitive info masked). + + Returns: + str: Database URL with password masked for logging + """ + if "sqlite" in DATABASE_URL: + return DATABASE_URL + + # For PostgreSQL/MySQL, mask the password + if "@" in DATABASE_URL: + parts = DATABASE_URL.split("@") + if "://" in parts[0]: + protocol_and_auth = parts[0].split("://")[1] + if ":" in protocol_and_auth: + user_pass, host_port = protocol_and_auth.split(":", 1) + return DATABASE_URL.replace(user_pass, "****") + + return "****://****:****@****/****" diff --git a/app/main.py b/app/main.py index 0110a0f..1551825 100644 --- a/app/main.py +++ b/app/main.py @@ -1 +1,85 @@ -# FastAPI application entry point +""" +FastAPI application entry point for Delphi Database. + +This module initializes the FastAPI application, sets up database connections, +and provides the main application instance. +""" + +import logging +from contextlib import asynccontextmanager + +from fastapi import FastAPI, Depends +from sqlalchemy.orm import Session + +from .database import create_tables, get_db, get_database_url +from .models import User + + +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """ + Lifespan context manager for FastAPI application. + + Handles startup and shutdown events: + - Creates database tables on startup + - Logs database connection info + """ + # Startup + logger.info("Starting Delphi Database application...") + + # Create database tables + create_tables() + logger.info("Database tables created/verified") + + # Log database connection info + db_url = get_database_url() + logger.info(f"Database connected: {db_url}") + + yield + + # Shutdown + logger.info("Shutting down Delphi Database application...") + + +# Create FastAPI application with lifespan management +app = FastAPI( + title="Delphi Database", + description="Legal case management database application", + version="1.0.0", + lifespan=lifespan +) + + +@app.get("/") +async def root(): + """ + Root endpoint - health check. + """ + return {"message": "Delphi Database API is running"} + + +@app.get("/health") +async def health_check(db: Session = Depends(get_db)): + """ + Health check endpoint that verifies database connectivity. + """ + try: + # Test database connection by querying user count + user_count = db.query(User).count() + return { + "status": "healthy", + "database": "connected", + "users": user_count + } + except Exception as e: + logger.error(f"Health check failed: {e}") + return { + "status": "unhealthy", + "database": "error", + "error": str(e) + } diff --git a/app/models.py b/app/models.py index 9c65c6d..f4409cd 100644 --- a/app/models.py +++ b/app/models.py @@ -1 +1,181 @@ -# SQLAlchemy models for the Delphi database +""" +SQLAlchemy models for the Delphi database. + +All models inherit from Base which is configured in the database module. +""" + +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float, Text, Boolean +from sqlalchemy.orm import relationship +from sqlalchemy.sql import func +from .database import Base + + +class User(Base): + """ + User model for authentication. + + Stores user credentials and basic information for login functionality. + """ + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String(50), unique=True, index=True, nullable=False) + password_hash = Column(String(255), nullable=False) + is_active = Column(Boolean, default=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + def __repr__(self): + return f"" + + +class Client(Base): + """ + Client model representing individuals or entities. + + Core client information imported from ROLODEX data. + """ + __tablename__ = "clients" + + id = Column(Integer, primary_key=True, index=True) + rolodex_id = Column(String(20), unique=True, index=True) + last_name = Column(String(50)) + first_name = Column(String(50)) + middle_initial = Column(String(10)) + company = Column(String(100)) + address = Column(String(255)) + city = Column(String(50)) + state = Column(String(2)) + zip_code = Column(String(10)) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + # Relationships + phones = relationship("Phone", back_populates="client") + cases = relationship("Case", back_populates="client") + + def __repr__(self): + return f"" + + +class Phone(Base): + """ + Phone number model linked to clients. + + Stores phone number information for clients. + """ + __tablename__ = "phones" + + id = Column(Integer, primary_key=True, index=True) + client_id = Column(Integer, ForeignKey("clients.id"), nullable=False) + phone_type = Column(String(20)) # home, work, mobile, fax, etc. + phone_number = Column(String(20)) + extension = Column(String(10)) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + client = relationship("Client", back_populates="phones") + + def __repr__(self): + return f"" + + +class Case(Base): + """ + Case model representing legal cases or files. + + Main case information imported from FILES data. + """ + __tablename__ = "cases" + + id = Column(Integer, primary_key=True, index=True) + file_no = Column(String(20), unique=True, index=True, nullable=False) + client_id = Column(Integer, ForeignKey("clients.id"), nullable=False) + status = Column(String(20), default="active") + case_type = Column(String(50)) + description = Column(Text) + open_date = Column(DateTime(timezone=True)) + close_date = Column(DateTime(timezone=True)) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + + # Relationships + client = relationship("Client", back_populates="cases") + transactions = relationship("Transaction", back_populates="case") + documents = relationship("Document", back_populates="case") + payments = relationship("Payment", back_populates="case") + + def __repr__(self): + return f"" + + +class Transaction(Base): + """ + Transaction model for financial transactions. + + Records financial activities related to cases. + """ + __tablename__ = "transactions" + + id = Column(Integer, primary_key=True, index=True) + case_id = Column(Integer, ForeignKey("cases.id"), nullable=False) + transaction_date = Column(DateTime(timezone=True)) + transaction_type = Column(String(20)) + amount = Column(Float) + description = Column(Text) + reference = Column(String(50)) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + case = relationship("Case", back_populates="transactions") + + def __repr__(self): + return f"" + + +class Document(Base): + """ + Document model for case-related documents. + + Stores information about documents associated with cases. + """ + __tablename__ = "documents" + + id = Column(Integer, primary_key=True, index=True) + case_id = Column(Integer, ForeignKey("cases.id"), nullable=False) + document_type = Column(String(50)) + file_name = Column(String(255)) + file_path = Column(String(500)) + description = Column(Text) + uploaded_date = Column(DateTime(timezone=True)) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + case = relationship("Case", back_populates="documents") + + def __repr__(self): + return f"" + + +class Payment(Base): + """ + Payment model for payment records. + + Records payments made or received for cases. + """ + __tablename__ = "payments" + + id = Column(Integer, primary_key=True, index=True) + case_id = Column(Integer, ForeignKey("cases.id"), nullable=False) + payment_date = Column(DateTime(timezone=True)) + payment_type = Column(String(20)) + amount = Column(Float) + description = Column(Text) + check_number = Column(String(20)) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + # Relationships + case = relationship("Case", back_populates="payments") + + def __repr__(self): + return f"" diff --git a/delphi.db b/delphi.db new file mode 100644 index 0000000..6b2fd50 Binary files /dev/null and b/delphi.db differ