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
27 changes: 16 additions & 11 deletions backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,41 @@
from flask_cors import CORS
from backend.database import db, migrate


def create_app(config_class):
app = Flask(__name__,
template_folder='../frontend/templates',
static_folder='../frontend/static')
app = Flask(
__name__,
template_folder="../frontend/templates",
static_folder="../frontend/static",
)
app.config.from_object(config_class)

# Initialize extensions
db.init_app(app)
migrate.init_app(app, db)
jwt = JWTManager(app)
CORS(app)

# Landing page route
@app.route('/')
@app.route("/")
def index():
return render_template('index.html')
return render_template("index.html")

# Health check endpoint
@app.route('/api')
@app.route("/api")
def api_status():
return jsonify({'message': 'API is working', 'status': 'success'}), 200
return jsonify({"message": "API is working", "status": "success"}), 200

# Register blueprints
from backend.routes.auth import auth_bp
from backend.routes.budget_routes import budget_bp

app.register_blueprint(budget_bp, url_prefix="/api/budgets")

app.register_blueprint(auth_bp, url_prefix='/api/auth')
app.register_blueprint(auth_bp, url_prefix="/api/auth")

# Create tables
with app.app_context():
db.create_all()

return app
3 changes: 3 additions & 0 deletions backend/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .budget import Budget
from .transaction import Transaction
from .category import Category
15 changes: 15 additions & 0 deletions backend/models/budget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from backend.database import db
from datetime import datetime

class Budget(db.Model):
__tablename__ = 'budgets'

id = db.Column(db.Integer, primary_key=True)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
amount = db.Column(db.Float, nullable=False)
period = db.Column(db.String(20), nullable=False) # e.g., 'monthly', 'weekly'
created_at = db.Column(db.DateTime, default=datetime.utcnow)

category = db.relationship('Category', backref='budgets')
user = db.relationship('User', backref='budgets')
106 changes: 106 additions & 0 deletions backend/routes/budget_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from sqlalchemy import func
from backend.database import db
from backend.models.budget import Budget
from backend.models.transaction import Transaction
from backend.services.budget_service import calculate_spending


budget_bp = Blueprint("budget_bp", __name__)


@budget_bp.route("/ping", methods=["GET"])
def ping():
return {"message": "Budget service is up"}, 200


@budget_bp.route("/api/budgets", methods=["POST"])
@jwt_required()
def create_budget():
user_id = get_jwt_identity()
data = request.json

# Validate input...
budget = Budget(
category_id=data["category_id"],
user_id=user_id,
amount=data["amount"],
period=data["period"],
)
db.session.add(budget)
db.session.commit()
return jsonify({"message": "Budget created", "budget": budget.id}), 201


@budget_bp.route("/api/budgets", methods=["GET"])
@jwt_required()
def list_budgets():
user_id = get_jwt_identity()
budgets = Budget.query.filter_by(user_id=user_id).all()
return jsonify(
[
{
"id": b.id,
"category_id": b.category_id,
"amount": b.amount,
"period": b.period,
}
for b in budgets
]
)


@budget_bp.route("/api/budgets/<int:budget_id>", methods=["PUT"])
@jwt_required()
def update_budget(budget_id):
user_id = get_jwt_identity()
budget = Budget.query.filter_by(id=budget_id, user_id=user_id).first_or_404()
data = request.json
budget.amount = data.get("amount", budget.amount)
budget.period = data.get("period", budget.period)
db.session.commit()
return jsonify({"message": "Budget updated"})


@budget_bp.route("/api/budgets/<int:budget_id>", methods=["DELETE"])
@jwt_required()
def delete_budget(budget_id):
user_id = get_jwt_identity()
budget = Budget.query.filter_by(id=budget_id, user_id=user_id).first_or_404()
db.session.delete(budget)
db.session.commit()
return jsonify({"message": "Budget deleted"})


@budget_bp.route("/api/budgets/status", methods=["GET"])
@jwt_required()
def budget_status():
user_id = get_jwt_identity()
budgets = Budget.query.filter_by(user_id=user_id).all()
status_list = []

for b in budgets:
spent = calculate_spending(user_id, b.category_id, b.period)
status_list.append(
{
"budget_id": b.id,
"category_id": b.category_id,
"amount_budgeted": b.amount,
"amount_spent": spent,
"status": "over" if spent > b.amount else "under",
"difference": round(spent - b.amount, 2),
}
)

return jsonify(status_list)


def check_alerts(user_id):
alerts = []
budgets = Budget.query.filter_by(user_id=user_id).all()
for b in budgets:
spent = calculate_spending(user_id, b.category_id, b.period)
if spent > b.amount:
alerts.append(f"You've exceeded your budget for category {b.category_id}.")
return alerts
28 changes: 28 additions & 0 deletions backend/services/budget_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# services/budget_service.py
from datetime import datetime, timedelta
from sqlalchemy import func
from backend.database import db
from backend.models.transaction import Transaction


def calculate_spending(user_id, category_id, period):
now = datetime.utcnow()

if period == "monthly":
start = datetime(now.year, now.month, 1)
elif period == "weekly":
start = now - timedelta(days=now.weekday()) # Monday start
else:
return 0

total = (
db.session.query(func.coalesce(func.sum(Transaction.amount), 0))
.filter(
Transaction.user_id == user_id,
Transaction.category_id == category_id,
Transaction.created_at >= start,
)
.scalar()
)

return total