158 lines
7.7 KiB
PL/PgSQL
158 lines
7.7 KiB
PL/PgSQL
-- DailyMeals diet tracking module
|
|
-- Apply manually on PostgreSQL (database-first; EF does not run migrations).
|
|
-- Schema: diet (same as existing recipe tables)
|
|
--
|
|
-- Inspired by patterns from MyFitnessPal / Lifesum (meal + macro logging),
|
|
-- WaterMinder / Hydro Coach (hydration goals + quick-add volumes), and
|
|
-- generic reminder schedules stored server-side for client push/local alarms.
|
|
|
|
BEGIN;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Daily nutrition & hydration targets (one row per user)
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS diet."UserDailyGoals" (
|
|
"UserId" uuid PRIMARY KEY
|
|
REFERENCES diet."AspNetUsers"("Id") ON DELETE CASCADE,
|
|
"CalorieGoal" int NULL CHECK ("CalorieGoal" IS NULL OR "CalorieGoal" >= 0),
|
|
"ProteinGoalG" numeric(6,2) NULL CHECK ("ProteinGoalG" IS NULL OR "ProteinGoalG" >= 0),
|
|
"FatGoalG" numeric(6,2) NULL CHECK ("FatGoalG" IS NULL OR "FatGoalG" >= 0),
|
|
"CarbsGoalG" numeric(6,2) NULL CHECK ("CarbsGoalG" IS NULL OR "CarbsGoalG" >= 0),
|
|
"WaterGoalMl" int NULL CHECK ("WaterGoalMl" IS NULL OR "WaterGoalMl" > 0),
|
|
"CreatedAt" timestamptz NOT NULL DEFAULT now(),
|
|
"UpdatedAt" timestamptz NULL
|
|
);
|
|
|
|
COMMENT ON TABLE diet."UserDailyGoals" IS 'Per-user daily calorie, macro, and hydration targets (Lifesum-style goals).';
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Drink catalog (reference data; quick-add presets like WaterMinder)
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS diet."DrinkCatalog" (
|
|
"Id" serial PRIMARY KEY,
|
|
"Code" varchar(32) NOT NULL UNIQUE,
|
|
"Name" varchar(100) NOT NULL,
|
|
"DefaultVolumeMl" int NOT NULL CHECK ("DefaultVolumeMl" > 0),
|
|
"CaloriesPer100Ml" numeric(6,2) NULL CHECK ("CaloriesPer100Ml" IS NULL OR "CaloriesPer100Ml" >= 0),
|
|
"IconEmoji" varchar(16) NULL,
|
|
"SortOrder" int NOT NULL DEFAULT 0,
|
|
"IsActive" boolean NOT NULL DEFAULT true
|
|
);
|
|
|
|
COMMENT ON TABLE diet."DrinkCatalog" IS 'Preset drinks for one-tap logging (water, tea, coffee, etc.).';
|
|
|
|
INSERT INTO diet."DrinkCatalog" ("Code", "Name", "DefaultVolumeMl", "CaloriesPer100Ml", "IconEmoji", "SortOrder")
|
|
VALUES
|
|
('water', 'Water', 250, 0, '💧', 1),
|
|
('tea', 'Tea', 200, 1, '🍵', 2),
|
|
('coffee', 'Coffee', 150, 2, '☕', 3),
|
|
('juice', 'Juice', 200, 45, '🧃', 4),
|
|
('milk', 'Milk', 250, 42, '🥛', 5),
|
|
('soda', 'Soft drink', 330, 42, '🥤', 6),
|
|
('other', 'Other drink', 250, NULL, '🍶', 99)
|
|
ON CONFLICT ("Code") DO NOTHING;
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Logged meals (MyFitnessPal-style diary entries; optional recipe link)
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS diet."MealConsumptions" (
|
|
"Id" serial PRIMARY KEY,
|
|
"UserId" uuid NOT NULL
|
|
REFERENCES diet."AspNetUsers"("Id") ON DELETE CASCADE,
|
|
"RecipeId" int NULL
|
|
REFERENCES diet."Recipes"("Id") ON DELETE SET NULL,
|
|
"MealCategory" int NOT NULL CHECK ("MealCategory" BETWEEN 0 AND 4),
|
|
"LogDate" date NOT NULL,
|
|
"ConsumedAt" timestamptz NOT NULL,
|
|
"Name" varchar(255) NOT NULL,
|
|
"Portions" numeric(4,2) NOT NULL DEFAULT 1 CHECK ("Portions" > 0),
|
|
"Calories" int NULL CHECK ("Calories" IS NULL OR "Calories" >= 0),
|
|
"ProteinG" numeric(6,2) NULL CHECK ("ProteinG" IS NULL OR "ProteinG" >= 0),
|
|
"FatG" numeric(6,2) NULL CHECK ("FatG" IS NULL OR "FatG" >= 0),
|
|
"CarbsG" numeric(6,2) NULL CHECK ("CarbsG" IS NULL OR "CarbsG" >= 0),
|
|
"Notes" text NULL,
|
|
"CreatedAt" timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
COMMENT ON TABLE diet."MealConsumptions" IS 'Meals the user ate; macros snapshotted at log time.';
|
|
COMMENT ON COLUMN diet."MealConsumptions"."MealCategory" IS '0=Breakfast, 1=SecondBreakfast, 2=Lunch, 3=Dinner, 4=Snack';
|
|
|
|
CREATE INDEX IF NOT EXISTS "IX_MealConsumptions_UserId_LogDate"
|
|
ON diet."MealConsumptions" ("UserId", "LogDate" DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS "IX_MealConsumptions_UserId_ConsumedAt"
|
|
ON diet."MealConsumptions" ("UserId", "ConsumedAt" DESC);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Logged drinks (water tracker entries)
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS diet."DrinkConsumptions" (
|
|
"Id" serial PRIMARY KEY,
|
|
"UserId" uuid NOT NULL
|
|
REFERENCES diet."AspNetUsers"("Id") ON DELETE CASCADE,
|
|
"DrinkCatalogId" int NULL
|
|
REFERENCES diet."DrinkCatalog"("Id") ON DELETE SET NULL,
|
|
"LogDate" date NOT NULL,
|
|
"ConsumedAt" timestamptz NOT NULL,
|
|
"Name" varchar(255) NOT NULL,
|
|
"VolumeMl" int NOT NULL CHECK ("VolumeMl" > 0),
|
|
"Calories" int NULL CHECK ("Calories" IS NULL OR "Calories" >= 0),
|
|
"CreatedAt" timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
COMMENT ON TABLE diet."DrinkConsumptions" IS 'Hydration and beverage intake log.';
|
|
|
|
CREATE INDEX IF NOT EXISTS "IX_DrinkConsumptions_UserId_LogDate"
|
|
ON diet."DrinkConsumptions" ("UserId", "LogDate" DESC);
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Reminders (server-stored schedules; clients handle local notifications)
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS diet."DietReminders" (
|
|
"Id" serial PRIMARY KEY,
|
|
"UserId" uuid NOT NULL
|
|
REFERENCES diet."AspNetUsers"("Id") ON DELETE CASCADE,
|
|
"ReminderType" int NOT NULL CHECK ("ReminderType" BETWEEN 0 AND 3),
|
|
"Title" varchar(120) NOT NULL,
|
|
"Message" varchar(500) NULL,
|
|
"TimeOfDay" time NOT NULL,
|
|
"DaysOfWeekMask" int NOT NULL DEFAULT 127 CHECK ("DaysOfWeekMask" BETWEEN 1 AND 127),
|
|
"MealCategory" int NULL CHECK ("MealCategory" IS NULL OR "MealCategory" BETWEEN 0 AND 4),
|
|
"IsEnabled" boolean NOT NULL DEFAULT true,
|
|
"CreatedAt" timestamptz NOT NULL DEFAULT now(),
|
|
"UpdatedAt" timestamptz NULL
|
|
);
|
|
|
|
COMMENT ON TABLE diet."DietReminders" IS 'Meal/water/custom reminder schedules (bitmask: Sun=1, Mon=2, Tue=4, Wed=8, Thu=16, Fri=32, Sat=64).';
|
|
COMMENT ON COLUMN diet."DietReminders"."ReminderType" IS '0=Water, 1=Meal, 2=Snack, 3=Custom';
|
|
|
|
CREATE INDEX IF NOT EXISTS "IX_DietReminders_UserId_IsEnabled"
|
|
ON diet."DietReminders" ("UserId", "IsEnabled");
|
|
|
|
-- ---------------------------------------------------------------------------
|
|
-- Optional views for reporting (API computes the same in LINQ)
|
|
-- ---------------------------------------------------------------------------
|
|
CREATE OR REPLACE VIEW diet."v_DailyMealTotals" AS
|
|
SELECT
|
|
"UserId",
|
|
"LogDate",
|
|
COALESCE(SUM("Calories"), 0)::int AS "TotalCalories",
|
|
COALESCE(SUM("ProteinG"), 0) AS "TotalProteinG",
|
|
COALESCE(SUM("FatG"), 0) AS "TotalFatG",
|
|
COALESCE(SUM("CarbsG"), 0) AS "TotalCarbsG",
|
|
COUNT(*)::int AS "MealCount"
|
|
FROM diet."MealConsumptions"
|
|
GROUP BY "UserId", "LogDate";
|
|
|
|
CREATE OR REPLACE VIEW diet."v_DailyDrinkTotals" AS
|
|
SELECT
|
|
"UserId",
|
|
"LogDate",
|
|
COALESCE(SUM("VolumeMl"), 0)::int AS "TotalWaterMl",
|
|
COALESCE(SUM("Calories"), 0)::int AS "TotalDrinkCalories",
|
|
COUNT(*)::int AS "DrinkCount"
|
|
FROM diet."DrinkConsumptions"
|
|
GROUP BY "UserId", "LogDate";
|
|
|
|
COMMIT;
|