const Sequelize = require('sequelize'); const path = require('path'); const bcrypt = require('bcryptjs'); // Initialize database const dbPath = process.env.DATABASE_URL || 'sqlite://calorie_tracker.db'; const storagePath = dbPath.startsWith('sqlite://') ? dbPath.replace('sqlite://', '') : path.join(__dirname, '../data/calorie_tracker.db'); // Ensure storage path is absolute or relative to cwd correctly // For Docker, we use /app/data/calorie_tracker.db const sequelize = new Sequelize({ dialect: 'sqlite', storage: storagePath.startsWith('/') ? storagePath : path.join(process.cwd(), 'data', 'calorie_tracker.db'), logging: false }); const db = {}; // User Model const User = sequelize.define('User', { username: { type: Sequelize.STRING(80), unique: true, allowNull: false }, password: { type: Sequelize.STRING(200), allowNull: false }, name: Sequelize.STRING(100), age: Sequelize.INTEGER, gender: Sequelize.STRING(10), height_cm: Sequelize.FLOAT, weight_kg: Sequelize.FLOAT, activity_level: { type: Sequelize.STRING(20), defaultValue: 'moderate' }, target_daily_calories: { type: Sequelize.INTEGER, defaultValue: 2000 } }); // Method to verify password User.prototype.validPassword = function(password) { return bcrypt.compareSync(password, this.password); }; // Hook to hash password User.beforeCreate(async (user) => { if (user.password) { user.password = await bcrypt.hash(user.password, 10); } }); // FoodItem Model const FoodItem = sequelize.define('FoodItem', { name: { type: Sequelize.STRING(200), allowNull: false }, name_tagalog: Sequelize.STRING(200), category: Sequelize.STRING(50), calories: { type: Sequelize.FLOAT, allowNull: false }, protein_g: { type: Sequelize.FLOAT, defaultValue: 0 }, carbs_g: { type: Sequelize.FLOAT, defaultValue: 0 }, fat_g: { type: Sequelize.FLOAT, defaultValue: 0 }, fiber_g: { type: Sequelize.FLOAT, defaultValue: 0 }, sugar_g: { type: Sequelize.FLOAT, defaultValue: 0 }, sodium_mg: { type: Sequelize.FLOAT, defaultValue: 0 }, serving_size_g: { type: Sequelize.FLOAT, defaultValue: 100 }, serving_description: Sequelize.STRING(100), source: { type: Sequelize.STRING(20), defaultValue: 'manual' }, is_filipino: { type: Sequelize.BOOLEAN, defaultValue: false }, is_favorite: { type: Sequelize.BOOLEAN, defaultValue: false }, api_data: Sequelize.TEXT, last_updated: { type: Sequelize.DATE, defaultValue: Sequelize.NOW } }); // Meal Model const Meal = sequelize.define('Meal', { date: { type: Sequelize.DATEONLY, allowNull: false, defaultValue: Sequelize.NOW }, meal_type: { type: Sequelize.STRING(20), allowNull: false }, time: Sequelize.TIME, notes: Sequelize.TEXT }); // MealFood Model const MealFood = sequelize.define('MealFood', { quantity: { type: Sequelize.FLOAT, allowNull: false, defaultValue: 1.0 }, quantity_grams: Sequelize.FLOAT, calories_consumed: Sequelize.FLOAT, protein_consumed: Sequelize.FLOAT, carbs_consumed: Sequelize.FLOAT, fat_consumed: Sequelize.FLOAT }); // WaterLog Model const WaterLog = sequelize.define('WaterLog', { date: { type: Sequelize.DATEONLY, allowNull: false, defaultValue: Sequelize.NOW }, amount_ml: { type: Sequelize.INTEGER, allowNull: false }, time: { type: Sequelize.TIME, defaultValue: Sequelize.NOW } }); // WeightLog Model const WeightLog = sequelize.define('WeightLog', { date: { type: Sequelize.DATEONLY, allowNull: false, defaultValue: Sequelize.NOW }, weight_kg: { type: Sequelize.FLOAT, allowNull: false }, body_fat_percentage: Sequelize.FLOAT, notes: Sequelize.TEXT, time: { type: Sequelize.TIME, defaultValue: Sequelize.NOW } }); // MealPlan Model const MealPlan = sequelize.define('MealPlan', { date: { type: Sequelize.DATEONLY, allowNull: false }, meal_type: { type: Sequelize.STRING(20), allowNull: false }, is_completed: { type: Sequelize.BOOLEAN, defaultValue: false }, notes: Sequelize.TEXT }); // PlannedFood Model const PlannedFood = sequelize.define('PlannedFood', { quantity: { type: Sequelize.FLOAT, allowNull: false, defaultValue: 1.0 } }); // UserGoal Model const UserGoal = sequelize.define('UserGoal', { goal_type: { type: Sequelize.STRING(20), defaultValue: 'recomp' }, target_weight_kg: Sequelize.FLOAT, weekly_goal_kg: { type: Sequelize.FLOAT, defaultValue: 0.5 }, target_protein_g: { type: Sequelize.INTEGER, defaultValue: 150 }, target_carbs_g: { type: Sequelize.INTEGER, defaultValue: 200 }, target_fat_g: { type: Sequelize.INTEGER, defaultValue: 60 }, target_water_ml: { type: Sequelize.INTEGER, defaultValue: 2000 } }); // DailySummary Model const DailySummary = sequelize.define('DailySummary', { date: { type: Sequelize.DATEONLY, allowNull: false }, total_calories: { type: Sequelize.FLOAT, defaultValue: 0 }, total_protein_g: { type: Sequelize.FLOAT, defaultValue: 0 }, total_carbs_g: { type: Sequelize.FLOAT, defaultValue: 0 }, total_fat_g: { type: Sequelize.FLOAT, defaultValue: 0 }, total_water_ml: { type: Sequelize.INTEGER, defaultValue: 0 }, calories_remaining: Sequelize.FLOAT, weight_kg: Sequelize.FLOAT, notes: Sequelize.TEXT }); // APICache Model const APICache = sequelize.define('APICache', { query: { type: Sequelize.STRING(200), allowNull: false, unique: true }, api_source: Sequelize.STRING(50), response_json: Sequelize.TEXT, cached_at: { type: Sequelize.DATE, defaultValue: Sequelize.NOW } }); // Relationships User.hasMany(Meal, { onDelete: 'CASCADE' }); Meal.belongsTo(User); User.hasMany(WeightLog, { onDelete: 'CASCADE' }); WeightLog.belongsTo(User); User.hasMany(WaterLog, { onDelete: 'CASCADE' }); WaterLog.belongsTo(User); User.hasMany(MealPlan, { onDelete: 'CASCADE' }); MealPlan.belongsTo(User); User.hasOne(UserGoal, { onDelete: 'CASCADE' }); UserGoal.belongsTo(User); Meal.hasMany(MealFood, { onDelete: 'CASCADE' }); MealFood.belongsTo(Meal); FoodItem.hasMany(MealFood); MealFood.belongsTo(FoodItem); MealPlan.hasMany(PlannedFood, { onDelete: 'CASCADE' }); PlannedFood.belongsTo(MealPlan); FoodItem.hasMany(PlannedFood); PlannedFood.belongsTo(FoodItem); User.hasMany(DailySummary, { onDelete: 'CASCADE' }); DailySummary.belongsTo(User); db.sequelize = sequelize; db.Sequelize = Sequelize; db.User = User; db.FoodItem = FoodItem; db.Meal = Meal; db.MealFood = MealFood; db.WaterLog = WaterLog; db.WeightLog = WeightLog; db.MealPlan = MealPlan; db.PlannedFood = PlannedFood; db.UserGoal = UserGoal; db.DailySummary = DailySummary; db.APICache = APICache; module.exports = db;