feat: Implement core application structure, AI extraction, persistence, and Telegram bot modules with updated configuration and dependencies.

This commit is contained in:
Marco Gallegos
2025-12-18 12:15:04 -06:00
parent 7276e480b0
commit 899482580e
45 changed files with 1157 additions and 225 deletions

View File

46
app/persistence/db.py Normal file
View File

@@ -0,0 +1,46 @@
"""
Database connection and session management.
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import logging
from app.config import config
logger = logging.getLogger(__name__)
try:
# The 'check_same_thread' argument is specific to SQLite.
engine_args = {"check_same_thread": False} if config.DATABASE_URL.startswith("sqlite") else {}
engine = create_engine(
config.DATABASE_URL,
connect_args=engine_args
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
logger.info("Database engine created successfully.")
except Exception as e:
logger.critical(f"Failed to connect to the database: {e}")
# Exit or handle the critical error appropriately
engine = None
SessionLocal = None
Base = None
def get_db():
"""
Dependency for FastAPI routes to get a DB session.
"""
if SessionLocal is None:
raise Exception("Database is not configured. Cannot create session.")
db = SessionLocal()
try:
yield db
finally:
db.close()

View File

@@ -0,0 +1,66 @@
"""
Data access layer for persistence.
Contains functions to interact with the database.
"""
from sqlalchemy import Column, Integer, String, Float, Date, DateTime, Text
from sqlalchemy.orm import Session
import logging
from app.persistence.db import Base, engine
from app.schema.base import FinalExpense
logger = logging.getLogger(__name__)
# --- Database ORM Model ---
class ExpenseDB(Base):
__tablename__ = "expenses"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(String, index=True, nullable=False)
provider_name = Column(String, nullable=False)
amount = Column(Float, nullable=False)
currency = Column(String(3), nullable=False)
expense_date = Column(Date, nullable=False)
description = Column(Text, nullable=True)
category = Column(String, nullable=False)
subcategory = Column(String, nullable=True)
expense_type = Column(String, nullable=False)
confirmed_at = Column(DateTime, nullable=False)
initial_processing_method = Column(String)
def create_tables():
"""
Creates all database tables defined by models inheriting from Base.
"""
if engine:
logger.info("Creating database tables if they don't exist...")
Base.metadata.create_all(bind=engine)
logger.info("Tables created successfully.")
else:
logger.error("Cannot create tables, database engine is not available.")
# --- Repository Functions ---
def save_final_expense(db: Session, expense: FinalExpense) -> ExpenseDB:
"""
Saves a user-confirmed expense to the database.
Args:
db: The database session.
expense: The FinalExpense object to save.
Returns:
The created ExpenseDB object.
"""
logger.info(f"Saving final expense for user {expense.user_id} to the database.")
db_expense = ExpenseDB(**expense.dict())
db.add(db_expense)
db.commit()
db.refresh(db_expense)
logger.info(f"Successfully saved expense with ID {db_expense.id}.")
return db_expense