Files
DailyMeals/database/scripts/001_diet_tracking.sql
Piotr Kus f15bb7a916 * Extended functionalities
* Added different UIs
2026-06-24 21:43:40 +02:00

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;