Skip to content
Merged
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
167 changes: 159 additions & 8 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from flask_cors import CORS
from functools import wraps
from werkzeug.security import generate_password_hash, check_password_hash
from bson.objectid import ObjectId # Used to work with MongoDB document IDs

from config import Config
from services.db import mongo
Expand All @@ -17,7 +18,7 @@

# Load values from config.py
app.config.from_object(Config)

print("MONGO_URI =", app.config.get("MONGO_URI"))
# Secret key is needed for session/login
app.secret_key = app.config.get("SECRET_KEY", "mysecret123")

Expand All @@ -30,7 +31,7 @@

# ------------------------
# LOGIN REQUIRED DECORATOR
# Protect pages that need login
# This protects routes so only logged-in users can open them
# ------------------------
def login_required(f):
@wraps(f)
Expand Down Expand Up @@ -126,24 +127,29 @@ def health():
# ------------------------
# DASHBOARD
# Protected page
# IMPORTANT:
# We use find() without hiding _id
# because _id is needed for edit/delete buttons
# ------------------------
@app.route("/")
@app.route("/dashboard")
@login_required
def dashboard():
try:
workouts = list(mongo.db.workouts.find({}, {"_id": 0}))
meals = list(mongo.db.meals.find({}, {"_id": 0}))
sleeps = list(mongo.db.sleeps.find({}, {"_id": 0}))
workouts = list(mongo.db.workouts.find())
meals = list(mongo.db.meals.find())
sleeps = list(mongo.db.sleeps.find())
except Exception:
workouts = []
meals = []
sleeps = []

# Calculate average workout duration
avg_workout = round(
sum(w.get("duration", 0) for w in workouts) / len(workouts), 2
) if workouts else 0

# Calculate average sleep duration
avg_sleep = round(
sum(s.get("duration", 0) for s in sleeps) / len(sleeps), 2
) if sleeps else 0
Expand All @@ -160,7 +166,6 @@ def dashboard():

# ------------------------
# ADD WORKOUT PAGE
# Protected page
# ------------------------
@app.route("/add-workout", methods=["GET", "POST"])
@login_required
Expand All @@ -187,9 +192,59 @@ def add_workout_page():
return render_template("add_workout.html", error=error)


# ------------------------
# EDIT WORKOUT PAGE
# Opens selected workout and updates it
# ------------------------
@app.route("/edit-workout/<id>", methods=["GET", "POST"])
@login_required
def edit_workout(id):
workout = mongo.db.workouts.find_one({"_id": ObjectId(id)})

if not workout:
return "Workout not found", 404

error = None

if request.method == "POST":
try:
updated_workout = {
"workout_type": request.form.get("workout_type"),
"duration": int(request.form.get("duration")),
"calories_burned": int(request.form.get("calories_burned", 0)),
"date": request.form.get("date"),
"created_by": session.get("user")
}

mongo.db.workouts.update_one(
{"_id": ObjectId(id)},
{"$set": updated_workout}
)

return redirect(url_for("dashboard"))
except Exception:
error = "Failed to update workout."

return render_template("edit_workout.html", workout=workout, error=error)


# ------------------------
# DELETE WORKOUT
# Deletes selected workout from database
# POST is safer for delete than GET
# ------------------------
@app.route("/delete-workout/<id>", methods=["POST"])
@login_required
def delete_workout(id):
try:
mongo.db.workouts.delete_one({"_id": ObjectId(id)})
return redirect(url_for("dashboard"))
except Exception:
return "Failed to delete workout", 500


# ------------------------
# ADD MEAL PAGE
# Protected page
# ------------------------
@app.route("/add-meal", methods=["GET", "POST"])
@login_required
Expand All @@ -214,9 +269,56 @@ def add_meal_page():
return render_template("add_meal.html", error=error)


# ------------------------
# EDIT MEAL PAGE
# Opens selected meal and updates it
# ------------------------
@app.route("/edit-meal/<id>", methods=["GET", "POST"])
@login_required
def edit_meal(id):
meal = mongo.db.meals.find_one({"_id": ObjectId(id)})

if not meal:
return "Meal not found", 404

error = None

if request.method == "POST":
try:
updated_meal = {
"meal_type": request.form.get("meal_type"),
"calories": int(request.form.get("calories")),
"created_by": session.get("user")
}

mongo.db.meals.update_one(
{"_id": ObjectId(id)},
{"$set": updated_meal}
)

return redirect(url_for("dashboard"))
except Exception:
error = "Failed to update meal."

return render_template("edit_meal.html", meal=meal, error=error)


# ------------------------
# DELETE MEAL
# Deletes selected meal from database
# ------------------------
@app.route("/delete-meal/<id>", methods=["POST"])
@login_required
def delete_meal(id):
try:
mongo.db.meals.delete_one({"_id": ObjectId(id)})
return redirect(url_for("dashboard"))
except Exception:
return "Failed to delete meal", 500


# ------------------------
# ADD SLEEP PAGE
# Protected page
# ------------------------
@app.route("/add-sleep", methods=["GET", "POST"])
@login_required
Expand All @@ -241,8 +343,57 @@ def add_sleep_page():
return render_template("add_sleep.html", error=error)


# ------------------------
# EDIT SLEEP PAGE
# Opens selected sleep record and updates it
# ------------------------
@app.route("/edit-sleep/<id>", methods=["GET", "POST"])
@login_required
def edit_sleep(id):
sleep = mongo.db.sleeps.find_one({"_id": ObjectId(id)})

if not sleep:
return "Sleep record not found", 404

error = None

if request.method == "POST":
try:
updated_sleep = {
"duration": float(request.form.get("duration")),
"quality": request.form.get("quality"),
"created_by": session.get("user")
}

mongo.db.sleeps.update_one(
{"_id": ObjectId(id)},
{"$set": updated_sleep}
)

return redirect(url_for("dashboard"))
except Exception:
error = "Failed to update sleep record."

return render_template("edit_sleep.html", sleep=sleep, error=error)


# ------------------------
# DELETE SLEEP
# Deletes selected sleep record from database
# ------------------------
@app.route("/delete-sleep/<id>", methods=["POST"])
@login_required
def delete_sleep(id):
try:
mongo.db.sleeps.delete_one({"_id": ObjectId(id)})
return redirect(url_for("dashboard"))
except Exception:
return "Failed to delete sleep record", 500


# ------------------------
# REGISTER API BLUEPRINTS
# These are your existing API routes
# ------------------------
app.register_blueprint(workout_bp)
app.register_blueprint(meal_bp)
Expand Down
80 changes: 71 additions & 9 deletions templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,32 @@ <h5 class="mb-0"><i class="fas fa-dumbbell me-2"></i>Workout Summary</h5>
</div>
<div class="card-body">
<p class="fs-5"><strong>Avg Duration:</strong> {{ avg_workout }} minutes</p>

<ul class="list-group list-group-flush">
{% for workout in workouts %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>{{ workout['workout_type'] }}</span>
<span class="badge bg-success">{{ workout['duration'] }} min • {{ workout.get('calories_burned', 0) }} cal</span>
<li class="list-group-item">
<!-- First row: workout info -->
<div class="d-flex justify-content-between align-items-center">
<span>{{ workout['workout_type'] }}</span>
<span class="badge bg-success">
{{ workout['duration'] }} min • {{ workout.get('calories_burned', 0) }} cal
</span>
</div>

<!-- Second row: edit and delete buttons -->
<div class="mt-2 d-flex gap-2">
<!-- Edit button -->
<a href="{{ url_for('edit_workout', id=workout['_id']) }}" class="btn btn-sm btn-outline-primary">
Edit
</a>

<!-- Delete button -->
<form action="{{ url_for('delete_workout', id=workout['_id']) }}" method="POST" style="display:inline;">
<button type="submit" class="btn btn-sm btn-outline-danger">
Delete
</button>
</form>
</div>
</li>
{% else %}
<li class="list-group-item text-muted">No workout data yet.</li>
Expand All @@ -43,9 +64,29 @@ <h5 class="mb-0"><i class="fas fa-utensils me-2"></i>Meal Summary</h5>
<div class="card-body">
<ul class="list-group list-group-flush">
{% for meal in meals %}
<li class="list-group-item d-flex justify-content-between">
<span>{{ meal['meal_type'] }}</span>
<span class="badge bg-warning text-dark">{{ meal['calories'] }} cal</span>
<li class="list-group-item">
<!-- First row: meal info -->
<div class="d-flex justify-content-between align-items-center">
<span>{{ meal['meal_type'] }}</span>
<span class="badge bg-warning text-dark">
{{ meal['calories'] }} cal
</span>
</div>

<!-- Second row: edit and delete buttons -->
<div class="mt-2 d-flex gap-2">
<!-- Edit button -->
<a href="{{ url_for('edit_meal', id=meal['_id']) }}" class="btn btn-sm btn-outline-primary">
Edit
</a>

<!-- Delete button -->
<form action="{{ url_for('delete_meal', id=meal['_id']) }}" method="POST" style="display:inline;">
<button type="submit" class="btn btn-sm btn-outline-danger">
Delete
</button>
</form>
</div>
</li>
{% else %}
<li class="list-group-item text-muted">No meal data yet.</li>
Expand All @@ -63,11 +104,32 @@ <h5 class="mb-0"><i class="fas fa-bed me-2"></i>Sleep Summary</h5>
</div>
<div class="card-body">
<p class="fs-5"><strong>Avg Sleep:</strong> {{ avg_sleep }} hours</p>

<ul class="list-group list-group-flush">
{% for sleep in sleeps %}
<li class="list-group-item d-flex justify-content-between">
<span>{{ sleep['duration'] }} hours</span>
<span class="badge bg-info">{{ sleep.get('quality', 'N/A') }}</span>
<li class="list-group-item">
<!-- First row: sleep info -->
<div class="d-flex justify-content-between align-items-center">
<span>{{ sleep['duration'] }} hours</span>
<span class="badge bg-info">
{{ sleep.get('quality', 'N/A') }}
</span>
</div>

<!-- Second row: edit and delete buttons -->
<div class="mt-2 d-flex gap-2">
<!-- Edit button -->
<a href="{{ url_for('edit_sleep', id=sleep['_id']) }}" class="btn btn-sm btn-outline-primary">
Edit
</a>

<!-- Delete button -->
<form action="{{ url_for('delete_sleep', id=sleep['_id']) }}" method="POST" style="display:inline;">
<button type="submit" class="btn btn-sm btn-outline-danger">
Delete
</button>
</form>
</div>
</li>
{% else %}
<li class="list-group-item text-muted">No sleep data yet.</li>
Expand Down
26 changes: 26 additions & 0 deletions templates/edit_meal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Edit Meal</title>
</head>
<body>
<h2>Edit Meal</h2>

{% if error %}
<p style="color:red;">{{ error }}</p>
{% endif %}

<form method="POST">
<label>Meal Type:</label><br>
<input type="text" name="meal_type" value="{{ meal.meal_type }}" required><br><br>

<label>Calories:</label><br>
<input type="number" name="calories" value="{{ meal.calories }}" required><br><br>

<button type="submit">Update Meal</button>
</form>

<br>
<a href="{{ url_for('dashboard') }}">Back to Dashboard</a>
</body>
</html>
Loading
Loading