735 lines
20 KiB
JavaScript
735 lines
20 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const session = require('express-session');
|
|
const passport = require('passport');
|
|
const LocalStrategy = require('passport-local').Strategy;
|
|
const expressLayouts = require('express-ejs-layouts');
|
|
const flash = require('express-flash');
|
|
const methodOverride = require('method-override');
|
|
const path = require('path');
|
|
const SQLiteStore = require('connect-sqlite3')(session);
|
|
const db = require('./models');
|
|
const utils = require('./utils');
|
|
const { NutritionAPI, searchAllSources } = require('./utils/api_client');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 5001;
|
|
|
|
// Initialize API Client
|
|
const apiClient = new NutritionAPI(process.env.API_NINJAS_KEY);
|
|
|
|
// Passport Config
|
|
passport.use(new LocalStrategy(
|
|
async (username, password, done) => {
|
|
try {
|
|
const user = await db.User.findOne({ where: { username } });
|
|
if (!user) {
|
|
return done(null, false, { message: 'Incorrect username.' });
|
|
}
|
|
if (!user.validPassword(password)) {
|
|
return done(null, false, { message: 'Incorrect password.' });
|
|
}
|
|
return done(null, user);
|
|
} catch (err) {
|
|
return done(err);
|
|
}
|
|
}
|
|
));
|
|
|
|
passport.serializeUser((user, done) => {
|
|
done(null, user.id);
|
|
});
|
|
|
|
passport.deserializeUser(async (id, done) => {
|
|
try {
|
|
const user = await db.User.findByPk(id);
|
|
done(null, user);
|
|
} catch (err) {
|
|
done(err);
|
|
}
|
|
});
|
|
|
|
// Middleware
|
|
app.set('view engine', 'ejs');
|
|
app.set('views', path.join(__dirname, 'views'));
|
|
app.set('layout', 'layout');
|
|
app.use(expressLayouts);
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
app.use(express.urlencoded({ extended: true }));
|
|
app.use(express.json());
|
|
app.use(methodOverride('_method'));
|
|
|
|
// Session
|
|
app.use(session({
|
|
store: new SQLiteStore({ db: 'sessions.db', dir: './data' }),
|
|
secret: process.env.SECRET_KEY || 'secret',
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: { maxAge: 30 * 24 * 60 * 60 * 1000 } // 30 days
|
|
}));
|
|
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
app.use(flash());
|
|
|
|
// Global Variables
|
|
app.use((req, res, next) => {
|
|
res.locals.current_user = req.user;
|
|
res.locals.success_msg = req.flash('success_msg');
|
|
res.locals.error_msg = req.flash('error_msg');
|
|
res.locals.error = req.flash('error');
|
|
res.locals.path = req.path;
|
|
next();
|
|
});
|
|
|
|
// Helper functions for templates
|
|
app.locals.round = Math.round;
|
|
|
|
// Auth Middleware
|
|
function ensureAuthenticated(req, res, next) {
|
|
if (req.isAuthenticated()) {
|
|
return next();
|
|
}
|
|
req.flash('error_msg', 'Please log in to view that resource');
|
|
res.redirect('/login');
|
|
}
|
|
|
|
// Routes
|
|
app.get('/', (req, res) => {
|
|
if (req.isAuthenticated()) {
|
|
res.redirect('/dashboard');
|
|
} else {
|
|
res.redirect('/login');
|
|
}
|
|
});
|
|
|
|
app.get('/login', (req, res) => {
|
|
res.render('login');
|
|
});
|
|
|
|
app.post('/login', passport.authenticate('local', {
|
|
successRedirect: '/dashboard',
|
|
failureRedirect: '/login',
|
|
failureFlash: true
|
|
}));
|
|
|
|
app.get('/register', (req, res) => {
|
|
res.render('register');
|
|
});
|
|
|
|
app.post('/register', async (req, res) => {
|
|
const { username, password, name } = req.body;
|
|
try {
|
|
const existingUser = await db.User.findOne({ where: { username } });
|
|
if (existingUser) {
|
|
req.flash('error', 'Username already exists');
|
|
return res.redirect('/register');
|
|
}
|
|
await db.User.create({ username, password, name });
|
|
req.flash('success_msg', 'You are now registered and can log in');
|
|
res.redirect('/login');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error', 'Error registering user');
|
|
res.redirect('/register');
|
|
}
|
|
});
|
|
|
|
app.get('/logout', (req, res, next) => {
|
|
req.logout((err) => {
|
|
if (err) { return next(err); }
|
|
req.flash('success_msg', 'You are logged out');
|
|
res.redirect('/login');
|
|
});
|
|
});
|
|
|
|
app.get('/dashboard', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const today = new Date();
|
|
const dateStr = today.toISOString().split('T')[0];
|
|
|
|
// Get daily totals
|
|
const nutrition = await utils.calculateDailyTotals(req.user.id, dateStr);
|
|
const water = await utils.calculateWaterTotal(req.user.id, dateStr);
|
|
|
|
// Get user goals
|
|
let goals = await db.UserGoal.findOne({ where: { user_id: req.user.id } });
|
|
if (!goals) {
|
|
goals = await db.UserGoal.create({
|
|
user_id: req.user.id,
|
|
target_protein_g: 150,
|
|
target_carbs_g: 200,
|
|
target_fat_g: 60,
|
|
target_water_ml: 2000
|
|
});
|
|
}
|
|
|
|
// Get weight info
|
|
const weightLogToday = await db.WeightLog.findOne({
|
|
where: { user_id: req.user.id, date: dateStr }
|
|
});
|
|
|
|
const yesterday = new Date(today);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
const yesterdayStr = yesterday.toISOString().split('T')[0];
|
|
|
|
const weightLogYesterday = await db.WeightLog.findOne({
|
|
where: { user_id: req.user.id, date: yesterdayStr }
|
|
});
|
|
|
|
let weightChange = null;
|
|
if (weightLogToday && weightLogYesterday) {
|
|
weightChange = weightLogToday.weight_kg - weightLogYesterday.weight_kg;
|
|
}
|
|
|
|
// Calculate remaining
|
|
const remaining = {
|
|
calories: req.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
|
|
const macroPercentages = utils.getMacroPercentages(
|
|
nutrition.protein,
|
|
nutrition.carbs,
|
|
nutrition.fat
|
|
);
|
|
|
|
// Get trends
|
|
const weightTrend = await utils.getWeightTrend(req.user.id, 7);
|
|
const calorieTrend = await utils.getCalorieTrend(req.user.id, 7);
|
|
|
|
// Suggestions
|
|
const suggestions = utils.suggestFoodsForMacros(
|
|
remaining.protein,
|
|
remaining.carbs,
|
|
remaining.fat
|
|
);
|
|
|
|
res.render('dashboard', {
|
|
nutrition,
|
|
water,
|
|
goals,
|
|
remaining,
|
|
macro_percentages: macroPercentages,
|
|
weight_today: weightLogToday,
|
|
weight_change: weightChange,
|
|
weight_trend: weightTrend,
|
|
calorie_trend: calorieTrend,
|
|
suggestions,
|
|
today: dateStr
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error loading dashboard');
|
|
res.render('dashboard', {
|
|
nutrition: { calories: 0, protein: 0, carbs: 0, fat: 0 },
|
|
water: { total_ml: 0 },
|
|
goals: { target_water_ml: 2000 },
|
|
remaining: { calories: 2000, protein: 150, carbs: 200, fat: 60, water: 2000 },
|
|
macro_percentages: { protein: 0, carbs: 0, fat: 0 },
|
|
weight_trend: [],
|
|
calorie_trend: [],
|
|
suggestions: [],
|
|
today: new Date().toISOString().split('T')[0]
|
|
});
|
|
}
|
|
});
|
|
|
|
app.get('/add-meal', ensureAuthenticated, (req, res) => {
|
|
res.render('add_meal', { today: new Date().toISOString().split('T')[0] });
|
|
});
|
|
|
|
app.post('/add-meal', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
let { date, meal_type, time, 'food_id[]': foodIds, 'quantity[]': quantities } = req.body;
|
|
|
|
if (!foodIds) {
|
|
req.flash('error_msg', 'Please add at least one food item');
|
|
return res.redirect('/add-meal');
|
|
}
|
|
|
|
// Normalize input (handle single item vs array)
|
|
if (!Array.isArray(foodIds)) {
|
|
foodIds = [foodIds];
|
|
quantities = [quantities];
|
|
}
|
|
|
|
const meal = await db.Meal.create({
|
|
user_id: req.user.id,
|
|
date: date || new Date(),
|
|
meal_type,
|
|
time: time || null
|
|
});
|
|
|
|
for (let i = 0; i < foodIds.length; i++) {
|
|
const foodId = foodIds[i];
|
|
const quantity = quantities[i];
|
|
|
|
if (foodId && quantity) {
|
|
const food = await db.FoodItem.findByPk(foodId);
|
|
if (food) {
|
|
await db.MealFood.create({
|
|
MealId: meal.id,
|
|
FoodItemId: foodId,
|
|
quantity: quantity,
|
|
calories_consumed: food.calories * quantity,
|
|
protein_consumed: food.protein_g * quantity,
|
|
carbs_consumed: food.carbs_g * quantity,
|
|
fat_consumed: food.fat_g * quantity
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
await utils.updateDailySummary(req.user.id, date);
|
|
|
|
req.flash('success_msg', 'Meal added successfully!');
|
|
res.redirect('/dashboard');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error adding meal');
|
|
res.redirect('/add-meal');
|
|
}
|
|
});
|
|
|
|
app.get('/api/search-food', ensureAuthenticated, async (req, res) => {
|
|
const query = req.query.q || '';
|
|
if (query.length < 2) {
|
|
return res.json([]);
|
|
}
|
|
|
|
try {
|
|
const results = await searchAllSources(query, apiClient);
|
|
res.json(results);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
app.post('/api/add-food', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const data = req.body;
|
|
const food = await apiClient.saveFoodToDb(data);
|
|
|
|
if (food) {
|
|
res.json({
|
|
success: true,
|
|
food_id: food.id,
|
|
name: food.name
|
|
});
|
|
} else {
|
|
res.status(500).json({ success: false });
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ success: false });
|
|
}
|
|
});
|
|
|
|
app.post('/add-water', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const amountMl = parseInt(req.body.amount_ml) || 250;
|
|
const date = req.body.date || new Date().toISOString().split('T')[0];
|
|
|
|
await db.WaterLog.create({
|
|
user_id: req.user.id,
|
|
date: date,
|
|
amount_ml: amountMl,
|
|
time: new Date()
|
|
});
|
|
|
|
await utils.updateDailySummary(req.user.id, date);
|
|
|
|
req.flash('success_msg', `Added ${amountMl}ml of water!`);
|
|
res.redirect('/dashboard');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error adding water');
|
|
res.redirect('/dashboard');
|
|
}
|
|
});
|
|
|
|
app.post('/add-weight', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const weightKg = parseFloat(req.body.weight_kg);
|
|
const date = req.body.date || new Date().toISOString().split('T')[0];
|
|
|
|
let weightLog = await db.WeightLog.findOne({
|
|
where: { user_id: req.user.id, date: date }
|
|
});
|
|
|
|
if (weightLog) {
|
|
weightLog.weight_kg = weightKg;
|
|
weightLog.time = new Date();
|
|
await weightLog.save();
|
|
} else {
|
|
await db.WeightLog.create({
|
|
user_id: req.user.id,
|
|
date: date,
|
|
weight_kg: weightKg,
|
|
time: new Date()
|
|
});
|
|
}
|
|
|
|
await utils.updateDailySummary(req.user.id, date);
|
|
|
|
res.redirect('/dashboard');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error adding weight');
|
|
res.redirect('/dashboard');
|
|
}
|
|
});
|
|
|
|
app.get('/goals', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const userGoals = await db.UserGoal.findOne({ where: { user_id: req.user.id } });
|
|
|
|
let bmr = null;
|
|
let tdee = null;
|
|
|
|
if (req.user.weight_kg && req.user.height_cm && req.user.age) {
|
|
bmr = utils.calculateBMR(
|
|
req.user.weight_kg,
|
|
req.user.height_cm,
|
|
req.user.age,
|
|
req.user.gender || 'male'
|
|
);
|
|
tdee = utils.calculateTDEE(bmr, req.user.activity_level || 'moderate');
|
|
}
|
|
|
|
res.render('goals', {
|
|
goals: userGoals,
|
|
bmr,
|
|
tdee
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error loading goals');
|
|
res.redirect('/dashboard');
|
|
}
|
|
});
|
|
|
|
app.post('/goals', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
// Update user info
|
|
const user = await db.User.findByPk(req.user.id);
|
|
user.age = parseInt(req.body.age) || 25;
|
|
user.gender = req.body.gender || 'male';
|
|
user.height_cm = parseFloat(req.body.height_cm) || 170;
|
|
user.weight_kg = parseFloat(req.body.weight_kg) || 70;
|
|
user.activity_level = req.body.activity_level || 'moderate';
|
|
|
|
// Calculate targets
|
|
const bmr = utils.calculateBMR(user.weight_kg, user.height_cm, user.age, user.gender);
|
|
const tdee = utils.calculateTDEE(bmr, user.activity_level);
|
|
|
|
const goalType = req.body.goal_type || 'recomp';
|
|
let targetCalories;
|
|
|
|
if (goalType === 'weight_loss') {
|
|
targetCalories = tdee - 500;
|
|
} else if (goalType === 'muscle_gain') {
|
|
targetCalories = tdee + 300;
|
|
} else {
|
|
targetCalories = tdee;
|
|
}
|
|
|
|
user.target_daily_calories = Math.round(targetCalories);
|
|
await user.save();
|
|
|
|
// Update or create goals
|
|
let userGoals = await db.UserGoal.findOne({ where: { user_id: req.user.id } });
|
|
if (!userGoals) {
|
|
userGoals = await db.UserGoal.create({ user_id: req.user.id });
|
|
}
|
|
|
|
userGoals.goal_type = goalType;
|
|
userGoals.target_weight_kg = parseFloat(req.body.target_weight_kg) || 70;
|
|
|
|
// Calculate macros
|
|
const macros = utils.calculateMacroTargets(user.weight_kg, goalType);
|
|
userGoals.target_protein_g = macros.protein_g;
|
|
userGoals.target_carbs_g = macros.carbs_g;
|
|
userGoals.target_fat_g = macros.fat_g;
|
|
userGoals.target_water_ml = parseInt(req.body.target_water_ml) || 2000;
|
|
|
|
await userGoals.save();
|
|
|
|
req.flash('success_msg', 'Goals updated successfully!');
|
|
res.redirect('/goals');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error updating goals');
|
|
res.redirect('/goals');
|
|
}
|
|
});
|
|
|
|
app.get('/foods', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const category = req.query.category || 'all';
|
|
const searchQuery = req.query.q || '';
|
|
|
|
const whereClause = {};
|
|
|
|
if (category !== 'all') {
|
|
whereClause.category = category;
|
|
}
|
|
|
|
if (searchQuery) {
|
|
whereClause[db.Sequelize.Op.or] = [
|
|
{ name: { [db.Sequelize.Op.like]: `%${searchQuery}%` } },
|
|
{ name_tagalog: { [db.Sequelize.Op.like]: `%${searchQuery}%` } }
|
|
];
|
|
}
|
|
|
|
const filipinoWhere = { ...whereClause, is_filipino: true };
|
|
const otherWhere = { ...whereClause, is_filipino: false };
|
|
|
|
const filipinoFoods = await db.FoodItem.findAll({
|
|
where: filipinoWhere,
|
|
order: [['name', 'ASC']]
|
|
});
|
|
|
|
const otherFoods = await db.FoodItem.findAll({
|
|
where: otherWhere,
|
|
limit: 20,
|
|
order: [['name', 'ASC']]
|
|
});
|
|
|
|
const categories = ['all', 'kanin', 'ulam', 'sabaw', 'gulay', 'meryenda', 'almusal'];
|
|
|
|
res.render('foods', {
|
|
filipino_foods: filipinoFoods,
|
|
other_foods: otherFoods,
|
|
categories,
|
|
current_category: category,
|
|
search_query: searchQuery
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error loading foods');
|
|
res.redirect('/dashboard');
|
|
}
|
|
});
|
|
|
|
app.get('/progress', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const days = parseInt(req.query.days) || 30;
|
|
|
|
const weightTrend = await utils.getWeightTrend(req.user.id, days);
|
|
const calorieTrend = await utils.getCalorieTrend(req.user.id, days);
|
|
|
|
let avgCalories = 0, avgProtein = 0, avgCarbs = 0, avgFat = 0;
|
|
|
|
if (calorieTrend.length > 0) {
|
|
avgCalories = calorieTrend.reduce((sum, d) => sum + d.calories, 0) / calorieTrend.length;
|
|
avgProtein = calorieTrend.reduce((sum, d) => sum + d.protein, 0) / calorieTrend.length;
|
|
avgCarbs = calorieTrend.reduce((sum, d) => sum + d.carbs, 0) / calorieTrend.length;
|
|
avgFat = calorieTrend.reduce((sum, d) => sum + d.fat, 0) / calorieTrend.length;
|
|
}
|
|
|
|
let weightChange = null;
|
|
if (weightTrend.length >= 2) {
|
|
weightChange = weightTrend[weightTrend.length - 1].weight_kg - weightTrend[0].weight_kg;
|
|
}
|
|
|
|
res.render('progress', {
|
|
weight_trend: weightTrend,
|
|
calorie_trend: calorieTrend,
|
|
avg_calories: Math.round(avgCalories),
|
|
avg_protein: Math.round(avgProtein),
|
|
avg_carbs: Math.round(avgCarbs),
|
|
avg_fat: Math.round(avgFat),
|
|
weight_change: weightChange,
|
|
days
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error loading progress');
|
|
res.redirect('/dashboard');
|
|
}
|
|
});
|
|
|
|
app.get('/meal-planner', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const today = new Date();
|
|
// Calculate Monday of current week
|
|
const day = today.getDay();
|
|
const diff = today.getDate() - day + (day === 0 ? -6 : 1); // adjust when day is sunday
|
|
const startDate = new Date(today);
|
|
startDate.setDate(diff);
|
|
|
|
const dates = [];
|
|
for (let i = 0; i < 7; i++) {
|
|
const d = new Date(startDate);
|
|
d.setDate(startDate.getDate() + i);
|
|
dates.push(d);
|
|
}
|
|
|
|
const mealPlans = {};
|
|
|
|
for (const d of dates) {
|
|
const dateStr = d.toISOString().split('T')[0];
|
|
|
|
const plans = await db.MealPlan.findAll({
|
|
where: { user_id: req.user.id, date: dateStr },
|
|
include: [{
|
|
model: db.PlannedFood,
|
|
include: [db.FoodItem]
|
|
}]
|
|
});
|
|
|
|
mealPlans[dateStr] = plans.map(p => {
|
|
let calories = 0, protein = 0, carbs = 0, fat = 0;
|
|
const foods = [];
|
|
|
|
if (p.PlannedFoods) {
|
|
p.PlannedFoods.forEach(pf => {
|
|
if (pf.FoodItem) {
|
|
const q = pf.quantity || 1;
|
|
calories += pf.FoodItem.calories * q;
|
|
protein += pf.FoodItem.protein_g * q;
|
|
carbs += pf.FoodItem.carbs_g * q;
|
|
fat += pf.FoodItem.fat_g * q;
|
|
|
|
foods.push({
|
|
name: pf.FoodItem.name,
|
|
quantity: q
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
id: p.id,
|
|
meal_type: p.meal_type,
|
|
is_completed: p.is_completed,
|
|
foods: foods,
|
|
totals: {
|
|
calories, protein, carbs, fat
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
res.render('meal_planner', {
|
|
dates: dates.map(d => d.toISOString().split('T')[0]),
|
|
meal_plans: mealPlans,
|
|
today: new Date().toISOString().split('T')[0]
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error loading meal planner');
|
|
res.redirect('/dashboard');
|
|
}
|
|
});
|
|
|
|
app.post('/meal-planner/add', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
let { date, meal_type, 'food_id[]': foodIds, 'quantity[]': quantities } = req.body;
|
|
|
|
if (!foodIds) {
|
|
req.flash('error_msg', 'Please add at least one food item');
|
|
return res.redirect('/meal-planner');
|
|
}
|
|
|
|
// Normalize input
|
|
if (!Array.isArray(foodIds)) {
|
|
foodIds = [foodIds];
|
|
quantities = [quantities];
|
|
}
|
|
|
|
// Find or create meal plan
|
|
let mealPlan = await db.MealPlan.findOne({
|
|
where: {
|
|
user_id: req.user.id,
|
|
date: date,
|
|
meal_type: meal_type
|
|
}
|
|
});
|
|
|
|
if (!mealPlan) {
|
|
mealPlan = await db.MealPlan.create({
|
|
user_id: req.user.id,
|
|
date: date,
|
|
meal_type: meal_type
|
|
});
|
|
}
|
|
|
|
// Add foods
|
|
for (let i = 0; i < foodIds.length; i++) {
|
|
const foodId = foodIds[i];
|
|
const quantity = quantities[i];
|
|
|
|
if (foodId && quantity) {
|
|
await db.PlannedFood.create({
|
|
MealPlanId: mealPlan.id,
|
|
FoodItemId: foodId,
|
|
quantity: quantity
|
|
});
|
|
}
|
|
}
|
|
|
|
req.flash('success_msg', 'Meal plan updated!');
|
|
res.redirect('/meal-planner');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error adding meal plan');
|
|
res.redirect('/meal-planner');
|
|
}
|
|
});
|
|
|
|
app.post('/meal-planner/complete/:id', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
const plan = await db.MealPlan.findOne({
|
|
where: { id: req.params.id, user_id: req.user.id }
|
|
});
|
|
|
|
if (plan) {
|
|
plan.is_completed = !plan.is_completed;
|
|
await plan.save();
|
|
|
|
// If marked complete, optionally create actual meal log?
|
|
// For now just toggle status as per request simplicity.
|
|
}
|
|
|
|
res.redirect('/meal-planner');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error updating plan status');
|
|
res.redirect('/meal-planner');
|
|
}
|
|
});
|
|
|
|
app.post('/meal-planner/delete/:id', ensureAuthenticated, async (req, res) => {
|
|
try {
|
|
await db.MealPlan.destroy({
|
|
where: { id: req.params.id, user_id: req.user.id }
|
|
});
|
|
req.flash('success_msg', 'Meal plan deleted');
|
|
res.redirect('/meal-planner');
|
|
} catch (err) {
|
|
console.error(err);
|
|
req.flash('error_msg', 'Error deleting meal plan');
|
|
res.redirect('/meal-planner');
|
|
}
|
|
});
|
|
|
|
// Database Sync and Server Start
|
|
const startServer = async () => {
|
|
try {
|
|
await db.sequelize.sync();
|
|
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
|
|
} catch (err) {
|
|
console.error('Database connection error:', err);
|
|
}
|
|
};
|
|
|
|
startServer();
|