- Add tsconfig.json for TypeScript compilation with declaration and source map generation - Generate .d.ts declaration files for all modules, services, controllers, and models - Update package.json with NestJS dependencies and TypeScript development tools - Include database files in the distribution output for persistence
385 lines
16 KiB
JavaScript
385 lines
16 KiB
JavaScript
"use strict";
|
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
};
|
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
};
|
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.UtilsService = void 0;
|
|
const common_1 = require("@nestjs/common");
|
|
const sequelize_1 = require("@nestjs/sequelize");
|
|
const sequelize_2 = require("sequelize");
|
|
const user_model_1 = require("../models/user.model");
|
|
const food_item_model_1 = require("../models/food-item.model");
|
|
const meal_model_1 = require("../models/meal.model");
|
|
const meal_food_model_1 = require("../models/meal-food.model");
|
|
const water_log_model_1 = require("../models/water-log.model");
|
|
const weight_log_model_1 = require("../models/weight-log.model");
|
|
const meal_plan_model_1 = require("../models/meal-plan.model");
|
|
const planned_food_model_1 = require("../models/planned-food.model");
|
|
const daily_summary_model_1 = require("../models/daily-summary.model");
|
|
let UtilsService = class UtilsService {
|
|
constructor(userModel, foodItemModel, mealModel, waterLogModel, weightLogModel, mealPlanModel, plannedFoodModel, dailySummaryModel) {
|
|
this.userModel = userModel;
|
|
this.foodItemModel = foodItemModel;
|
|
this.mealModel = mealModel;
|
|
this.waterLogModel = waterLogModel;
|
|
this.weightLogModel = weightLogModel;
|
|
this.mealPlanModel = mealPlanModel;
|
|
this.plannedFoodModel = plannedFoodModel;
|
|
this.dailySummaryModel = dailySummaryModel;
|
|
}
|
|
calculateBMR(weightKg, heightCm, age, gender) {
|
|
let bmr;
|
|
if (gender.toLowerCase() === 'male') {
|
|
bmr = 10 * weightKg + 6.25 * heightCm - 5 * age + 5;
|
|
}
|
|
else {
|
|
bmr = 10 * weightKg + 6.25 * heightCm - 5 * age - 161;
|
|
}
|
|
return Math.round(bmr);
|
|
}
|
|
calculateTDEE(bmr, activityLevel) {
|
|
const multipliers = {
|
|
sedentary: 1.2,
|
|
light: 1.375,
|
|
moderate: 1.55,
|
|
active: 1.725,
|
|
very_active: 1.9,
|
|
};
|
|
const multiplier = multipliers[activityLevel] || 1.55;
|
|
return Math.round(bmr * multiplier);
|
|
}
|
|
calculateMacroTargets(weightKg, goalType = 'recomp') {
|
|
let protein, carbs, fat;
|
|
if (goalType === 'muscle_gain') {
|
|
protein = weightKg * 2.4;
|
|
carbs = weightKg * 3.5;
|
|
fat = weightKg * 1.0;
|
|
}
|
|
else if (goalType === 'weight_loss') {
|
|
protein = weightKg * 2.2;
|
|
carbs = weightKg * 2.0;
|
|
fat = weightKg * 0.8;
|
|
}
|
|
else {
|
|
protein = weightKg * 2.2;
|
|
carbs = weightKg * 2.5;
|
|
fat = weightKg * 0.9;
|
|
}
|
|
return {
|
|
protein_g: Math.round(protein),
|
|
carbs_g: Math.round(carbs),
|
|
fat_g: Math.round(fat),
|
|
};
|
|
}
|
|
async calculateDailyTotals(userId, targetDate = new Date()) {
|
|
const dateStr = targetDate instanceof Date ? targetDate.toISOString().split('T')[0] : targetDate;
|
|
const meals = await this.mealModel.findAll({
|
|
where: {
|
|
UserId: userId,
|
|
date: dateStr,
|
|
},
|
|
include: [
|
|
{
|
|
model: meal_food_model_1.MealFood,
|
|
include: [food_item_model_1.FoodItem],
|
|
},
|
|
],
|
|
});
|
|
const totals = {
|
|
calories: 0,
|
|
protein: 0,
|
|
carbs: 0,
|
|
fat: 0,
|
|
meals: [],
|
|
};
|
|
for (const meal of meals) {
|
|
const mealTotals = {
|
|
calories: 0,
|
|
protein: 0,
|
|
carbs: 0,
|
|
fat: 0,
|
|
};
|
|
const mealFoods = [];
|
|
for (const mf of meal.mealFoods) {
|
|
const quantity = mf.quantity || 1.0;
|
|
const food = mf.foodItem;
|
|
const calories = mf.calories_consumed || food.calories * quantity;
|
|
const protein = mf.protein_consumed || food.protein_g * quantity;
|
|
const carbs = mf.carbs_consumed || food.carbs_g * quantity;
|
|
const fat = mf.fat_consumed || food.fat_g * quantity;
|
|
mealTotals.calories += calories;
|
|
mealTotals.protein += protein;
|
|
mealTotals.carbs += carbs;
|
|
mealTotals.fat += fat;
|
|
mealFoods.push({
|
|
name: food.name,
|
|
quantity: quantity,
|
|
calories: calories,
|
|
});
|
|
}
|
|
totals.calories += mealTotals.calories;
|
|
totals.protein += mealTotals.protein;
|
|
totals.carbs += mealTotals.carbs;
|
|
totals.fat += mealTotals.fat;
|
|
totals.meals.push({
|
|
id: meal.id,
|
|
type: meal.meal_type,
|
|
time: meal.time ? meal.time.substring(0, 5) : null,
|
|
totals: mealTotals,
|
|
foods: mealFoods,
|
|
});
|
|
}
|
|
return totals;
|
|
}
|
|
async calculateWaterTotal(userId, targetDate = new Date()) {
|
|
const dateStr = targetDate instanceof Date ? targetDate.toISOString().split('T')[0] : targetDate;
|
|
const waterLogs = await this.waterLogModel.findAll({
|
|
where: {
|
|
UserId: userId,
|
|
date: dateStr,
|
|
},
|
|
});
|
|
const total = waterLogs.reduce((sum, log) => sum + log.amount_ml, 0);
|
|
return {
|
|
total_ml: total,
|
|
logs: waterLogs.map((log) => ({
|
|
id: log.id,
|
|
amount_ml: log.amount_ml,
|
|
time: log.time ? log.time.substring(0, 5) : null,
|
|
})),
|
|
};
|
|
}
|
|
async getWeightTrend(userId, days = 7) {
|
|
const endDate = new Date();
|
|
const startDate = new Date();
|
|
startDate.setDate(endDate.getDate() - (days - 1));
|
|
const startDateStr = startDate.toISOString().split('T')[0];
|
|
const endDateStr = endDate.toISOString().split('T')[0];
|
|
const weightLogs = await this.weightLogModel.findAll({
|
|
where: {
|
|
UserId: userId,
|
|
date: {
|
|
[sequelize_2.Op.between]: [startDateStr, endDateStr],
|
|
},
|
|
},
|
|
order: [['date', 'ASC']],
|
|
});
|
|
return weightLogs.map((log) => ({
|
|
date: log.date,
|
|
weight_kg: log.weight_kg,
|
|
}));
|
|
}
|
|
async getCalorieTrend(userId, days = 7) {
|
|
const endDate = new Date();
|
|
const startDate = new Date();
|
|
startDate.setDate(endDate.getDate() - (days - 1));
|
|
const trend = [];
|
|
const currentDate = new Date(startDate);
|
|
while (currentDate <= endDate) {
|
|
const dateStr = currentDate.toISOString().split('T')[0];
|
|
const totals = await this.calculateDailyTotals(userId, dateStr);
|
|
trend.push({
|
|
date: dateStr,
|
|
calories: Math.round(totals.calories),
|
|
protein: Math.round(totals.protein),
|
|
carbs: Math.round(totals.carbs),
|
|
fat: Math.round(totals.fat),
|
|
});
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
return trend;
|
|
}
|
|
async updateDailySummary(userId, targetDate = new Date()) {
|
|
const dateStr = targetDate instanceof Date ? targetDate.toISOString().split('T')[0] : targetDate;
|
|
const nutrition = await this.calculateDailyTotals(userId, dateStr);
|
|
const water = await this.calculateWaterTotal(userId, dateStr);
|
|
const weightLog = await this.weightLogModel.findOne({
|
|
where: {
|
|
UserId: userId,
|
|
date: dateStr,
|
|
},
|
|
});
|
|
const weight = weightLog ? weightLog.weight_kg : null;
|
|
const user = await this.userModel.findByPk(userId);
|
|
const targetCalories = user ? user.target_daily_calories : 2000;
|
|
let summary = await this.dailySummaryModel.findOne({
|
|
where: {
|
|
UserId: userId,
|
|
date: dateStr,
|
|
},
|
|
});
|
|
if (!summary) {
|
|
summary = await this.dailySummaryModel.create({
|
|
UserId: userId,
|
|
date: dateStr,
|
|
});
|
|
}
|
|
summary.total_calories = nutrition.calories;
|
|
summary.total_protein_g = nutrition.protein;
|
|
summary.total_carbs_g = nutrition.carbs;
|
|
summary.total_fat_g = nutrition.fat;
|
|
summary.total_water_ml = water.total_ml;
|
|
summary.calories_remaining = targetCalories - nutrition.calories;
|
|
summary.weight_kg = weight;
|
|
try {
|
|
await summary.save();
|
|
return summary;
|
|
}
|
|
catch (e) {
|
|
console.error(`Error updating daily summary: ${e}`);
|
|
return null;
|
|
}
|
|
}
|
|
getMacroPercentages(proteinG, carbsG, fatG) {
|
|
const proteinCal = proteinG * 4;
|
|
const carbsCal = carbsG * 4;
|
|
const fatCal = fatG * 9;
|
|
const totalCal = proteinCal + carbsCal + fatCal;
|
|
if (totalCal === 0) {
|
|
return { protein: 0, carbs: 0, fat: 0 };
|
|
}
|
|
return {
|
|
protein: Math.round((proteinCal / totalCal) * 100),
|
|
carbs: Math.round((carbsCal / totalCal) * 100),
|
|
fat: Math.round((fatCal / totalCal) * 100),
|
|
};
|
|
}
|
|
suggestFoodsForMacros(remainingProtein, remainingCarbs, remainingFat) {
|
|
const suggestions = [];
|
|
if (remainingProtein > 30) {
|
|
suggestions.push({
|
|
category: 'High Protein Ulam',
|
|
examples: ['Grilled Tilapia', 'Chicken Tinola', 'Grilled Chicken'],
|
|
});
|
|
}
|
|
if (remainingCarbs > 40) {
|
|
suggestions.push({
|
|
category: 'Carbs',
|
|
examples: ['White Rice', 'Pandesal', 'Sweet Potato'],
|
|
});
|
|
}
|
|
if (remainingFat > 20) {
|
|
suggestions.push({
|
|
category: 'Healthy Fats',
|
|
examples: ['Sisig', 'Lechon Kawali', 'Bicol Express'],
|
|
});
|
|
}
|
|
if (remainingProtein > 20 && remainingCarbs > 30) {
|
|
suggestions.push({
|
|
category: 'Balanced Meals',
|
|
examples: ['Tapsilog', 'Chicken Adobo with Rice', 'Sinigang'],
|
|
});
|
|
}
|
|
return suggestions;
|
|
}
|
|
async generateDailyMealPlan(userId, dateStr) {
|
|
try {
|
|
const user = await this.userModel.findByPk(userId);
|
|
const targetCalories = user ? user.target_daily_calories : 2000;
|
|
const targets = {
|
|
breakfast: Math.round(targetCalories * 0.25),
|
|
lunch: Math.round(targetCalories * 0.35),
|
|
dinner: Math.round(targetCalories * 0.3),
|
|
snack: Math.round(targetCalories * 0.1),
|
|
};
|
|
const foods = await this.foodItemModel.findAll();
|
|
const foodByCat = {
|
|
almusal: foods.filter((f) => f.category === 'almusal'),
|
|
ulam: foods.filter((f) => ['ulam', 'sabaw'].includes(f.category)),
|
|
kanin: foods.filter((f) => f.category === 'kanin'),
|
|
gulay: foods.filter((f) => f.category === 'gulay'),
|
|
meryenda: foods.filter((f) => f.category === 'meryenda'),
|
|
};
|
|
const allFoods = foods;
|
|
const pickRandom = (arr) => (arr.length > 0 ? arr[Math.floor(Math.random() * arr.length)] : null);
|
|
const generateMeal = async (mealType, targetCal) => {
|
|
let currentCal = 0;
|
|
const selectedFoods = [];
|
|
if (mealType === 'breakfast') {
|
|
const main = pickRandom(foodByCat.almusal) || pickRandom(allFoods);
|
|
if (main) {
|
|
selectedFoods.push({ food: main, qty: 1 });
|
|
currentCal += main.calories;
|
|
}
|
|
}
|
|
else if (['lunch', 'dinner'].includes(mealType)) {
|
|
const rice = pickRandom(foodByCat.kanin);
|
|
const ulam = pickRandom(foodByCat.ulam) || pickRandom(allFoods);
|
|
const gulay = pickRandom(foodByCat.gulay);
|
|
if (rice) {
|
|
selectedFoods.push({ food: rice, qty: 1 });
|
|
currentCal += rice.calories;
|
|
}
|
|
if (ulam) {
|
|
selectedFoods.push({ food: ulam, qty: 1 });
|
|
currentCal += ulam.calories;
|
|
}
|
|
if (gulay && currentCal < targetCal) {
|
|
selectedFoods.push({ food: gulay, qty: 1 });
|
|
currentCal += gulay.calories;
|
|
}
|
|
}
|
|
else if (mealType === 'snack') {
|
|
const snack = pickRandom(foodByCat.meryenda) || pickRandom(allFoods);
|
|
if (snack) {
|
|
selectedFoods.push({ food: snack, qty: 1 });
|
|
currentCal += snack.calories;
|
|
}
|
|
}
|
|
if (currentCal > 0 && selectedFoods.length > 0) {
|
|
const mealPlan = await this.mealPlanModel.create({
|
|
UserId: userId,
|
|
date: dateStr,
|
|
meal_type: mealType,
|
|
is_completed: false,
|
|
});
|
|
for (const item of selectedFoods) {
|
|
await this.plannedFoodModel.create({
|
|
MealPlanId: mealPlan.id,
|
|
FoodItemId: item.food.id,
|
|
quantity: item.qty,
|
|
});
|
|
}
|
|
}
|
|
};
|
|
await this.mealPlanModel.destroy({
|
|
where: {
|
|
UserId: userId,
|
|
date: dateStr,
|
|
},
|
|
});
|
|
await generateMeal('breakfast', targets.breakfast);
|
|
await generateMeal('lunch', targets.lunch);
|
|
await generateMeal('dinner', targets.dinner);
|
|
await generateMeal('snack', targets.snack);
|
|
return true;
|
|
}
|
|
catch (error) {
|
|
console.error('Error generating meal plan:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
exports.UtilsService = UtilsService;
|
|
exports.UtilsService = UtilsService = __decorate([
|
|
(0, common_1.Injectable)(),
|
|
__param(0, (0, sequelize_1.InjectModel)(user_model_1.User)),
|
|
__param(1, (0, sequelize_1.InjectModel)(food_item_model_1.FoodItem)),
|
|
__param(2, (0, sequelize_1.InjectModel)(meal_model_1.Meal)),
|
|
__param(3, (0, sequelize_1.InjectModel)(water_log_model_1.WaterLog)),
|
|
__param(4, (0, sequelize_1.InjectModel)(weight_log_model_1.WeightLog)),
|
|
__param(5, (0, sequelize_1.InjectModel)(meal_plan_model_1.MealPlan)),
|
|
__param(6, (0, sequelize_1.InjectModel)(planned_food_model_1.PlannedFood)),
|
|
__param(7, (0, sequelize_1.InjectModel)(daily_summary_model_1.DailySummary)),
|
|
__metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object, Object])
|
|
], UtilsService);
|
|
//# sourceMappingURL=utils.service.js.map
|