Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion db/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

database_url = settings.DATABASE_URL

engine = create_engine(database_url)
engine = create_engine(database_url,pool_pre_ping=True)
Comment thread
Sameer292 marked this conversation as resolved.
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

Expand Down
75 changes: 46 additions & 29 deletions middlewares/authMiddleWare.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,60 @@
from fastapi import Request, HTTPException
from fastapi import Request, HTTPException, Depends
from fastapi.responses import JSONResponse
from db import models
from jwt import decode, DecodeError
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from starlette.middleware.base import BaseHTTPMiddleware
from db import models
from db.database import get_db
from utils.utils import decode_token
from jwt import DecodeError

security = HTTPBearer()

def get_user_from_token(token: str, db: Session):
try:
payload = decode_token(token)
user_id = int(payload.get("sub"))
if not user_id:
return None
except HTTPException:
return None
try:
user = db.query(models.User).filter(models.User.id == user_id).first()
return user
except Exception:
return None


# Middleware to attach user to request.state
class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
db = next(get_db())
auth_header = request.headers.get("Authorization")
if auth_header and auth_header.startswith("Bearer "):
token = auth_header.split(" ")[1]
user = get_user_from_token(token, db)
if user:
request.state.user = user
else:
return JSONResponse({"detail": "Invalid token"}, status_code=401)
else:
request.state.user = None
try:
auth_header = request.headers.get("Authorization")

if auth_header and auth_header.startswith("Bearer "):
token = auth_header.split(" ")[1]
user = get_user_from_token(token, db)
if user:
request.state.user = user
print("USER:", request.state.user)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this line


else:
return JSONResponse({"message": "Invalid token"}, status_code=401)
else:
request.state.user = None

response = await call_next(request)
return response
finally:
db.close()
db.close()


# Helper function to decode token and fetch user
def get_user_from_token(token: str, db: Session):
try:
payload = decode_token(token)
user_id = int(payload.get("sub")) # just to ensure integer
if not user_id:
raise HTTPException(status_code=401, detail="Invalid token payload")

except (KeyError, ValueError, DecodeError):
raise HTTPException(status_code=401, detail="Invalid authentication token")

return db.query(models.User).filter(models.User.id == user_id).first()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle error for when no user is found



# Dependency to protect routes
def require_auth(
request: Request,
credentials: HTTPAuthorizationCredentials = Depends(security),
) -> models.User:
user = getattr(request.state, "user", None)
if not user:
raise HTTPException(status_code=401, detail="Not authenticated")
return user
5 changes: 2 additions & 3 deletions routes/authRoutes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
CreateUser,
Login,
UserResponse,
AllUsers,
RefreshTokenRequest,
AccessTokenResponse,
)
Expand Down Expand Up @@ -89,12 +88,12 @@ def get_user(
return request.state.user


@router.get("/users", response_model=AllUsers, status_code=status.HTTP_200_OK)
@router.get("/users", response_model=list[UserResponse], status_code=status.HTTP_200_OK)
def get_AllUsers(
db: Session = Depends(get_db),
):
users = db.query(models.User).all()
return {"users": users}
return users

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return in a json and proper format



@router.post("/seed_me", status_code=status.HTTP_200_OK)
Expand Down
147 changes: 105 additions & 42 deletions routes/categoryRoutes.py
Original file line number Diff line number Diff line change
@@ -1,77 +1,140 @@
from fastapi import APIRouter, Depends, Request, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBearer
from sqlalchemy.orm import Session
from db.database import get_db
from schemas.schemas import Category, AllCategories
from db import models
from schemas.schemas import Transaction, CategoryTransactionResponse
from db.database import get_db
from schemas.schemas import CategoryResponse,Category, Categoryupdate, TransactionResponse
from typing import List
from middlewares.authMiddleWare import require_auth

router = APIRouter()
security = HTTPBearer()
security = HTTPBearer()


@router.post("/categories")
@router.post("/categories", status_code=status.HTTP_201_CREATED)
def add_category(
request: Request,
category: Category,
db: Session = Depends(get_db),
credentials: HTTPAuthorizationCredentials = Depends(security),
current_user: models.User = Depends(require_auth),
):
user_id = request.state.user.id
user = db.query(models.User).filter(models.User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
normalized_name = category.name.strip().lower()
existing_category = db.query(models.Category).filter(
models.Category.user_id == current_user.id,
models.Category.name.ilike(normalized_name)
).first()
if existing_category:
raise HTTPException(status_code=400, detail="Category with this name already exists")

new_category = models.Category(
name=category.name, user_id=user.id, color=category.color, icon=category.icon
name=normalized_name,
color=category.color,
icon=category.icon,
user_id=current_user.id
)
db.add(new_category)
db.commit()
db.refresh(new_category)
return {"id": new_category.id, "message": "New category added"}
return {"id": new_category.id, "message": "Category added successfully"}


@router.get(
"/categories", response_model=AllCategories, status_code=status.HTTP_200_OK
)
@router.get("/categories", response_model=list[CategoryResponse], status_code=status.HTTP_200_OK)
def get_categories(
request: Request,
db: Session = Depends(get_db),
credentials: HTTPAuthorizationCredentials = Depends(security),
current_user: models.User = Depends(require_auth),
):
categories = db.query(models.Category).filter(models.Category.user_id == current_user.id).all()
return categories

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return in a proper json format



@router.get("/categories/{id}", status_code=status.HTTP_200_OK)
def get_category(
id: int,
db: Session = Depends(get_db),
current_user: models.User = Depends(require_auth),
):
user_id = request.state.user.id
categories = db.query(models.Category).filter(models.Category.user_id == user_id).all()
categories = db.query(models.Category).filter(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a single category... rename it to category

models.Category.id == id,
models.Category.user_id == current_user.id
).first()
if not categories:
raise HTTPException(status_code=404, detail="Categories not found")

return {"categories": categories}
raise HTTPException(status_code=404, detail="Category not found")
return categories

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use proper json format



@router.get("/category/{id}/transactions",response_model=CategoryTransactionResponse, status_code=status.HTTP_200_OK)
def category_transactions(id:int, db: Session = Depends(get_db)):
category = db.query(models.Category).filter(models.Category.id == id).first()
@router.get("/categories/{id}/transactions", response_model=List[TransactionResponse], status_code=status.HTTP_200_OK)
def category_transactions(
id: int,
db: Session = Depends(get_db),
current_user: models.User = Depends(require_auth),
):
category = db.query(models.Category).filter(
models.Category.id == id,
models.Category.user_id == current_user.id
).first()
if not category:
raise HTTPException(status_code=404, detail="Category not found")
transactions = db.query(models.Transaction).filter(models.Transaction.category_id == id).all()
return {'transactions': transactions }

transactions = db.query(models.Transaction).filter(
models.Transaction.category_id == id,
models.Transaction.user_id == current_user.id
).all()
return {"transactions": transactions}


@router.get("/category/{id}", status_code=status.HTTP_200_OK)
def getCategory(id: int, db: Session = Depends(get_db)):
category = db.query(models.Category).filter(models.Category.id == id).first()
@router.patch("/categories/{category_id}")
def update_category(
category_id: int,
payload: Categoryupdate,
db: Session = Depends(get_db),
current_user: models.User = Depends(require_auth),
):
category = db.query(models.Category).filter(
models.Category.id == category_id,
models.Category.user_id == current_user.id
).first()
if not category:
raise HTTPException(status_code=404, detail="Category not found")
return category

@router.delete("/category/{id}", status_code=status.HTTP_200_OK)
def deleteCategory(id: int, db: Session = Depends(get_db)):
category_to_delete = db.query(models.Category).filter(models.Category.id == id).first()
update_data = payload.model_dump(exclude_unset=True)
if not update_data:
raise HTTPException(status_code=400, detail="No fields provided for update")

for field, value in update_data.items():
if isinstance(value, str) and not value.strip():
raise HTTPException(status_code=400, detail=f"{field} cannot be empty")
setattr(category, field, value)

db.commit()
db.refresh(category)
return {"message": "Category updated successfully", "category_id": category.id}


if not category_to_delete:
@router.delete("/categories/{category_id}")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe related transaction should be deleted first

def delete_category(
category_id: int,
db: Session = Depends(get_db),
current_user: models.User = Depends(require_auth),
):
category = db.query(models.Category).filter(
models.Category.id == category_id,
models.Category.user_id == current_user.id
).first()
if not category:
raise HTTPException(status_code=404, detail="Category not found")

db.delete(category_to_delete)
db.delete(category)
db.commit()
return {
"message": "Category deleted successfully",
}
return {"message": "Category deleted successfully"}


@router.delete("/categories")
def delete_all_categories(
db: Session = Depends(get_db),
current_user: models.User = Depends(require_auth),
):
# Delete all transactions first
db.query(models.Transaction).filter(models.Transaction.user_id == current_user.id).delete(synchronize_session=False)
# Delete all categories
db.query(models.Category).filter(models.Category.user_id == current_user.id).delete(synchronize_session=False)
db.commit()
return {"message": "All categories and related transactions deleted"}
Loading