initial commit
This commit is contained in:
191
models/index.js
Normal file
191
models/index.js
Normal 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;
|
||||
Reference in New Issue
Block a user