initial commit

This commit is contained in:
Jp
2026-01-30 23:32:43 +08:00
commit 3df16ee995
20 changed files with 6405 additions and 0 deletions

191
models/index.js Normal file
View File

@@ -0,0 +1,191 @@
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;