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)