first commit

This commit is contained in:
Jp
2026-01-30 15:03:43 +08:00
commit 656a510c73
32 changed files with 3721 additions and 0 deletions

494
calorie_tracker_app/app.py Normal file
View File

@@ -0,0 +1,494 @@
from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import date, datetime, timedelta
import os
from config import Config
from models import db, User, FoodItem, Meal, MealFood, WaterLog, WeightLog, MealPlan, PlannedFood, UserGoal
from api_client import NutritionAPI, search_all_sources
from utils import (
calculate_daily_totals, calculate_water_total, get_weight_trend,
get_calorie_trend, update_daily_summary, calculate_bmr, calculate_tdee,
calculate_macro_targets, get_macro_percentages, suggest_foods_for_macros
)
app = Flask(__name__)
app.config.from_object(Config)
# Initialize extensions
db.init_app(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
# Initialize API client
api_client = NutritionAPI(app.config['API_NINJAS_KEY'])
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# ==================== ROUTES ====================
@app.route('/')
@login_required
def index():
"""Dashboard - Today's summary"""
today = date.today()
# Get daily totals
nutrition = calculate_daily_totals(current_user.id, today)
water = calculate_water_total(current_user.id, today)
# Get user goals
goals = UserGoal.query.filter_by(user_id=current_user.id).first()
if not goals:
goals = UserGoal(
user_id=current_user.id,
target_protein_g=150,
target_carbs_g=200,
target_fat_g=60,
target_water_ml=2000
)
db.session.add(goals)
db.session.commit()
# Get weight info
weight_log_today = WeightLog.query.filter_by(user_id=current_user.id, date=today).first()
weight_log_yesterday = WeightLog.query.filter_by(
user_id=current_user.id,
date=today - timedelta(days=1)
).first()
weight_change = None
if weight_log_today and weight_log_yesterday:
weight_change = weight_log_today.weight_kg - weight_log_yesterday.weight_kg
# Calculate remaining
remaining = {
'calories': current_user.target_daily_calories - nutrition['calories'],
'protein': goals.target_protein_g - nutrition['protein'],
'carbs': goals.target_carbs_g - nutrition['carbs'],
'fat': goals.target_fat_g - nutrition['fat'],
'water': goals.target_water_ml - water['total_ml']
}
# Get macro percentages
macro_percentages = get_macro_percentages(
nutrition['protein'],
nutrition['carbs'],
nutrition['fat']
)
# Get trends
weight_trend = get_weight_trend(current_user.id, 7)
calorie_trend = get_calorie_trend(current_user.id, 7)
# Suggestions
suggestions = suggest_foods_for_macros(
remaining['protein'],
remaining['carbs'],
remaining['fat']
)
return render_template('dashboard.html',
nutrition=nutrition,
water=water,
goals=goals,
remaining=remaining,
macro_percentages=macro_percentages,
weight_today=weight_log_today,
weight_change=weight_change,
weight_trend=weight_trend,
calorie_trend=calorie_trend,
suggestions=suggestions,
today=today)
@app.route('/login', methods=['GET', 'POST'])
def login():
"""User login"""
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password):
login_user(user)
return redirect(url_for('index'))
else:
flash('Invalid username or password', 'error')
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
"""User registration"""
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
name = request.form.get('name')
if User.query.filter_by(username=username).first():
flash('Username already exists', 'error')
return redirect(url_for('register'))
user = User(
username=username,
password=generate_password_hash(password),
name=name
)
db.session.add(user)
db.session.commit()
flash('Registration successful! Please login.', 'success')
return redirect(url_for('login'))
return render_template('register.html')
@app.route('/logout')
@login_required
def logout():
"""User logout"""
logout_user()
return redirect(url_for('login'))
@app.route('/add-meal', methods=['GET', 'POST'])
@login_required
def add_meal():
"""Add a meal"""
if request.method == 'POST':
meal_date = request.form.get('date', date.today())
meal_type = request.form.get('meal_type')
meal_time = request.form.get('time')
# Convert date string to date object
if isinstance(meal_date, str):
meal_date = datetime.strptime(meal_date, '%Y-%m-%d').date()
# Convert time string to time object
meal_time_obj = None
if meal_time:
meal_time_obj = datetime.strptime(meal_time, '%H:%M').time()
# Create meal
meal = Meal(
user_id=current_user.id,
date=meal_date,
meal_type=meal_type,
time=meal_time_obj
)
db.session.add(meal)
db.session.flush() # Get meal ID
# Add foods to meal
food_ids = request.form.getlist('food_id[]')
quantities = request.form.getlist('quantity[]')
for food_id, quantity in zip(food_ids, quantities):
if food_id and quantity:
meal_food = MealFood(
meal_id=meal.id,
food_id=int(food_id),
quantity=float(quantity)
)
meal_food.calculate_nutrition()
db.session.add(meal_food)
db.session.commit()
update_daily_summary(current_user.id, meal_date)
flash('Meal added successfully!', 'success')
return redirect(url_for('index'))
# GET request - show form
return render_template('add_meal.html', today=date.today())
@app.route('/api/search-food')
@login_required
def api_search_food():
"""API endpoint for food search (AJAX)"""
query = request.args.get('q', '')
if len(query) < 2:
return jsonify([])
results = search_all_sources(query, api_client)
return jsonify(results)
@app.route('/api/add-food', methods=['POST'])
@login_required
def api_add_food():
"""API endpoint to add a food from API to database"""
data = request.json
# Save food to database
food = FoodItem(
name=data['name'],
calories=data['calories'],
protein_g=data.get('protein_g', 0),
carbs_g=data.get('carbs_g', 0),
fat_g=data.get('fat_g', 0),
serving_size_g=data.get('serving_size_g', 100),
serving_description=data.get('serving_description', '1 serving'),
source='api'
)
db.session.add(food)
db.session.commit()
return jsonify({
'success': True,
'food_id': food.id,
'name': food.name
})
@app.route('/add-water', methods=['POST'])
@login_required
def add_water():
"""Add water intake"""
amount_ml = int(request.form.get('amount_ml', 250))
log_date = request.form.get('date', date.today())
if isinstance(log_date, str):
log_date = datetime.strptime(log_date, '%Y-%m-%d').date()
water_log = WaterLog(
user_id=current_user.id,
date=log_date,
amount_ml=amount_ml,
time=datetime.now().time()
)
db.session.add(water_log)
db.session.commit()
update_daily_summary(current_user.id, log_date)
flash(f'Added {amount_ml}ml of water!', 'success')
return redirect(url_for('index'))
@app.route('/add-weight', methods=['POST'])
@login_required
def add_weight():
"""Add weight log"""
weight_kg = float(request.form.get('weight_kg'))
log_date = request.form.get('date', date.today())
if isinstance(log_date, str):
log_date = datetime.strptime(log_date, '%Y-%m-%d').date()
# Check if weight log already exists for today
existing = WeightLog.query.filter_by(user_id=current_user.id, date=log_date).first()
if existing:
existing.weight_kg = weight_kg
existing.time = datetime.now().time()
else:
weight_log = WeightLog(
user_id=current_user.id,
date=log_date,
weight_kg=weight_kg,
time=datetime.now().time()
)
db.session.add(weight_log)
db.session.commit()
update_daily_summary(current_user.id, log_date)
flash(f'Weight logged: {weight_kg}kg', 'success')
return redirect(url_for('index'))
@app.route('/meal-planner')
@login_required
def meal_planner():
"""Meal planner page"""
# Get date range (current week)
today = date.today()
start_date = today - timedelta(days=today.weekday()) # Monday
dates = [start_date + timedelta(days=i) for i in range(7)]
# Get meal plans for the week
meal_plans = {}
for d in dates:
plans = MealPlan.query.filter_by(user_id=current_user.id, date=d).all()
meal_plans[d.isoformat()] = [
{
'id': p.id,
'meal_type': p.meal_type,
'is_completed': p.is_completed,
'foods': [
{
'name': pf.food.name,
'quantity': pf.quantity
}
for pf in p.foods
],
'totals': p.calculate_totals()
}
for p in plans
]
return render_template('meal_planner.html', dates=dates, meal_plans=meal_plans, today=today)
@app.route('/foods')
@login_required
def foods():
"""Food database page"""
category = request.args.get('category', 'all')
search_query = request.args.get('q', '')
query = FoodItem.query
if category != 'all':
query = query.filter_by(category=category)
if search_query:
query = query.filter(
db.or_(
FoodItem.name.ilike(f'%{search_query}%'),
FoodItem.name_tagalog.ilike(f'%{search_query}%')
)
)
# Filipino foods first
filipino_foods = query.filter_by(is_filipino=True).all()
other_foods = query.filter_by(is_filipino=False).limit(20).all()
categories = ['all', 'kanin', 'ulam', 'sabaw', 'gulay', 'meryenda', 'almusal']
return render_template('foods.html',
filipino_foods=filipino_foods,
other_foods=other_foods,
categories=categories,
current_category=category,
search_query=search_query)
@app.route('/progress')
@login_required
def progress():
"""Progress tracking page"""
days = int(request.args.get('days', 30))
weight_trend = get_weight_trend(current_user.id, days)
calorie_trend = get_calorie_trend(current_user.id, days)
# Calculate averages
if calorie_trend:
avg_calories = sum(d['calories'] for d in calorie_trend) / len(calorie_trend)
avg_protein = sum(d['protein'] for d in calorie_trend) / len(calorie_trend)
avg_carbs = sum(d['carbs'] for d in calorie_trend) / len(calorie_trend)
avg_fat = sum(d['fat'] for d in calorie_trend) / len(calorie_trend)
else:
avg_calories = avg_protein = avg_carbs = avg_fat = 0
# Weight change
weight_change = None
if len(weight_trend) >= 2:
weight_change = weight_trend[-1]['weight_kg'] - weight_trend[0]['weight_kg']
return render_template('progress.html',
weight_trend=weight_trend,
calorie_trend=calorie_trend,
avg_calories=avg_calories,
avg_protein=avg_protein,
avg_carbs=avg_carbs,
avg_fat=avg_fat,
weight_change=weight_change,
days=days)
@app.route('/goals', methods=['GET', 'POST'])
@login_required
def goals():
"""Goals and settings page"""
if request.method == 'POST':
# Update user info
current_user.age = int(request.form.get('age', 25))
current_user.gender = request.form.get('gender', 'male')
current_user.height_cm = float(request.form.get('height_cm', 170))
current_user.weight_kg = float(request.form.get('weight_kg', 70))
current_user.activity_level = request.form.get('activity_level', 'moderate')
# Calculate targets
bmr = calculate_bmr(
current_user.weight_kg,
current_user.height_cm,
current_user.age,
current_user.gender
)
tdee = calculate_tdee(bmr, current_user.activity_level)
# Get goal type
goal_type = request.form.get('goal_type', 'recomp')
# Adjust calories based on goal
if goal_type == 'weight_loss':
target_calories = tdee - 500
elif goal_type == 'muscle_gain':
target_calories = tdee + 300
else: # recomp
target_calories = tdee
current_user.target_daily_calories = int(target_calories)
# Update or create goals
user_goals = UserGoal.query.filter_by(user_id=current_user.id).first()
if not user_goals:
user_goals = UserGoal(user_id=current_user.id)
db.session.add(user_goals)
user_goals.goal_type = goal_type
user_goals.target_weight_kg = float(request.form.get('target_weight_kg', 70))
# Calculate macros
macros = calculate_macro_targets(current_user.weight_kg, goal_type)
user_goals.target_protein_g = macros['protein_g']
user_goals.target_carbs_g = macros['carbs_g']
user_goals.target_fat_g = macros['fat_g']
user_goals.target_water_ml = int(request.form.get('target_water_ml', 2000))
db.session.commit()
flash('Goals updated successfully!', 'success')
return redirect(url_for('goals'))
# GET request
user_goals = UserGoal.query.filter_by(user_id=current_user.id).first()
# Calculate current BMR and TDEE
bmr = tdee = None
if current_user.weight_kg and current_user.height_cm and current_user.age:
bmr = calculate_bmr(
current_user.weight_kg,
current_user.height_cm,
current_user.age,
current_user.gender or 'male'
)
tdee = calculate_tdee(bmr, current_user.activity_level or 'moderate')
return render_template('goals.html',
user=current_user,
goals=user_goals,
bmr=bmr,
tdee=tdee)
# ==================== DATABASE INITIALIZATION ====================
@app.cli.command()
def init_db():
"""Initialize the database"""
db.create_all()
print("Database initialized!")
@app.cli.command()
def seed_db():
"""Seed the database with Filipino foods"""
from seed_data import seed_filipino_foods
seed_filipino_foods()
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(debug=True, host='127.0.0.1', port=5001)