Repository: glucopilot/GlucoPilot
Pull request #61: feat: Add meal, ingredient and reading.
Author: login: Loko3610
Created: 2025-04-04T15:27:22Z | Merged: 2025-04-04T22:03:24Z



Diff:
diff --git a/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/20250404212136_MealsIngredients.Designer.cs b/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/20250404212136_MealsIngredients.Designer.cs
new file mode 100644
index 00000000..5119dcf3
--- /dev/null
+++ b/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/20250404212136_MealsIngredients.Designer.cs
@@ -0,0 +1,239 @@
+﻿// <auto-generated />
+using System;
+using GlucoPilot.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace GlucoPilot.Data.Migrators.MSSQL.Migrations
+{
+    [DbContext(typeof(GlucoPilotDbContext))]
+    [Migration("20250404212136_MealsIngredients")]
+    partial class MealsIngredients
+    {
+        /// <inheritdoc />
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "9.0.3")
+                .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+            SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Ingredient", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<int>("Calories")
+                        .HasColumnType("int");
+
+                    b.Property<int>("Carbs")
+                        .HasColumnType("int");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("datetimeoffset");
+
+                    b.Property<int>("Fat")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int>("Protein")
+                        .HasColumnType("int");
+
+                    b.Property<int>("Uom")
+                        .HasColumnType("int");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("ingredients");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Meal", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("datetimeoffset");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("meals");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.MealIngredient", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<Guid>("IngredientId")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<Guid>("MealId")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<int>("Quantity")
+                        .HasColumnType("int");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("IngredientId");
+
+                    b.HasIndex("MealId");
+
+                    b.ToTable("meals_ingredients");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Reading", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("datetimeoffset");
+
+                    b.Property<int>("Direction")
+                        .HasColumnType("int");
+
+                    b.Property<double>("GlucoseLevel")
+                        .HasColumnType("float");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("readings");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.User", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<bool>("AcceptedTerms")
+                        .HasColumnType("bit");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("datetimeoffset");
+
+                    b.Property<string>("Discriminator")
+                        .IsRequired()
+                        .HasMaxLength(8)
+                        .HasColumnType("nvarchar(8)");
+
+                    b.Property<string>("Email")
+                        .IsRequired()
+                        .HasMaxLength(320)
+                        .HasColumnType("nvarchar(320)");
+
+                    b.Property<string>("PasswordHash")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<DateTimeOffset?>("Updated")
+                        .HasColumnType("datetimeoffset");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("users");
+
+                    b.HasDiscriminator().HasValue("User");
+
+                    b.UseTphMappingStrategy();
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Patient", b =>
+                {
+                    b.HasBaseType("GlucoPilot.Data.Entities.User");
+
+                    b.Property<Guid?>("UserId")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("users");
+
+                    b.HasDiscriminator().HasValue("Patient");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.MealIngredient", b =>
+                {
+                    b.HasOne("GlucoPilot.Data.Entities.Ingredient", "Ingredient")
+                        .WithMany("Meals")
+                        .HasForeignKey("IngredientId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("GlucoPilot.Data.Entities.Meal", "Meal")
+                        .WithMany("MealIngredients")
+                        .HasForeignKey("MealId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Ingredient");
+
+                    b.Navigation("Meal");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Patient", b =>
+                {
+                    b.HasOne("GlucoPilot.Data.Entities.User", null)
+                        .WithMany("Patients")
+                        .HasForeignKey("UserId");
+
+                    b.OwnsOne("GlucoPilot.Data.Entities.AuthTicket", "AuthTicket", b1 =>
+                        {
+                            b1.Property<Guid>("PatientId")
+                                .HasColumnType("uniqueidentifier");
+
+                            b1.Property<long>("Duration")
+                                .HasColumnType("bigint");
+
+                            b1.Property<long>("Expires")
+                                .HasColumnType("bigint");
+
+                            b1.Property<string>("Token")
+                                .IsRequired()
+                                .HasColumnType("nvarchar(max)");
+
+                            b1.HasKey("PatientId");
+
+                            b1.ToTable("users");
+
+                            b1.WithOwner()
+                                .HasForeignKey("PatientId");
+                        });
+
+                    b.Navigation("AuthTicket");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Ingredient", b =>
+                {
+                    b.Navigation("Meals");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Meal", b =>
+                {
+                    b.Navigation("MealIngredients");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.User", b =>
+                {
+                    b.Navigation("Patients");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/20250404212136_MealsIngredients.cs b/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/20250404212136_MealsIngredients.cs
new file mode 100644
index 00000000..cdd4b826
--- /dev/null
+++ b/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/20250404212136_MealsIngredients.cs
@@ -0,0 +1,112 @@
+﻿using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace GlucoPilot.Data.Migrators.MSSQL.Migrations
+{
+    /// <inheritdoc />
+    public partial class MealsIngredients : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.CreateTable(
+                name: "ingredients",
+                columns: table => new
+                {
+                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
+                    Created = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
+                    Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
+                    Carbs = table.Column<int>(type: "int", nullable: false),
+                    Protein = table.Column<int>(type: "int", nullable: false),
+                    Fat = table.Column<int>(type: "int", nullable: false),
+                    Calories = table.Column<int>(type: "int", nullable: false),
+                    Uom = table.Column<int>(type: "int", nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_ingredients", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "meals",
+                columns: table => new
+                {
+                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
+                    Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
+                    Created = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_meals", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "readings",
+                columns: table => new
+                {
+                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
+                    Created = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
+                    GlucoseLevel = table.Column<double>(type: "float", nullable: false),
+                    Direction = table.Column<int>(type: "int", nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_readings", x => x.Id);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "meals_ingredients",
+                columns: table => new
+                {
+                    Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
+                    MealId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
+                    IngredientId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
+                    Quantity = table.Column<int>(type: "int", nullable: false)
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_meals_ingredients", x => x.Id);
+                    table.ForeignKey(
+                        name: "FK_meals_ingredients_ingredients_IngredientId",
+                        column: x => x.IngredientId,
+                        principalTable: "ingredients",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Cascade);
+                    table.ForeignKey(
+                        name: "FK_meals_ingredients_meals_MealId",
+                        column: x => x.MealId,
+                        principalTable: "meals",
+                        principalColumn: "Id",
+                        onDelete: ReferentialAction.Cascade);
+                });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_meals_ingredients_IngredientId",
+                table: "meals_ingredients",
+                column: "IngredientId");
+
+            migrationBuilder.CreateIndex(
+                name: "IX_meals_ingredients_MealId",
+                table: "meals_ingredients",
+                column: "MealId");
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropTable(
+                name: "meals_ingredients");
+
+            migrationBuilder.DropTable(
+                name: "readings");
+
+            migrationBuilder.DropTable(
+                name: "ingredients");
+
+            migrationBuilder.DropTable(
+                name: "meals");
+        }
+    }
+}
diff --git a/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/GlucoPilotDbContextModelSnapshot.cs b/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/GlucoPilotDbContextModelSnapshot.cs
index fda17ff0..98a1ec9c 100644
--- a/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/GlucoPilotDbContextModelSnapshot.cs
+++ b/src/GlucoPilot.Data.Migrators.MSSQL/Migrations/GlucoPilotDbContextModelSnapshot.cs
@@ -22,6 +22,98 @@ protected override void BuildModel(ModelBuilder modelBuilder)
 
             SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
 
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Ingredient", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<int>("Calories")
+                        .HasColumnType("int");
+
+                    b.Property<int>("Carbs")
+                        .HasColumnType("int");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("datetimeoffset");
+
+                    b.Property<int>("Fat")
+                        .HasColumnType("int");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.Property<int>("Protein")
+                        .HasColumnType("int");
+
+                    b.Property<int>("Uom")
+                        .HasColumnType("int");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("ingredients");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Meal", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("datetimeoffset");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("nvarchar(max)");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("meals");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.MealIngredient", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<Guid>("IngredientId")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<Guid>("MealId")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<int>("Quantity")
+                        .HasColumnType("int");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("IngredientId");
+
+                    b.HasIndex("MealId");
+
+                    b.ToTable("meals_ingredients");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Reading", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .HasColumnType("uniqueidentifier");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("datetimeoffset");
+
+                    b.Property<int>("Direction")
+                        .HasColumnType("int");
+
+                    b.Property<double>("GlucoseLevel")
+                        .HasColumnType("float");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("readings");
+                });
+
             modelBuilder.Entity("GlucoPilot.Data.Entities.User", b =>
                 {
                     b.Property<Guid>("Id")
@@ -73,6 +165,25 @@ protected override void BuildModel(ModelBuilder modelBuilder)
                     b.HasDiscriminator().HasValue("Patient");
                 });
 
+            modelBuilder.Entity("GlucoPilot.Data.Entities.MealIngredient", b =>
+                {
+                    b.HasOne("GlucoPilot.Data.Entities.Ingredient", "Ingredient")
+                        .WithMany("Meals")
+                        .HasForeignKey("IngredientId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("GlucoPilot.Data.Entities.Meal", "Meal")
+                        .WithMany("MealIngredients")
+                        .HasForeignKey("MealId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Ingredient");
+
+                    b.Navigation("Meal");
+                });
+
             modelBuilder.Entity("GlucoPilot.Data.Entities.Patient", b =>
                 {
                     b.HasOne("GlucoPilot.Data.Entities.User", null)
@@ -105,6 +216,16 @@ protected override void BuildModel(ModelBuilder modelBuilder)
                     b.Navigation("AuthTicket");
                 });
 
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Ingredient", b =>
+                {
+                    b.Navigation("Meals");
+                });
+
+            modelBuilder.Entity("GlucoPilot.Data.Entities.Meal", b =>
+                {
+                    b.Navigation("MealIngredients");
+                });
+
             modelBuilder.Entity("GlucoPilot.Data.Entities.User", b =>
                 {
                     b.Navigation("Patients");
diff --git a/src/GlucoPilot.Data.Tests/GlucoPilotDbContextTests.cs b/src/GlucoPilot.Data.Tests/GlucoPilotDbContextTests.cs
index 32ce3ac9..7cb80b56 100644
--- a/src/GlucoPilot.Data.Tests/GlucoPilotDbContextTests.cs
+++ b/src/GlucoPilot.Data.Tests/GlucoPilotDbContextTests.cs
@@ -1,3 +1,5 @@
+using GlucoPilot.Data.Entities;
+using GlucoPilot.Data.Enums;
 using Microsoft.Data.Sqlite;
 using Microsoft.EntityFrameworkCore;
 using NUnit.Framework;
@@ -10,8 +12,8 @@ internal sealed class GlucoPilotDbContextTests
     private SqliteConnection _connection;
     private GlucoPilotDbContext _dbContext;
 
-    [Test]
-    public void Context_Can_Be_Created()
+    [SetUp]
+    public void SetUp()
     {
         _connection = new SqliteConnection("DataSource=:memory:");
         _connection.Open();
@@ -22,10 +24,174 @@ public void Context_Can_Be_Created()
         _dbContext.Database.EnsureCreated();
     }
 
+    [Test]
+    public void Context_Can_Be_Created()
+    {
+        Assert.That(_dbContext, Is.Not.Null);
+    }
+
     [TearDown]
     public void TearDown()
     {
         _connection?.Dispose();
         _dbContext?.Dispose();
     }
+
+    [Test]
+    public void Can_Add_And_Retrieve_Reading()
+    {
+        var reading = new Reading
+        {
+            Id = Guid.NewGuid(),
+            Created = DateTimeOffset.UtcNow,
+            GlucoseLevel = 120.5,
+            Direction = ReadingDirection.Steady,
+        };
+
+        _dbContext.Readings.Add(reading);
+        _dbContext.SaveChanges();
+
+        var retrievedReading = _dbContext.Readings.Find(reading.Id);
+        Assert.Multiple(() =>
+        {
+            Assert.That(retrievedReading, Is.Not.Null);
+            Assert.That(reading.GlucoseLevel, Is.EqualTo(retrievedReading.GlucoseLevel));
+            Assert.That(reading.Direction, Is.EqualTo(retrievedReading.Direction));
+        });
+    }
+
+    [Test]
+    public void Can_Add_And_Retrieve_Ingredient()
+    {
+        var ingredient = new Ingredient
+        {
+            Id = Guid.NewGuid(),
+            Created = DateTimeOffset.UtcNow,
+            Name = "Ingredient 1",
+            Carbs = 10,
+            Protein = 5,
+            Fat = 2,
+            Calories = 100,
+            Uom = UnitOfMeasurement.Grams,
+        };
+        var meal = new Meal
+        {
+            Id = Guid.NewGuid(),
+            Created = DateTimeOffset.UtcNow,
+            Name = "Test Meal",
+        };
+        var mealIngredient = new List<MealIngredient>
+        {
+            new MealIngredient
+            {
+                Id = Guid.NewGuid(),
+                MealId = meal.Id,
+                IngredientId = ingredient.Id,
+                Quantity = 1,
+                Ingredient = ingredient,
+                Meal = meal,
+            }
+        };
+        meal.MealIngredients = mealIngredient;
+        ingredient.Meals = mealIngredient;
+
+        _dbContext.Ingredients.Add(ingredient);
+        _dbContext.SaveChanges();
+
+        var retrievedIngredient = _dbContext.Ingredients.Find(ingredient.Id);
+        Assert.Multiple(() =>
+        {
+            Assert.That(retrievedIngredient, Is.Not.Null);
+            Assert.That(ingredient, Is.EqualTo(retrievedIngredient));
+        });
+    }
+
+    [Test]
+    public void Can_Add_And_Retrieve_Meal()
+    {
+        var ingredient = new Ingredient
+        {
+            Id = Guid.NewGuid(),
+            Created = DateTimeOffset.UtcNow,
+            Name = "Ingredient 1",
+            Carbs = 10,
+            Protein = 5,
+            Fat = 2,
+            Calories = 100,
+            Uom = UnitOfMeasurement.Grams,
+        };
+        var meal = new Meal
+        {
+            Id = Guid.NewGuid(),
+            Created = DateTimeOffset.UtcNow,
+            Name = "Test Meal",
+        };
+        var mealIngredient = new List<MealIngredient>
+        {
+            new MealIngredient
+            {
+                Id = Guid.NewGuid(),
+                MealId = meal.Id,
+                IngredientId = ingredient.Id,
+                Quantity = 1,
+                Ingredient = ingredient,
+                Meal = meal,
+            }
+        };
+        meal.MealIngredients = mealIngredient;
+        ingredient.Meals = mealIngredient;
+
+        _dbContext.Meals.Add(meal);
+        _dbContext.SaveChanges();
+
+        var retrievedMeal = _dbContext.Meals
+            .Include(m => m.MealIngredients)
+            .FirstOrDefault(m => m.Id == meal.Id);
+
+        Assert.Multiple(() =>
+        {
+            Assert.That(retrievedMeal, Is.Not.Null);
+            Assert.That(retrievedMeal, Is.EqualTo(meal));
+        });
+    }
+
+    [Test]
+    public void Can_Add_And_Retrieve_Meal_Ingredient()
+    {
+        var meal = new Meal
+        {
+            Id = Guid.NewGuid(),
+            Created = DateTimeOffset.UtcNow,
+            Name = "Test Meal"
+        };
+        var ingredient = new Ingredient
+        {
+            Id = Guid.NewGuid(),
+            Created = DateTimeOffset.UtcNow,
+            Name = "Test Ingredient",
+            Carbs = 10,
+            Protein = 5,
+            Fat = 2,
+            Calories = 100,
+            Uom = UnitOfMeasurement.Grams
+        };
+        var mealIngredient = new MealIngredient
+        {
+            Id = Guid.NewGuid(),
+            IngredientId = ingredient.Id,
+            MealId = meal.Id,
+            Meal = meal,
+            Ingredient = ingredient,
+            Quantity = 1,
+        };
+
+        _dbContext.MealIngredients.Add(mealIngredient);
+        _dbContext.SaveChanges();
+
+        Assert.That(_dbContext.MealIngredients.Count(), Is.EqualTo(1));
+        var retrievedMealIngredient = _dbContext.MealIngredients.Include(mi => mi.Meal).Include(mi => mi.Ingredient).First();
+        Assert.That(mealIngredient, Is.Not.Null);
+        Assert.That(mealIngredient.Meal, Is.EqualTo(meal));
+        Assert.That(mealIngredient.Ingredient, Is.EqualTo(ingredient));
+    }
 }
\ No newline at end of file
diff --git a/src/GlucoPilot.Data/Entities/Ingredient.cs b/src/GlucoPilot.Data/Entities/Ingredient.cs
new file mode 100644
index 00000000..24c8fe85
--- /dev/null
+++ b/src/GlucoPilot.Data/Entities/Ingredient.cs
@@ -0,0 +1,58 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of the ingredient that the nutritional values are based on.
+    /// </summary>
+    public required UnitOfMeasurement Uom { get; set; }
+
+    /// <summary>
+    /// The list of ingredients associated with this ingredient.
+    /// </summary>
+    public virtual ICollection<MealIngredient> Meals { get; set; } = [];
+}
diff --git a/src/GlucoPilot.Data/Entities/Meal.cs b/src/GlucoPilot.Data/Entities/Meal.cs
new file mode 100644
index 00000000..9b02dc19
--- /dev/null
+++ b/src/GlucoPilot.Data/Entities/Meal.cs
@@ -0,0 +1,32 @@
+﻿using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("meals")]
+public class Meal
+{
+    /// <summary>
+    /// The unique identifier for the meal.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The name of the meal.
+    /// </summary>
+    public required string Name { get; set; } = string.Empty;
+
+    /// <summary>
+    /// The date and time the meal was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The list of ingredients associated with the meal.
+    /// </summary>
+    public virtual ICollection<MealIngredient> MealIngredients { get; set; } = [];
+}
diff --git a/src/GlucoPilot.Data/Entities/MealIngredient.cs b/src/GlucoPilot.Data/Entities/MealIngredient.cs
new file mode 100644
index 00000000..63bb5a93
--- /dev/null
+++ b/src/GlucoPilot.Data/Entities/MealIngredient.cs
@@ -0,0 +1,41 @@
+﻿using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("meals_ingredients")]
+public class MealIngredient
+{
+    /// <summary>
+    /// The unique identifier for the meal ingredient.
+    /// </summary>
+    public required Guid Id { get; set; }
+
+    /// <summary>
+    /// the unique identifier for the meal.
+    /// </summary>
+    public required Guid MealId { get; set; }
+
+    /// <summary>
+    /// The meals the meal ingredient is associated with.
+    /// </summary>
+    public virtual Meal? Meal { get; set; }
+
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    public required Guid IngredientId { get; set; }
+
+    /// <summary>
+    /// The ingredients the meal ingredient is associated with.
+    /// </summary>
+    public virtual Ingredient? Ingredient { get; set; }
+
+    /// <summary>
+    /// The quantity of the ingredient in the meal.
+    /// </summary>
+    [Range(0, int.MaxValue)]
+    public required int Quantity { get; set; }
+
+}
diff --git a/src/GlucoPilot.Data/Entities/Reading.cs b/src/GlucoPilot.Data/Entities/Reading.cs
new file mode 100644
index 00000000..75b2cb8b
--- /dev/null
+++ b/src/GlucoPilot.Data/Entities/Reading.cs
@@ -0,0 +1,32 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("readings")]
+public class Reading
+{
+    /// <summary>
+    /// The unique identifier for the reading.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the reading was taken.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The value of the glucose reading. Stored in gl/dl. Divide by 18 to get mmol/L.
+    /// </summary>
+    public required double GlucoseLevel { get; set; }
+
+    /// <summary>
+    /// A value representing the trend of the glucose level.
+    /// </summary>
+    public required ReadingDirection Direction { get; set; }
+}
diff --git a/src/GlucoPilot.Data/Enums/ReadingDirection.cs b/src/GlucoPilot.Data/Enums/ReadingDirection.cs
new file mode 100644
index 00000000..7bcb0988
--- /dev/null
+++ b/src/GlucoPilot.Data/Enums/ReadingDirection.cs
@@ -0,0 +1,11 @@
+﻿namespace GlucoPilot.Data.Enums;
+
+public enum ReadingDirection
+{
+    Decrease = 1,
+    RapidDecrease = 2,
+    Steady = 3,
+    Increase = 4,
+    RapidIncrease = 5,
+    NotComputable = 6,
+}
diff --git a/src/GlucoPilot.Data/Enums/UnitOfMeasurement.cs b/src/GlucoPilot.Data/Enums/UnitOfMeasurement.cs
new file mode 100644
index 00000000..53c2f5dc
--- /dev/null
+++ b/src/GlucoPilot.Data/Enums/UnitOfMeasurement.cs
@@ -0,0 +1,12 @@
+﻿namespace GlucoPilot.Data.Enums;
+
+public enum UnitOfMeasurement
+{
+    Unit = 0,
+    Grams = 1,
+    Hectograms = 2,
+    Kilograms = 3,
+    Millilitres = 4,
+    Hectolitres = 5,
+    Litres = 6,
+}
diff --git a/src/GlucoPilot.Data/GlucoPilotDbContext.cs b/src/GlucoPilot.Data/GlucoPilotDbContext.cs
index ce37b0e0..f58d8b75 100644
--- a/src/GlucoPilot.Data/GlucoPilotDbContext.cs
+++ b/src/GlucoPilot.Data/GlucoPilotDbContext.cs
@@ -7,6 +7,10 @@ public class GlucoPilotDbContext : DbContext
 {
     public DbSet<User> Users { get; set; }
     public DbSet<Patient> Patients { get; set; }
+    public DbSet<Reading> Readings { get; set; }
+    public DbSet<Ingredient> Ingredients { get; set; }
+    public DbSet<Meal> Meals { get; set; }
+    public DbSet<MealIngredient> MealIngredients { get; set; }
 
     public GlucoPilotDbContext(DbContextOptions<GlucoPilotDbContext> options) : base(options)
     {


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.


<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**src/GlucoPilot.Data/Enums/UnitOfMeasurement.cs:5**
* [nitpick] Enum member 'unit' is not in PascalCase; consider renaming it to 'Unit' to follow C# naming conventions.
```
unit = 0,
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T15:28:11Z
user: login: Loko3610
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T15:29:05Z
user: login: jcsawyer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T15:30:54Z
user: login: jcsawyer
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-04T15:33:15Z
user: login: Loko3610
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T15:50:05Z
user: login: Loko3610
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T15:56:48Z
user: login: Loko3610
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T16:00:08Z
user: login: jcsawyer
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T16:48:46Z
user: login: Loko3610
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T17:07:00Z
user: login: Loko3610
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T21:03:02Z
user: login: jcsawyer
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-04-04T21:13:09Z
user: login: Loko3610
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T21:13:59Z
user: login: jcsawyer
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-04T21:22:12Z

Review comments:
diff_hunk: @@ -0,0 +1,27 @@
+﻿using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("meals")]
+public class Meal
+{
+    /// <summary>
+    /// The unique identifier for the meal.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the meal was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The list of ingredients in the meal.
+    /// </summary>
+    public List<Ingredient> Ingredients { get; set; } = [];
path: src/GlucoPilot.Data/Entities/Meal.cs
user: login: Copilot
body: The Ingredients list initialization using [] is invalid C# syntax; please replace it with 'new List<Ingredient>()'.
```suggestion
    public List<Ingredient> Ingredients { get; set; } = new List<Ingredient>();
```
created_at: 2025-04-04T15:28:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of he ingredient that the nutritional values are based on.
path: src/GlucoPilot.Data/Entities/Ingredient.cs
user: login: Copilot
body: There is a typo in the comment ('he ingredient' should be 'the ingredient').
```suggestion
    /// The unit of measurement of the ingredient that the nutritional values are based on.
```
created_at: 2025-04-04T15:28:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,27 @@
+﻿using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("meals")]
+public class Meal
+{
+    /// <summary>
+    /// The unique identifier for the meal.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the meal was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The list of ingredients in the meal.
+    /// </summary>
+    public List<Ingredient> Ingredients { get; set; } = [];
path: src/GlucoPilot.Data/Entities/Meal.cs
user: login: Loko3610
body: Yes it is.
created_at: 2025-04-04T15:29:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,11 @@
+﻿namespace GlucoPilot.Data.Enums;
+
+public enum UnitOfMeasurement
path: src/GlucoPilot.Data/Enums/UnitOfMeasurement.cs
user: login: jcsawyer
body: These need to follow C# naming conventions
created_at: 2025-04-04T15:30:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,27 @@
+﻿using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("meals")]
+public class Meal
+{
+    /// <summary>
+    /// The unique identifier for the meal.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the meal was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The list of ingredients in the meal.
+    /// </summary>
+    public List<Ingredient> Ingredients { get; set; } = [];
path: src/GlucoPilot.Data/Entities/Meal.cs
user: login: jcsawyer
body: ```suggestion
    public virtual List<Ingredient> Ingredients { get; set; } = [];
```
created_at: 2025-04-04T15:31:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,52 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of he ingredient that the nutritional values are based on.
+    /// </summary>
+    public required UnitOfMeasurement Uom { get; set; }
path: src/GlucoPilot.Data/Entities/Ingredient.cs
user: login: jcsawyer
body: It might be nice to be able to filter recipes by ingredients. So we might want to split the unit of measurement into a separate table for a many to many relationship between meals and ingredients...
created_at: 2025-04-04T15:33:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,52 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of he ingredient that the nutritional values are based on.
+    /// </summary>
+    public required UnitOfMeasurement Uom { get; set; }
path: src/GlucoPilot.Data/Entities/Ingredient.cs
user: login: Loko3610
body: This can be done with a list of meals attached to ingredients too? Or it needs an explicit entity?
created_at: 2025-04-04T15:50:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,52 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of he ingredient that the nutritional values are based on.
+    /// </summary>
+    public required UnitOfMeasurement Uom { get; set; }
path: src/GlucoPilot.Data/Entities/Ingredient.cs
user: login: Loko3610
body: Wait no I get you. That won't work for UOM.
created_at: 2025-04-04T15:56:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,52 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of he ingredient that the nutritional values are based on.
+    /// </summary>
+    public required UnitOfMeasurement Uom { get; set; }
path: src/GlucoPilot.Data/Entities/Ingredient.cs
user: login: Loko3610
body: We want to split a quantity out, not the UOM. The ingredient has the same UOM. Say 20g of carbs for 100g of ingredient. But the quantity could be 2. For example, 2 x 100g of ingredient in the meal giving 40g of carbs in total.
created_at: 2025-04-04T16:00:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,52 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of he ingredient that the nutritional values are based on.
+    /// </summary>
+    public required UnitOfMeasurement Uom { get; set; }
path: src/GlucoPilot.Data/Entities/Ingredient.cs
user: login: jcsawyer
body: I would agree except you could have regional units of measurement. For example, imperial vs metric...

The unit of measurement should be separate in the quantity. The only thing that might make sense here would be if this were to be Measurement type like "by weight" or "by volume", etc.
created_at: 2025-04-04T16:48:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,52 @@
+﻿using GlucoPilot.Data.Enums;
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("ingredients")]
+public class Ingredient
+{
+    /// <summary>
+    /// The unique identifier for the ingredient.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the ingredient was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The name of the ingredient.
+    /// </summary>
+    public required string Name { get; set; }
+
+    /// <summary>
+    /// The number of carbs in an ingredient.
+    /// </summary>
+    public int Carbs { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of protein in an ingredient.
+    /// </summary>
+    public int Protein { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of fat in an ingredient.
+    /// </summary>
+    public int Fat { get; set; } = 0;
+
+    /// <summary>
+    /// The amount of calories in an ingredient.
+    /// </summary>
+    public int Calories { get; set; } = 0;
+
+    /// <summary>
+    /// The unit of measurement of he ingredient that the nutritional values are based on.
+    /// </summary>
+    public required UnitOfMeasurement Uom { get; set; }
path: src/GlucoPilot.Data/Entities/Ingredient.cs
user: login: Loko3610
body: Uom stays with ingredient as is used to calculate nutrition based on stored values. You are right in that we need a m to m table with quanity however. I will add, thanks. 
created_at: 2025-04-04T17:07:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,27 @@
+﻿using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace GlucoPilot.Data.Entities;
+
+[Table("meals")]
+public class Meal
+{
+    /// <summary>
+    /// The unique identifier for the meal.
+    /// </summary>
+    [Key]
+    [DatabaseGenerated(DatabaseGeneratedOption.None)]
+    public Guid Id { get; set; } = Guid.NewGuid();
+
+    /// <summary>
+    /// The date and time the meal was created.
+    /// </summary>
+    public required DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow;
+
+    /// <summary>
+    /// The list of ingredients in the meal.
+    /// </summary>
+    public List<Ingredient> Ingredients { get; set; } = [];
path: src/GlucoPilot.Data/Entities/Meal.cs
user: login: Loko3610
body: Fixed, thanks.
created_at: 2025-04-04T21:03:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,12 @@
+﻿namespace GlucoPilot.Data.Enums;
+
+public enum UnitOfMeasurement
+{
+    Unit = 0,
+    Grams = 1,
+    Hectograms = 2,
+    Kilograms = 3,
+    Millilitres = 4,
+    Hectolitres = 5,
+    Liters = 6,
path: src/GlucoPilot.Data/Enums/UnitOfMeasurement.cs
user: login: jcsawyer
body: ```suggestion
    Litres = 6,
```
created_at: 2025-04-04T21:12:55Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,12 @@
+﻿namespace GlucoPilot.Data.Enums;
+
+public enum UnitOfMeasurement
+{
+    Unit = 0,
+    Grams = 1,
+    Hectograms = 2,
+    Kilograms = 3,
+    Millilitres = 4,
+    Hectolitres = 5,
+    Liters = 6,
path: src/GlucoPilot.Data/Enums/UnitOfMeasurement.cs
user: login: Loko3610
body: Fixed. Thanks.
created_at: 2025-04-04T21:13:59Z
author_association: MEMBER

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-04T21:23:26Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=glucopilot_GlucoPilot&pullRequest=61) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=glucopilot_GlucoPilot&pullRequest=61&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=glucopilot_GlucoPilot&pullRequest=61&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=glucopilot_GlucoPilot&pullRequest=61&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=glucopilot_GlucoPilot&pullRequest=61&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=glucopilot_GlucoPilot&pullRequest=61&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=glucopilot_GlucoPilot&pullRequest=61)


author_association: NONE