From e654c1e0cc0e7fc11bcc82ae5fb234e4408e7b02 Mon Sep 17 00:00:00 2001
From: MingNian <1281442923@qq.com>
Date: Mon, 13 Jul 2026 10:39:34 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=9A=E7=9F=A5=E6=8E=A8=E9=80=81/?=
=?UTF-8?q?=E5=85=8D=E6=89=93=E6=89=B0=E5=81=8F=E5=A5=BD=20+=20=E9=A5=AE?=
=?UTF-8?q?=E9=A3=9F=E8=AE=B0=E5=BD=95=E4=BE=A7=E6=BB=91=E5=88=A0=E9=99=A4?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=20+=20=E8=81=8A=E5=A4=A9=E4=BA=A4=E4=BA=92?=
=?UTF-8?q?=E4=BC=98=E5=8C=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 通知: 新增推送开关/免打扰时段偏好持久化 + EF 迁移; 通知管线支持免打扰过滤
- 饮食: 侧滑删除 confirmDismiss 按方向放行(修复删不掉); 新增 diet_nutrition_widgets; 饮食录入页重构
- 聊天: 智能体胶囊点击锁防误触; 流式状态 select 优化; 卡片入场动画
- 主页: 消息列表提取 _HomeMessages + 侧滑手势打开抽屉
- 其他: api_client IP 适配; 通知/用药/饮食端点微调; 测试更新
---
.../Entities/support_entities.cs | 4 +
...22401_AddPushAndDndPreferences.Designer.cs | 1473 +++++++++++++++++
...20260713022401_AddPushAndDndPreferences.cs | 62 +
.../Migrations/AppDbContextModelSnapshot.cs | 12 +
.../EfUserNotificationPipeline.cs | 27 +-
.../health_record_reminder_service.cs | 7 +-
.../Health.WebApi/Endpoints/diet_endpoints.cs | 6 +-
.../Endpoints/medication_endpoints.cs | 6 +-
.../Endpoints/notification_endpoints.cs | 12 +
health_app/lib/core/api_client.dart | 2 +-
.../lib/pages/diet/diet_capture_page.dart | 276 +--
.../pages/diet/diet_nutrition_widgets.dart | 156 ++
health_app/lib/pages/home/home_page.dart | 41 +-
.../home/widgets/chat_messages_view.dart | 27 +-
.../medication/medication_list_page.dart | 14 +-
health_app/lib/pages/remaining_pages.dart | 178 +-
.../settings/notification_prefs_page.dart | 63 +-
health_app/lib/providers/chat_provider.dart | 70 +-
health_app/test/chat_provider_test.dart | 47 +-
19 files changed, 2140 insertions(+), 343 deletions(-)
create mode 100644 backend/src/Health.Infrastructure/Data/Migrations/20260713022401_AddPushAndDndPreferences.Designer.cs
create mode 100644 backend/src/Health.Infrastructure/Data/Migrations/20260713022401_AddPushAndDndPreferences.cs
create mode 100644 health_app/lib/pages/diet/diet_nutrition_widgets.dart
diff --git a/backend/src/Health.Domain/Entities/support_entities.cs b/backend/src/Health.Domain/Entities/support_entities.cs
index 293e98b..45abfac 100644
--- a/backend/src/Health.Domain/Entities/support_entities.cs
+++ b/backend/src/Health.Domain/Entities/support_entities.cs
@@ -89,6 +89,10 @@ public sealed class NotificationPreference
public bool FollowUpReminder { get; set; } = true;
public bool DoctorReply { get; set; } = true;
public bool AbnormalAlert { get; set; } = true;
+ public bool PushEnabled { get; set; } = true;
+ public bool DndEnabled { get; set; }
+ public int DndStartMinutes { get; set; } = 22 * 60;
+ public int DndEndMinutes { get; set; } = 8 * 60;
// 健康录入提醒——总开关 + 5 子项
public bool HealthRecordReminder { get; set; } = true;
public bool HealthRecordReminderBloodPressure { get; set; } = true;
diff --git a/backend/src/Health.Infrastructure/Data/Migrations/20260713022401_AddPushAndDndPreferences.Designer.cs b/backend/src/Health.Infrastructure/Data/Migrations/20260713022401_AddPushAndDndPreferences.Designer.cs
new file mode 100644
index 0000000..e8c9162
--- /dev/null
+++ b/backend/src/Health.Infrastructure/Data/Migrations/20260713022401_AddPushAndDndPreferences.Designer.cs
@@ -0,0 +1,1473 @@
+//
+using System;
+using System.Collections.Generic;
+using Health.Infrastructure.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Health.Infrastructure.Data.Migrations
+{
+ [DbContext(typeof(AppDbContext))]
+ [Migration("20260713022401_AddPushAndDndPreferences")]
+ partial class AddPushAndDndPreferences
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Health.Domain.Entities.Consultation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ClosedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DoctorId")
+ .HasColumnType("uuid");
+
+ b.Property("Month")
+ .HasColumnType("integer");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DoctorId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Consultations");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ConsultationMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConsultationId")
+ .HasColumnType("uuid");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SenderName")
+ .HasColumnType("text");
+
+ b.Property("SenderType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ConsultationId", "CreatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("ConsultationMessages");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Conversation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AgentType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MessageCount")
+ .HasColumnType("integer");
+
+ b.Property("Summary")
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "UpdatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("Conversations");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ConversationMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ConversationId")
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Intent")
+ .HasColumnType("text");
+
+ b.Property("MetadataJson")
+ .HasColumnType("jsonb");
+
+ b.Property("Role")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ConversationId", "CreatedAt");
+
+ b.ToTable("ConversationMessages");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DeviceToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("Platform")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("PushToken")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("DeviceTokens");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DietFoodItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Calories")
+ .HasColumnType("integer");
+
+ b.Property("CarbsGrams")
+ .HasColumnType("numeric");
+
+ b.Property("DietRecordId")
+ .HasColumnType("uuid");
+
+ b.Property("FatGrams")
+ .HasColumnType("numeric");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Portion")
+ .HasColumnType("text");
+
+ b.Property("ProteinGrams")
+ .HasColumnType("numeric");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("Warning")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DietRecordId");
+
+ b.ToTable("DietFoodItems");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DietRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("HealthScore")
+ .HasColumnType("integer");
+
+ b.Property("MealType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RecordedAt")
+ .HasColumnType("date");
+
+ b.Property("TotalCalories")
+ .HasColumnType("integer");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "RecordedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("DietRecords");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Doctor", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AvatarUrl")
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Department")
+ .HasColumnType("text");
+
+ b.Property("Introduction")
+ .HasColumnType("text");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Phone")
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)");
+
+ b.Property("ProfessionalDirection")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("Doctors");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.DoctorProfile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AvatarUrl")
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Department")
+ .HasColumnType("text");
+
+ b.Property("DoctorId")
+ .HasColumnType("uuid");
+
+ b.Property("Hospital")
+ .HasColumnType("text");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("IsOnline")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Title")
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DoctorId");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("DoctorProfiles");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ExercisePlan", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("EndDate")
+ .HasColumnType("date");
+
+ b.Property("ReminderTime")
+ .HasColumnType("time without time zone");
+
+ b.Property("StartDate")
+ .HasColumnType("date");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "StartDate", "EndDate");
+
+ b.ToTable("ExercisePlans");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.ExercisePlanItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CompletedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DurationMinutes")
+ .HasColumnType("integer");
+
+ b.Property("ExerciseType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsCompleted")
+ .HasColumnType("boolean");
+
+ b.Property("IsRestDay")
+ .HasColumnType("boolean");
+
+ b.Property("PlanId")
+ .HasColumnType("uuid");
+
+ b.Property("ScheduledDate")
+ .HasColumnType("date");
+
+ b.HasKey("Id");
+
+ b.HasIndex("PlanId", "ScheduledDate")
+ .IsUnique();
+
+ b.ToTable("ExercisePlanItems");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.FollowUp", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Department")
+ .HasColumnType("text");
+
+ b.Property("DoctorName")
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("ScheduledAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("FollowUps");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.HealthArchive", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.PrimitiveCollection>("Allergies")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.PrimitiveCollection>("ChronicDiseases")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.Property("Diagnosis")
+ .HasColumnType("text");
+
+ b.PrimitiveCollection>("DietRestrictions")
+ .IsRequired()
+ .HasColumnType("text[]");
+
+ b.Property("FamilyHistory")
+ .HasColumnType("text");
+
+ b.Property("SurgeryDate")
+ .HasColumnType("date");
+
+ b.Property("SurgeryType")
+ .HasColumnType("text");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("HealthArchives");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.HealthArchiveSurgery", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Date")
+ .HasColumnType("date");
+
+ b.Property("HealthArchiveId")
+ .HasColumnType("uuid");
+
+ b.Property("SortOrder")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("HealthArchiveId", "SortOrder");
+
+ b.ToTable("HealthArchiveSurgeries", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.HealthRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Diastolic")
+ .HasColumnType("integer");
+
+ b.Property("IsAbnormal")
+ .HasColumnType("boolean");
+
+ b.Property("MetricType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RecordedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Systolic")
+ .HasColumnType("integer");
+
+ b.Property("Unit")
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("Value")
+ .HasColumnType("numeric");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "MetricType");
+
+ b.HasIndex("UserId", "RecordedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("HealthRecords");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Medication", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Dosage")
+ .HasColumnType("text");
+
+ b.Property("EndDate")
+ .HasColumnType("date");
+
+ b.Property("Frequency")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Notes")
+ .HasColumnType("text");
+
+ b.Property("Source")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("StartDate")
+ .HasColumnType("date");
+
+ b.PrimitiveCollection>("TimeOfDay")
+ .IsRequired()
+ .HasColumnType("time[]");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "IsActive");
+
+ b.ToTable("Medications");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.MedicationLog", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ConfirmedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MedicationId")
+ .HasColumnType("uuid");
+
+ b.Property("ScheduledTime")
+ .HasColumnType("time without time zone");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("MedicationId", "CreatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("MedicationLogs");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.NotificationPreference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AbnormalAlert")
+ .HasColumnType("boolean");
+
+ b.Property("DndEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("DndEndMinutes")
+ .HasColumnType("integer");
+
+ b.Property("DndStartMinutes")
+ .HasColumnType("integer");
+
+ b.Property("DoctorReply")
+ .HasColumnType("boolean");
+
+ b.Property("FollowUpReminder")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminder")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderBloodPressure")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderGlucose")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderHeartRate")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderSpO2")
+ .HasColumnType("boolean");
+
+ b.Property("HealthRecordReminderWeight")
+ .HasColumnType("boolean");
+
+ b.Property("MedicationReminder")
+ .HasColumnType("boolean");
+
+ b.Property("PushEnabled")
+ .HasColumnType("boolean");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("NotificationPreferences");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.RefreshToken", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsRevoked")
+ .HasColumnType("boolean");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.ToTable("RefreshTokens");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.Report", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AiIndicators")
+ .HasColumnType("jsonb");
+
+ b.Property("AiSummary")
+ .HasColumnType("text");
+
+ b.Property("Category")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DoctorComment")
+ .HasColumnType("text");
+
+ b.Property("DoctorName")
+ .HasColumnType("text");
+
+ b.Property("DoctorRecommendation")
+ .HasColumnType("text");
+
+ b.Property("FileType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("FileUrl")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReviewedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Severity")
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Reports");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AvatarUrl")
+ .HasColumnType("text");
+
+ b.Property("BirthDate")
+ .HasColumnType("date");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DoctorId")
+ .HasColumnType("uuid");
+
+ b.Property("Gender")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Role")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasDefaultValue("User");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DoctorId");
+
+ b.HasIndex("Phone")
+ .IsUnique();
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.UserNotification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("ActionTargetId")
+ .HasColumnType("text");
+
+ b.Property("ActionType")
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsDeleted")
+ .HasColumnType("boolean");
+
+ b.Property("IsRead")
+ .HasColumnType("boolean");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ReadAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Severity")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)");
+
+ b.Property("SourceId")
+ .HasColumnType("uuid");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SourceId")
+ .IsUnique();
+
+ b.HasIndex("UserId", "IsDeleted", "IsRead", "CreatedAt");
+
+ b.ToTable("UserNotifications", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Domain.Entities.VerificationCode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsUsed")
+ .HasColumnType("boolean");
+
+ b.Property("Phone")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Phone", "CreatedAt")
+ .IsDescending(false, true);
+
+ b.ToTable("VerificationCodes");
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.AiWriteCommandRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Arguments")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("ToolName")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId", "Status", "ExpiresAt");
+
+ b.ToTable("AiWriteCommands", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.DietImageAnalysisTaskRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempts")
+ .HasColumnType("integer");
+
+ b.Property("AvailableAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("FilePaths")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("LastError")
+ .HasColumnType("text");
+
+ b.Property("ResultCode")
+ .HasColumnType("integer");
+
+ b.Property("ResultData")
+ .HasColumnType("text");
+
+ b.Property("ResultMessage")
+ .HasColumnType("text");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status", "AvailableAt");
+
+ b.ToTable("DietImageAnalysisTasks", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.MedicationReminderTaskRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempts")
+ .HasColumnType("integer");
+
+ b.Property("AvailableAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Dosage")
+ .HasColumnType("text");
+
+ b.Property("LastError")
+ .HasColumnType("text");
+
+ b.Property("MedicationId")
+ .HasColumnType("uuid");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ScheduledDate")
+ .HasColumnType("date");
+
+ b.Property("ScheduledTime")
+ .HasColumnType("time without time zone");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Status", "AvailableAt");
+
+ b.HasIndex("MedicationId", "ScheduledDate", "ScheduledTime")
+ .IsUnique();
+
+ b.ToTable("MedicationReminderTasks", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.NotificationOutboxRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Attempts")
+ .HasColumnType("integer");
+
+ b.Property("AvailableAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LastError")
+ .HasColumnType("text");
+
+ b.Property("Payload")
+ .IsRequired()
+ .HasColumnType("jsonb");
+
+ b.Property("SourceTaskId")
+ .HasColumnType("uuid");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasMaxLength(64)
+ .HasColumnType("character varying(64)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SourceTaskId")
+ .IsUnique();
+
+ b.HasIndex("Status", "AvailableAt");
+
+ b.ToTable("NotificationOutbox", (string)null);
+ });
+
+ modelBuilder.Entity("Health.Infrastructure.Data.Records.ReportAnalysisTaskRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property