diff --git a/src/GreenHome.Infrastructure/GreenHomeDbContext.cs b/src/GreenHome.Infrastructure/Data/GreenHomeDbContext.cs similarity index 94% rename from src/GreenHome.Infrastructure/GreenHomeDbContext.cs rename to src/GreenHome.Infrastructure/Data/GreenHomeDbContext.cs index 8916f1b..230e55f 100644 --- a/src/GreenHome.Infrastructure/GreenHomeDbContext.cs +++ b/src/GreenHome.Infrastructure/Data/GreenHomeDbContext.cs @@ -1,376 +1,394 @@ -using Microsoft.EntityFrameworkCore; - -namespace GreenHome.Infrastructure; - -public sealed class GreenHomeDbContext : DbContext -{ - public GreenHomeDbContext(DbContextOptions options) : base(options) { } - - public DbSet Devices => Set(); - public DbSet TelemetryRecords => Set(); - public DbSet DeviceSettings => Set(); - public DbSet AlertConditions => Set(); - public DbSet AlertRules => Set(); - public DbSet Users => Set(); - public DbSet VerificationCodes => Set(); - public DbSet DeviceUsers => Set(); - public DbSet AlertNotifications => Set(); - public DbSet AlertLogs => Set(); - public DbSet AIQueries => Set(); - public DbSet DailyReports => Set(); - public DbSet UserDailyReports => Set(); - public DbSet ReportImages => Set(); - public DbSet Checklists => Set(); - public DbSet ChecklistItems => Set(); - public DbSet ChecklistCompletions => Set(); - public DbSet ChecklistItemCompletions => Set(); - public DbSet DevicePosts => Set(); - public DbSet DevicePostImages => Set(); - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(b => - { - b.ToTable("Devices"); - b.HasKey(x => x.Id); - b.Property(x => x.DeviceName).IsRequired().HasMaxLength(10); - b.Property(x => x.UserId).IsRequired(); - b.Property(x => x.Location).HasMaxLength(250); - b.Property(x => x.NeshanLocation).HasMaxLength(80); - b.HasOne(x => x.User) - .WithMany() - .HasForeignKey(x => x.UserId) - .OnDelete(DeleteBehavior.Restrict); - }); - - modelBuilder.Entity(b => - { - b.ToTable("Telemetry"); - b.HasKey(x => x.Id); - b.Property(x => x.TemperatureC).HasColumnType("decimal(18,2)"); - b.Property(x => x.HumidityPercent).HasColumnType("decimal(18,2)"); - b.Property(x => x.SoilPercent).HasColumnType("decimal(18,2)"); - b.Property(x => x.Lux).HasColumnType("decimal(18,2)"); - b.Property(x => x.PersianDate).HasMaxLength(10); - b.Property(x => x.Voltage).HasColumnType("decimal(18,2)"); - b.Property(x => x.BatteryVoltage).HasColumnType("decimal(18,2)"); - b.Property(x => x.BatteryPercent).HasColumnType("decimal(18,2)"); - b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth }); - b.HasIndex(x => new { x.DeviceId, x.TimestampUtc }); - b.HasIndex(x => new { x.DeviceId, x.ServerTimestampUtc }); - }); - - modelBuilder.Entity(b => - { - b.ToTable("DeviceSettings"); - b.HasKey(x => x.Id); - b.Property(x => x.Province).HasMaxLength(100); - b.Property(x => x.City).HasMaxLength(100); - b.Property(x => x.Latitude).HasColumnType("decimal(9,6)"); - b.Property(x => x.Longitude).HasColumnType("decimal(9,6)"); - b.Property(x => x.ProductType).HasMaxLength(100); - b.Property(x => x.MinimumSmsIntervalMinutes).HasDefaultValue(15); - b.Property(x => x.MinimumCallIntervalMinutes).HasDefaultValue(60); - b.Property(x => x.AreaSquareMeters).HasColumnType("decimal(18,2)"); - b.Property(x => x.SmsRecievers).HasMaxLength(120); - b.Property(x => x.RelayCount).HasDefaultValue(4); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Cascade); - b.HasIndex(x => x.DeviceId).IsUnique(); - }); - - modelBuilder.Entity(b => - { - b.ToTable("AlertConditions"); - b.HasKey(x => x.Id); - b.Property(x => x.NotificationType).IsRequired().HasConversion(); - b.Property(x => x.TimeType).IsRequired().HasConversion(); - b.Property(x => x.CallCooldownMinutes).IsRequired(); - b.Property(x => x.SmsCooldownMinutes).IsRequired(); - b.Property(x => x.IsEnabled).IsRequired(); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Cascade); - b.HasIndex(x => x.DeviceId); - }); - - modelBuilder.Entity(b => - { - b.ToTable("AlertRules"); - b.HasKey(x => x.Id); - b.Property(x => x.SensorType).IsRequired().HasConversion(); - b.Property(x => x.ComparisonType).IsRequired().HasConversion(); - b.Property(x => x.Value1).IsRequired().HasColumnType("decimal(18,2)"); - b.Property(x => x.Value2).HasColumnType("decimal(18,2)"); - b.Property(x => x.Order).IsRequired(); - b.HasOne(x => x.AlertCondition) - .WithMany(c => c.Rules) - .HasForeignKey(x => x.AlertConditionId) - .OnDelete(DeleteBehavior.Cascade); - b.HasIndex(x => x.AlertConditionId); - }); - - modelBuilder.Entity(b => - { - b.ToTable("Users"); - b.HasKey(x => x.Id); - b.Property(x => x.Mobile).IsRequired().HasMaxLength(11); - b.Property(x => x.Name).IsRequired().HasMaxLength(100); - b.Property(x => x.Family).IsRequired().HasMaxLength(100); - b.Property(x => x.Role).IsRequired().HasConversion(); - b.HasIndex(x => x.Mobile).IsUnique(); - }); - - modelBuilder.Entity(b => - { - b.ToTable("DeviceUsers"); - b.HasKey(x => new { x.DeviceId, x.UserId }); - b.Property(x => x.ReceiveAlerts).IsRequired().HasDefaultValue(true); - b.HasOne(x => x.Device) - .WithMany(d => d.DeviceUsers) - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Cascade); - b.HasOne(x => x.User) - .WithMany(u => u.DeviceUsers) - .HasForeignKey(x => x.UserId) - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity(b => - { - b.ToTable("VerificationCodes"); - b.HasKey(x => x.Id); - b.Property(x => x.Mobile).IsRequired().HasMaxLength(11); - b.Property(x => x.Code).IsRequired().HasMaxLength(4); - b.HasIndex(x => new { x.Mobile, x.Code, x.IsUsed }); - }); - - modelBuilder.Entity(b => - { - b.ToTable("AlertNotifications"); - b.HasKey(x => x.Id); - b.Property(x => x.NotificationType).IsRequired().HasConversion(); - b.Property(x => x.Message).IsRequired().HasMaxLength(500); - b.Property(x => x.MessageOutboxIds).HasMaxLength(500); - b.Property(x => x.ErrorMessage).HasMaxLength(1000); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Restrict); - b.HasOne(x => x.User) - .WithMany() - .HasForeignKey(x => x.UserId) - .OnDelete(DeleteBehavior.Restrict); - b.HasOne(x => x.AlertCondition) - .WithMany() - .HasForeignKey(x => x.AlertConditionId) - .OnDelete(DeleteBehavior.Restrict); - b.HasIndex(x => new { x.DeviceId, x.UserId, x.AlertConditionId, x.SentAt }); - }); - - modelBuilder.Entity(b => - { - b.ToTable("AIQueries"); - b.HasKey(x => x.Id); - b.Property(x => x.Question).IsRequired(); - b.Property(x => x.Answer).IsRequired(); - b.Property(x => x.Model).HasMaxLength(100); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.SetNull); - b.HasOne(x => x.User) - .WithMany() - .HasForeignKey(x => x.UserId) - .OnDelete(DeleteBehavior.SetNull); - b.HasIndex(x => x.DeviceId); - b.HasIndex(x => x.UserId); - b.HasIndex(x => x.CreatedAt); - }); - - modelBuilder.Entity(b => - { - b.ToTable("DailyReports"); - b.HasKey(x => x.Id); - b.Property(x => x.PersianDate).IsRequired().HasMaxLength(10); - b.Property(x => x.Analysis).IsRequired(); - b.Property(x => x.Model).HasMaxLength(100); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Cascade); - b.HasIndex(x => new { x.DeviceId, x.PersianDate }).IsUnique(); - b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth }); - b.HasIndex(x => x.CreatedAt); - }); - - modelBuilder.Entity(b => - { - b.ToTable("AlertLogs"); - b.HasKey(x => x.Id); - b.Property(x => x.AlertType).IsRequired().HasConversion(); - b.Property(x => x.NotificationType).IsRequired().HasConversion(); - b.Property(x => x.Message).IsRequired().HasMaxLength(1000); - b.Property(x => x.Status).IsRequired().HasConversion(); - b.Property(x => x.ErrorMessage).HasMaxLength(2000); - b.Property(x => x.PhoneNumber).IsRequired().HasMaxLength(20); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Restrict); - b.HasOne(x => x.User) - .WithMany() - .HasForeignKey(x => x.UserId) - .OnDelete(DeleteBehavior.Restrict); - b.HasOne(x => x.AlertCondition) - .WithMany() - .HasForeignKey(x => x.AlertConditionId) - .OnDelete(DeleteBehavior.SetNull); - b.HasIndex(x => new { x.DeviceId, x.SentAt }); - b.HasIndex(x => new { x.UserId, x.SentAt }); - b.HasIndex(x => x.AlertType); - b.HasIndex(x => x.Status); - }); - - modelBuilder.Entity(b => - { - b.ToTable("UserDailyReports"); - b.HasKey(x => x.Id); - b.Property(x => x.PersianDate).IsRequired().HasMaxLength(10); - b.Property(x => x.Title).IsRequired().HasMaxLength(200); - b.Property(x => x.Observations).IsRequired(); - b.Property(x => x.Operations).IsRequired(); - b.Property(x => x.Notes).HasMaxLength(2000); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Cascade); - b.HasOne(x => x.User) - .WithMany() - .HasForeignKey(x => x.UserId) - .OnDelete(DeleteBehavior.Restrict); - b.HasIndex(x => new { x.DeviceId, x.PersianDate }); - b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth }); - b.HasIndex(x => x.UserId); - }); - - modelBuilder.Entity(b => - { - b.ToTable("ReportImages"); - b.HasKey(x => x.Id); - b.Property(x => x.FileName).IsRequired().HasMaxLength(255); - b.Property(x => x.FilePath).IsRequired().HasMaxLength(500); - b.Property(x => x.ContentType).IsRequired().HasMaxLength(100); - b.Property(x => x.Description).HasMaxLength(500); - b.HasOne(x => x.UserDailyReport) - .WithMany(r => r.Images) - .HasForeignKey(x => x.UserDailyReportId) - .OnDelete(DeleteBehavior.Cascade); - b.HasIndex(x => x.UserDailyReportId); - }); - - modelBuilder.Entity(b => - { - b.ToTable("Checklists"); - b.HasKey(x => x.Id); - b.Property(x => x.Title).IsRequired().HasMaxLength(200); - b.Property(x => x.Description).HasMaxLength(1000); - b.Property(x => x.IsActive).IsRequired(); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Cascade); - b.HasOne(x => x.CreatedByUser) - .WithMany() - .HasForeignKey(x => x.CreatedByUserId) - .OnDelete(DeleteBehavior.Restrict); - b.HasIndex(x => new { x.DeviceId, x.IsActive }); - }); - - modelBuilder.Entity(b => - { - b.ToTable("ChecklistItems"); - b.HasKey(x => x.Id); - b.Property(x => x.Title).IsRequired().HasMaxLength(300); - b.Property(x => x.Description).HasMaxLength(1000); - b.Property(x => x.Order).IsRequired(); - b.Property(x => x.IsRequired).IsRequired(); - b.HasOne(x => x.Checklist) - .WithMany(c => c.Items) - .HasForeignKey(x => x.ChecklistId) - .OnDelete(DeleteBehavior.Cascade); - b.HasIndex(x => new { x.ChecklistId, x.Order }); - }); - - modelBuilder.Entity(b => - { - b.ToTable("ChecklistCompletions"); - b.HasKey(x => x.Id); - b.Property(x => x.PersianDate).IsRequired().HasMaxLength(10); - b.Property(x => x.Notes).HasMaxLength(2000); - b.HasOne(x => x.Checklist) - .WithMany(c => c.Completions) - .HasForeignKey(x => x.ChecklistId) - .OnDelete(DeleteBehavior.Cascade); - b.HasOne(x => x.CompletedByUser) - .WithMany() - .HasForeignKey(x => x.CompletedByUserId) - .OnDelete(DeleteBehavior.Restrict); - b.HasIndex(x => new { x.ChecklistId, x.PersianDate }); - b.HasIndex(x => x.CompletedByUserId); - }); - - modelBuilder.Entity(b => - { - b.ToTable("ChecklistItemCompletions"); - b.HasKey(x => x.Id); - b.Property(x => x.IsChecked).IsRequired(); - b.Property(x => x.Note).HasMaxLength(500); - b.HasOne(x => x.ChecklistCompletion) - .WithMany(cc => cc.ItemCompletions) - .HasForeignKey(x => x.ChecklistCompletionId) - .OnDelete(DeleteBehavior.Cascade); - b.HasOne(x => x.ChecklistItem) - .WithMany() - .HasForeignKey(x => x.ChecklistItemId) - .OnDelete(DeleteBehavior.Restrict); - b.HasIndex(x => x.ChecklistCompletionId); - }); - - modelBuilder.Entity(b => - { - b.ToTable("DevicePosts"); - b.HasKey(x => x.Id); - b.Property(x => x.Content).IsRequired().HasMaxLength(5000); - b.HasOne(x => x.Device) - .WithMany() - .HasForeignKey(x => x.DeviceId) - .OnDelete(DeleteBehavior.Cascade); - b.HasOne(x => x.AuthorUser) - .WithMany() - .HasForeignKey(x => x.AuthorUserId) - .OnDelete(DeleteBehavior.Restrict); - b.HasIndex(x => new { x.DeviceId, x.CreatedAt }); - b.HasIndex(x => x.AuthorUserId); - }); - - modelBuilder.Entity(b => - { - b.ToTable("DevicePostImages"); - b.HasKey(x => x.Id); - b.Property(x => x.FileName).IsRequired().HasMaxLength(255); - b.Property(x => x.FilePath).IsRequired().HasMaxLength(500); - b.Property(x => x.ContentType).IsRequired().HasMaxLength(100); - b.HasOne(x => x.DevicePost) - .WithMany(p => p.Images) - .HasForeignKey(x => x.DevicePostId) - .OnDelete(DeleteBehavior.Cascade); - b.HasIndex(x => x.DevicePostId); - }); - } -} +using Microsoft.EntityFrameworkCore; + +namespace GreenHome.Infrastructure.Data; + +public sealed class GreenHomeDbContext : DbContext +{ + public GreenHomeDbContext(DbContextOptions options) : base(options) { } + + public DbSet Devices => Set(); + public DbSet TelemetryRecords => Set(); + public DbSet DeviceSettings => Set(); + public DbSet AlertConditions => Set(); + public DbSet AlertRules => Set(); + public DbSet Users => Set(); + public DbSet VerificationCodes => Set(); + public DbSet DeviceUsers => Set(); + public DbSet AlertNotifications => Set(); + public DbSet AlertLogs => Set(); + public DbSet AIQueries => Set(); + public DbSet DailyReports => Set(); + public DbSet UserDailyReports => Set(); + public DbSet ReportImages => Set(); + public DbSet Checklists => Set(); + public DbSet ChecklistItems => Set(); + public DbSet ChecklistCompletions => Set(); + public DbSet ChecklistItemCompletions => Set(); + public DbSet DevicePosts => Set(); + public DbSet DevicePostImages => Set(); + public DbSet PowerOutageLogs => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(b => + { + b.ToTable("Devices"); + b.HasKey(x => x.Id); + b.Property(x => x.DeviceName).IsRequired().HasMaxLength(10); + b.Property(x => x.UserId).IsRequired(); + b.Property(x => x.Location).HasMaxLength(250); + b.Property(x => x.NeshanLocation).HasMaxLength(80); + b.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity(b => + { + b.ToTable("Telemetry"); + b.HasKey(x => x.Id); + b.Property(x => x.TemperatureC).HasColumnType("decimal(18,2)"); + b.Property(x => x.HumidityPercent).HasColumnType("decimal(18,2)"); + b.Property(x => x.SoilPercent).HasColumnType("decimal(18,2)"); + b.Property(x => x.Lux).HasColumnType("decimal(18,2)"); + b.Property(x => x.PersianDate).HasMaxLength(10); + b.Property(x => x.Voltage).HasColumnType("decimal(18,2)"); + b.Property(x => x.BatteryVoltage).HasColumnType("decimal(18,2)"); + b.Property(x => x.BatteryPercent).HasColumnType("decimal(18,2)"); + b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth }); + b.HasIndex(x => new { x.DeviceId, x.TimestampUtc }); + b.HasIndex(x => new { x.DeviceId, x.ServerTimestampUtc }); + }); + + modelBuilder.Entity(b => + { + b.ToTable("DeviceSettings"); + b.HasKey(x => x.Id); + b.Property(x => x.Province).HasMaxLength(100); + b.Property(x => x.City).HasMaxLength(100); + b.Property(x => x.Latitude).HasColumnType("decimal(9,6)"); + b.Property(x => x.Longitude).HasColumnType("decimal(9,6)"); + b.Property(x => x.ProductType).HasMaxLength(100); + b.Property(x => x.MinimumSmsIntervalMinutes).HasDefaultValue(15); + b.Property(x => x.MinimumCallIntervalMinutes).HasDefaultValue(60); + b.Property(x => x.AreaSquareMeters).HasColumnType("decimal(18,2)"); + b.Property(x => x.SmsRecievers).HasMaxLength(120); + b.Property(x => x.RelayCount).HasDefaultValue(4); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => x.DeviceId).IsUnique(); + }); + + modelBuilder.Entity(b => + { + b.ToTable("AlertConditions"); + b.HasKey(x => x.Id); + b.Property(x => x.NotificationType).IsRequired().HasConversion(); + b.Property(x => x.TimeType).IsRequired().HasConversion(); + b.Property(x => x.CallCooldownMinutes).IsRequired(); + b.Property(x => x.SmsCooldownMinutes).IsRequired(); + b.Property(x => x.IsEnabled).IsRequired(); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => x.DeviceId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("AlertRules"); + b.HasKey(x => x.Id); + b.Property(x => x.SensorType).IsRequired().HasConversion(); + b.Property(x => x.ComparisonType).IsRequired().HasConversion(); + b.Property(x => x.Value1).IsRequired().HasColumnType("decimal(18,2)"); + b.Property(x => x.Value2).HasColumnType("decimal(18,2)"); + b.Property(x => x.Order).IsRequired(); + b.HasOne(x => x.AlertCondition) + .WithMany(c => c.Rules) + .HasForeignKey(x => x.AlertConditionId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => x.AlertConditionId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("Users"); + b.HasKey(x => x.Id); + b.Property(x => x.Mobile).IsRequired().HasMaxLength(11); + b.Property(x => x.Name).IsRequired().HasMaxLength(100); + b.Property(x => x.Family).IsRequired().HasMaxLength(100); + b.Property(x => x.Role).IsRequired().HasConversion(); + b.HasIndex(x => x.Mobile).IsUnique(); + }); + + modelBuilder.Entity(b => + { + b.ToTable("DeviceUsers"); + b.HasKey(x => new { x.DeviceId, x.UserId }); + b.Property(x => x.ReceiveAlerts).IsRequired().HasDefaultValue(true); + b.HasOne(x => x.Device) + .WithMany(d => d.DeviceUsers) + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasOne(x => x.User) + .WithMany(u => u.DeviceUsers) + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity(b => + { + b.ToTable("VerificationCodes"); + b.HasKey(x => x.Id); + b.Property(x => x.Mobile).IsRequired().HasMaxLength(11); + b.Property(x => x.Code).IsRequired().HasMaxLength(4); + b.HasIndex(x => new { x.Mobile, x.Code, x.IsUsed }); + }); + + modelBuilder.Entity(b => + { + b.ToTable("AlertNotifications"); + b.HasKey(x => x.Id); + b.Property(x => x.NotificationType).IsRequired().HasConversion(); + b.Property(x => x.Message).IsRequired().HasMaxLength(500); + b.Property(x => x.MessageOutboxIds).HasMaxLength(500); + b.Property(x => x.ErrorMessage).HasMaxLength(1000); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.AlertCondition) + .WithMany() + .HasForeignKey(x => x.AlertConditionId) + .OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.DeviceId, x.UserId, x.AlertConditionId, x.SentAt }); + }); + + modelBuilder.Entity(b => + { + b.ToTable("AIQueries"); + b.HasKey(x => x.Id); + b.Property(x => x.Question).IsRequired(); + b.Property(x => x.Answer).IsRequired(); + b.Property(x => x.Model).HasMaxLength(100); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.SetNull); + b.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.SetNull); + b.HasIndex(x => x.DeviceId); + b.HasIndex(x => x.UserId); + b.HasIndex(x => x.CreatedAt); + }); + + modelBuilder.Entity(b => + { + b.ToTable("DailyReports"); + b.HasKey(x => x.Id); + b.Property(x => x.PersianDate).IsRequired().HasMaxLength(10); + b.Property(x => x.Analysis).IsRequired(); + b.Property(x => x.Model).HasMaxLength(100); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => new { x.DeviceId, x.PersianDate }).IsUnique(); + b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth }); + b.HasIndex(x => x.CreatedAt); + }); + + modelBuilder.Entity(b => + { + b.ToTable("AlertLogs"); + b.HasKey(x => x.Id); + b.Property(x => x.AlertType).IsRequired().HasConversion(); + b.Property(x => x.NotificationType).IsRequired().HasConversion(); + b.Property(x => x.Message).IsRequired().HasMaxLength(1000); + b.Property(x => x.Status).IsRequired().HasConversion(); + b.Property(x => x.ErrorMessage).HasMaxLength(2000); + b.Property(x => x.PhoneNumber).IsRequired().HasMaxLength(20); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.AlertCondition) + .WithMany() + .HasForeignKey(x => x.AlertConditionId) + .OnDelete(DeleteBehavior.SetNull); + b.HasIndex(x => new { x.DeviceId, x.SentAt }); + b.HasIndex(x => new { x.UserId, x.SentAt }); + b.HasIndex(x => x.AlertType); + b.HasIndex(x => x.Status); + }); + + modelBuilder.Entity(b => + { + b.ToTable("UserDailyReports"); + b.HasKey(x => x.Id); + b.Property(x => x.PersianDate).IsRequired().HasMaxLength(10); + b.Property(x => x.Title).IsRequired().HasMaxLength(200); + b.Property(x => x.Observations).IsRequired(); + b.Property(x => x.Operations).IsRequired(); + b.Property(x => x.Notes).HasMaxLength(2000); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasOne(x => x.User) + .WithMany() + .HasForeignKey(x => x.UserId) + .OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.DeviceId, x.PersianDate }); + b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth }); + b.HasIndex(x => x.UserId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("ReportImages"); + b.HasKey(x => x.Id); + b.Property(x => x.FileName).IsRequired().HasMaxLength(255); + b.Property(x => x.FilePath).IsRequired().HasMaxLength(500); + b.Property(x => x.ContentType).IsRequired().HasMaxLength(100); + b.Property(x => x.Description).HasMaxLength(500); + b.HasOne(x => x.UserDailyReport) + .WithMany(r => r.Images) + .HasForeignKey(x => x.UserDailyReportId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => x.UserDailyReportId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("Checklists"); + b.HasKey(x => x.Id); + b.Property(x => x.Title).IsRequired().HasMaxLength(200); + b.Property(x => x.Description).HasMaxLength(1000); + b.Property(x => x.IsActive).IsRequired(); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasOne(x => x.CreatedByUser) + .WithMany() + .HasForeignKey(x => x.CreatedByUserId) + .OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.DeviceId, x.IsActive }); + }); + + modelBuilder.Entity(b => + { + b.ToTable("ChecklistItems"); + b.HasKey(x => x.Id); + b.Property(x => x.Title).IsRequired().HasMaxLength(300); + b.Property(x => x.Description).HasMaxLength(1000); + b.Property(x => x.Order).IsRequired(); + b.Property(x => x.IsRequired).IsRequired(); + b.HasOne(x => x.Checklist) + .WithMany(c => c.Items) + .HasForeignKey(x => x.ChecklistId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => new { x.ChecklistId, x.Order }); + }); + + modelBuilder.Entity(b => + { + b.ToTable("ChecklistCompletions"); + b.HasKey(x => x.Id); + b.Property(x => x.PersianDate).IsRequired().HasMaxLength(10); + b.Property(x => x.Notes).HasMaxLength(2000); + b.HasOne(x => x.Checklist) + .WithMany(c => c.Completions) + .HasForeignKey(x => x.ChecklistId) + .OnDelete(DeleteBehavior.Cascade); + b.HasOne(x => x.CompletedByUser) + .WithMany() + .HasForeignKey(x => x.CompletedByUserId) + .OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.ChecklistId, x.PersianDate }); + b.HasIndex(x => x.CompletedByUserId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("ChecklistItemCompletions"); + b.HasKey(x => x.Id); + b.Property(x => x.IsChecked).IsRequired(); + b.Property(x => x.Note).HasMaxLength(500); + b.HasOne(x => x.ChecklistCompletion) + .WithMany(cc => cc.ItemCompletions) + .HasForeignKey(x => x.ChecklistCompletionId) + .OnDelete(DeleteBehavior.Cascade); + b.HasOne(x => x.ChecklistItem) + .WithMany() + .HasForeignKey(x => x.ChecklistItemId) + .OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => x.ChecklistCompletionId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("DevicePosts"); + b.HasKey(x => x.Id); + b.Property(x => x.Content).IsRequired().HasMaxLength(5000); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasOne(x => x.AuthorUser) + .WithMany() + .HasForeignKey(x => x.AuthorUserId) + .OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.DeviceId, x.CreatedAt }); + b.HasIndex(x => x.AuthorUserId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("DevicePostImages"); + b.HasKey(x => x.Id); + b.Property(x => x.FileName).IsRequired().HasMaxLength(255); + b.Property(x => x.FilePath).IsRequired().HasMaxLength(500); + b.Property(x => x.ContentType).IsRequired().HasMaxLength(100); + b.HasOne(x => x.DevicePost) + .WithMany(p => p.Images) + .HasForeignKey(x => x.DevicePostId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => x.DevicePostId); + }); + + modelBuilder.Entity(b => + { + b.ToTable("PowerOutageLogs"); + b.HasKey(x => x.Id); + b.Property(x => x.PreviousPower).IsRequired(); + b.Property(x => x.CurrentPower).IsRequired(); + b.Property(x => x.EventType).IsRequired().HasConversion(); + b.Property(x => x.PersianDate).HasMaxLength(10); + b.HasOne(x => x.Device) + .WithMany() + .HasForeignKey(x => x.DeviceId) + .OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => new { x.DeviceId, x.TimestampUtc }); + b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth }); + b.HasIndex(x => x.EventType); + }); + } +} diff --git a/src/GreenHome.Infrastructure/Migrations/20251005195032_init.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251005195032_init.Designer.cs similarity index 94% rename from src/GreenHome.Infrastructure/Migrations/20251005195032_init.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251005195032_init.Designer.cs index ce5dd54..dded918 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251005195032_init.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251005195032_init.Designer.cs @@ -1,114 +1,114 @@ -// -using System; -using GreenHome.Infrastructure; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace GreenHome.Infrastructure.Migrations -{ - [DbContext(typeof(GreenHomeDbContext))] - [Migration("20251005195032_init")] - partial class init - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("GreenHome.Domain.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("DeviceName") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Mobile") - .HasColumnType("nvarchar(max)"); - - b.Property("NeshanLocation") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("nvarchar(80)"); - - b.Property("Owner") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Devices", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("GasPPM") - .HasColumnType("int"); - - b.Property("HumidityPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("Lux") - .HasColumnType("decimal(18,2)"); - - b.Property("PersianDate") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("PersianMonth") - .HasColumnType("int"); - - b.Property("PersianYear") - .HasColumnType("int"); - - b.Property("SoilPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("TemperatureC") - .HasColumnType("decimal(18,2)"); - - b.Property("TimestampUtc") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("DeviceId", "TimestampUtc"); - - b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); - - b.ToTable("Telemetry", (string)null); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using GreenHome.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GreenHome.Infrastructure.Data.Migrations +{ + [DbContext(typeof(GreenHomeDbContext))] + [Migration("20251005195032_init")] + partial class init + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("GreenHome.Domain.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Mobile") + .HasColumnType("nvarchar(max)"); + + b.Property("NeshanLocation") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Devices", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("GasPPM") + .HasColumnType("int"); + + b.Property("HumidityPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("Lux") + .HasColumnType("decimal(18,2)"); + + b.Property("PersianDate") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PersianMonth") + .HasColumnType("int"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("SoilPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("TemperatureC") + .HasColumnType("decimal(18,2)"); + + b.Property("TimestampUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId", "TimestampUtc"); + + b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); + + b.ToTable("Telemetry", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/GreenHome.Infrastructure/Migrations/20251005195032_init.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251005195032_init.cs similarity index 95% rename from src/GreenHome.Infrastructure/Migrations/20251005195032_init.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251005195032_init.cs index e4928bb..7749ba5 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251005195032_init.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251005195032_init.cs @@ -1,74 +1,74 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace GreenHome.Infrastructure.Migrations -{ - /// - public partial class init : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Devices", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - DeviceName = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), - Owner = table.Column(type: "nvarchar(max)", nullable: false), - Mobile = table.Column(type: "nvarchar(max)", nullable: true), - Location = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), - NeshanLocation = table.Column(type: "nvarchar(80)", maxLength: 80, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Devices", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Telemetry", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - DeviceId = table.Column(type: "int", nullable: false), - TimestampUtc = table.Column(type: "datetime2", nullable: false), - TemperatureC = table.Column(type: "decimal(18,2)", nullable: false), - HumidityPercent = table.Column(type: "decimal(18,2)", nullable: false), - SoilPercent = table.Column(type: "decimal(18,2)", nullable: false), - GasPPM = table.Column(type: "int", nullable: false), - Lux = table.Column(type: "decimal(18,2)", nullable: false), - PersianYear = table.Column(type: "int", nullable: false), - PersianMonth = table.Column(type: "int", nullable: false), - PersianDate = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Telemetry", x => x.Id); - }); - - migrationBuilder.CreateIndex( - name: "IX_Telemetry_DeviceId_PersianYear_PersianMonth", - table: "Telemetry", - columns: new[] { "DeviceId", "PersianYear", "PersianMonth" }); - - migrationBuilder.CreateIndex( - name: "IX_Telemetry_DeviceId_TimestampUtc", - table: "Telemetry", - columns: new[] { "DeviceId", "TimestampUtc" }); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Devices"); - - migrationBuilder.DropTable( - name: "Telemetry"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GreenHome.Infrastructure.Data.Migrations +{ + /// + public partial class init : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Devices", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + DeviceName = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false), + Owner = table.Column(type: "nvarchar(max)", nullable: false), + Mobile = table.Column(type: "nvarchar(max)", nullable: true), + Location = table.Column(type: "nvarchar(250)", maxLength: 250, nullable: false), + NeshanLocation = table.Column(type: "nvarchar(80)", maxLength: 80, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Devices", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Telemetry", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + DeviceId = table.Column(type: "int", nullable: false), + TimestampUtc = table.Column(type: "datetime2", nullable: false), + TemperatureC = table.Column(type: "decimal(18,2)", nullable: false), + HumidityPercent = table.Column(type: "decimal(18,2)", nullable: false), + SoilPercent = table.Column(type: "decimal(18,2)", nullable: false), + GasPPM = table.Column(type: "int", nullable: false), + Lux = table.Column(type: "decimal(18,2)", nullable: false), + PersianYear = table.Column(type: "int", nullable: false), + PersianMonth = table.Column(type: "int", nullable: false), + PersianDate = table.Column(type: "nvarchar(10)", maxLength: 10, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Telemetry", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Telemetry_DeviceId_PersianYear_PersianMonth", + table: "Telemetry", + columns: new[] { "DeviceId", "PersianYear", "PersianMonth" }); + + migrationBuilder.CreateIndex( + name: "IX_Telemetry_DeviceId_TimestampUtc", + table: "Telemetry", + columns: new[] { "DeviceId", "TimestampUtc" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Devices"); + + migrationBuilder.DropTable( + name: "Telemetry"); + } + } +} diff --git a/src/GreenHome.Infrastructure/Migrations/20251008205106_addminmaxdanger.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251008205106_addminmaxdanger.Designer.cs similarity index 95% rename from src/GreenHome.Infrastructure/Migrations/20251008205106_addminmaxdanger.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251008205106_addminmaxdanger.Designer.cs index 733ad28..91db522 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251008205106_addminmaxdanger.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251008205106_addminmaxdanger.Designer.cs @@ -1,180 +1,180 @@ -// -using System; -using GreenHome.Infrastructure; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace GreenHome.Infrastructure.Migrations -{ - [DbContext(typeof(GreenHomeDbContext))] - [Migration("20251008205106_addminmaxdanger")] - partial class addminmaxdanger - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("GreenHome.Domain.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("DeviceName") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("Mobile") - .HasColumnType("nvarchar(max)"); - - b.Property("NeshanLocation") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("nvarchar(80)"); - - b.Property("Owner") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Devices", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("DangerMaxTemperature") - .HasColumnType("decimal(18,2)"); - - b.Property("DangerMinTemperature") - .HasColumnType("decimal(18,2)"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("MaxGasPPM") - .HasColumnType("int"); - - b.Property("MaxHumidityPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("MaxLux") - .HasColumnType("decimal(18,2)"); - - b.Property("MaxTemperature") - .HasColumnType("decimal(18,2)"); - - b.Property("MinGasPPM") - .HasColumnType("int"); - - b.Property("MinHumidityPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("MinLux") - .HasColumnType("decimal(18,2)"); - - b.Property("MinTemperature") - .HasColumnType("decimal(18,2)"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("DeviceId") - .IsUnique(); - - b.ToTable("DeviceSettings", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("GasPPM") - .HasColumnType("int"); - - b.Property("HumidityPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("Lux") - .HasColumnType("decimal(18,2)"); - - b.Property("PersianDate") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("PersianMonth") - .HasColumnType("int"); - - b.Property("PersianYear") - .HasColumnType("int"); - - b.Property("SoilPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("TemperatureC") - .HasColumnType("decimal(18,2)"); - - b.Property("TimestampUtc") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("DeviceId", "TimestampUtc"); - - b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); - - b.ToTable("Telemetry", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => - { - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Device"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using GreenHome.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GreenHome.Infrastructure.Data.Migrations +{ + [DbContext(typeof(GreenHomeDbContext))] + [Migration("20251008205106_addminmaxdanger")] + partial class addminmaxdanger + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("GreenHome.Domain.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("Mobile") + .HasColumnType("nvarchar(max)"); + + b.Property("NeshanLocation") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("Owner") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Devices", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DangerMaxTemperature") + .HasColumnType("decimal(18,2)"); + + b.Property("DangerMinTemperature") + .HasColumnType("decimal(18,2)"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("MaxGasPPM") + .HasColumnType("int"); + + b.Property("MaxHumidityPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("MaxLux") + .HasColumnType("decimal(18,2)"); + + b.Property("MaxTemperature") + .HasColumnType("decimal(18,2)"); + + b.Property("MinGasPPM") + .HasColumnType("int"); + + b.Property("MinHumidityPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("MinLux") + .HasColumnType("decimal(18,2)"); + + b.Property("MinTemperature") + .HasColumnType("decimal(18,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.ToTable("DeviceSettings", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("GasPPM") + .HasColumnType("int"); + + b.Property("HumidityPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("Lux") + .HasColumnType("decimal(18,2)"); + + b.Property("PersianDate") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PersianMonth") + .HasColumnType("int"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("SoilPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("TemperatureC") + .HasColumnType("decimal(18,2)"); + + b.Property("TimestampUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId", "TimestampUtc"); + + b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); + + b.ToTable("Telemetry", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/GreenHome.Infrastructure/Migrations/20251008205106_addminmaxdanger.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251008205106_addminmaxdanger.cs similarity index 95% rename from src/GreenHome.Infrastructure/Migrations/20251008205106_addminmaxdanger.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251008205106_addminmaxdanger.cs index 5223620..34f4785 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251008205106_addminmaxdanger.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251008205106_addminmaxdanger.cs @@ -1,59 +1,59 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace GreenHome.Infrastructure.Migrations -{ - /// - public partial class addminmaxdanger : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "DeviceSettings", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - DeviceId = table.Column(type: "int", nullable: false), - DangerMaxTemperature = table.Column(type: "decimal(18,2)", nullable: false), - DangerMinTemperature = table.Column(type: "decimal(18,2)", nullable: false), - MaxTemperature = table.Column(type: "decimal(18,2)", nullable: false), - MinTemperature = table.Column(type: "decimal(18,2)", nullable: false), - MaxGasPPM = table.Column(type: "int", nullable: false), - MinGasPPM = table.Column(type: "int", nullable: false), - MaxLux = table.Column(type: "decimal(18,2)", nullable: false), - MinLux = table.Column(type: "decimal(18,2)", nullable: false), - MaxHumidityPercent = table.Column(type: "decimal(18,2)", nullable: false), - MinHumidityPercent = table.Column(type: "decimal(18,2)", nullable: false), - CreatedAt = table.Column(type: "datetime2", nullable: false), - UpdatedAt = table.Column(type: "datetime2", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DeviceSettings", x => x.Id); - table.ForeignKey( - name: "FK_DeviceSettings_Devices_DeviceId", - column: x => x.DeviceId, - principalTable: "Devices", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_DeviceSettings_DeviceId", - table: "DeviceSettings", - column: "DeviceId", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "DeviceSettings"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GreenHome.Infrastructure.Data.Migrations +{ + /// + public partial class addminmaxdanger : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "DeviceSettings", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + DeviceId = table.Column(type: "int", nullable: false), + DangerMaxTemperature = table.Column(type: "decimal(18,2)", nullable: false), + DangerMinTemperature = table.Column(type: "decimal(18,2)", nullable: false), + MaxTemperature = table.Column(type: "decimal(18,2)", nullable: false), + MinTemperature = table.Column(type: "decimal(18,2)", nullable: false), + MaxGasPPM = table.Column(type: "int", nullable: false), + MinGasPPM = table.Column(type: "int", nullable: false), + MaxLux = table.Column(type: "decimal(18,2)", nullable: false), + MinLux = table.Column(type: "decimal(18,2)", nullable: false), + MaxHumidityPercent = table.Column(type: "decimal(18,2)", nullable: false), + MinHumidityPercent = table.Column(type: "decimal(18,2)", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceSettings", x => x.Id); + table.ForeignKey( + name: "FK_DeviceSettings_Devices_DeviceId", + column: x => x.DeviceId, + principalTable: "Devices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_DeviceSettings_DeviceId", + table: "DeviceSettings", + column: "DeviceId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "DeviceSettings"); + } + } +} diff --git a/src/GreenHome.Infrastructure/Migrations/20251118204845_UpdateDeviceUserRelationship.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251118204845_UpdateDeviceUserRelationship.Designer.cs similarity index 98% rename from src/GreenHome.Infrastructure/Migrations/20251118204845_UpdateDeviceUserRelationship.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251118204845_UpdateDeviceUserRelationship.Designer.cs index 69dbaf6..d5f8ab9 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251118204845_UpdateDeviceUserRelationship.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251118204845_UpdateDeviceUserRelationship.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251118204845_UpdateDeviceUserRelationship")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251118204845_UpdateDeviceUserRelationship.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251118204845_UpdateDeviceUserRelationship.cs similarity index 98% rename from src/GreenHome.Infrastructure/Migrations/20251118204845_UpdateDeviceUserRelationship.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251118204845_UpdateDeviceUserRelationship.cs index 2f6a99b..ae09990 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251118204845_UpdateDeviceUserRelationship.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251118204845_UpdateDeviceUserRelationship.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class UpdateDeviceUserRelationship : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251119133827_AddUserRolesAndDeviceUsers.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119133827_AddUserRolesAndDeviceUsers.Designer.cs similarity index 98% rename from src/GreenHome.Infrastructure/Migrations/20251119133827_AddUserRolesAndDeviceUsers.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119133827_AddUserRolesAndDeviceUsers.Designer.cs index 2b5e10f..39ea882 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251119133827_AddUserRolesAndDeviceUsers.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119133827_AddUserRolesAndDeviceUsers.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251119133827_AddUserRolesAndDeviceUsers")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251119133827_AddUserRolesAndDeviceUsers.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119133827_AddUserRolesAndDeviceUsers.cs similarity index 95% rename from src/GreenHome.Infrastructure/Migrations/20251119133827_AddUserRolesAndDeviceUsers.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119133827_AddUserRolesAndDeviceUsers.cs index 37b0e88..79e1b10 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251119133827_AddUserRolesAndDeviceUsers.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119133827_AddUserRolesAndDeviceUsers.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddUserRolesAndDeviceUsers : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251119135914_AddAlertNotifications.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119135914_AddAlertNotifications.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251119135914_AddAlertNotifications.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119135914_AddAlertNotifications.Designer.cs index 27ec36a..26a893a 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251119135914_AddAlertNotifications.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119135914_AddAlertNotifications.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251119135914_AddAlertNotifications")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251119135914_AddAlertNotifications.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119135914_AddAlertNotifications.cs similarity index 97% rename from src/GreenHome.Infrastructure/Migrations/20251119135914_AddAlertNotifications.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119135914_AddAlertNotifications.cs index 989e347..f62f3a3 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251119135914_AddAlertNotifications.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119135914_AddAlertNotifications.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddAlertNotifications : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251119142729_FixAlertNotifications.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119142729_FixAlertNotifications.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251119142729_FixAlertNotifications.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119142729_FixAlertNotifications.Designer.cs index 32bab47..09782f9 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251119142729_FixAlertNotifications.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119142729_FixAlertNotifications.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251119142729_FixAlertNotifications")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251119142729_FixAlertNotifications.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119142729_FixAlertNotifications.cs similarity index 91% rename from src/GreenHome.Infrastructure/Migrations/20251119142729_FixAlertNotifications.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119142729_FixAlertNotifications.cs index ad0b9a0..31e0b55 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251119142729_FixAlertNotifications.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251119142729_FixAlertNotifications.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class FixAlertNotifications : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs index b6b9f10..81298b5 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251127125625_AddServerTimestampUtc")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251127125625_AddServerTimestampUtc.cs similarity index 94% rename from src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251127125625_AddServerTimestampUtc.cs index 768c39b..3b4f298 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251127125625_AddServerTimestampUtc.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddServerTimestampUtc : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216113127_AddAIQueryTable.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216113127_AddAIQueryTable.Designer.cs index 85e66ab..ff75649 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216113127_AddAIQueryTable.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251216113127_AddAIQueryTable")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216113127_AddAIQueryTable.cs similarity index 97% rename from src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216113127_AddAIQueryTable.cs index 923bd34..d55ac53 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216113127_AddAIQueryTable.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216113127_AddAIQueryTable.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddAIQueryTable : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216120357_adddailyreport.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216120357_adddailyreport.Designer.cs index 71bdc3b..b52400c 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216120357_adddailyreport.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251216120357_adddailyreport")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216120357_adddailyreport.cs similarity index 97% rename from src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216120357_adddailyreport.cs index a58ba68..c5c89ec 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216120357_adddailyreport.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216120357_adddailyreport.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class adddailyreport : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs index a92feec..74ae0c7 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216131032_UpdateAlertSystemWithConditions.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251216131032_UpdateAlertSystemWithConditions")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216131032_UpdateAlertSystemWithConditions.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216131032_UpdateAlertSystemWithConditions.cs index eb8b5ce..41ca524 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216131032_UpdateAlertSystemWithConditions.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216131032_UpdateAlertSystemWithConditions.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class UpdateAlertSystemWithConditions : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251216152746_AddDailyReportsTable.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216152746_AddDailyReportsTable.cs similarity index 98% rename from src/GreenHome.Infrastructure/Migrations/20251216152746_AddDailyReportsTable.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216152746_AddDailyReportsTable.cs index 45c44f9..b392811 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216152746_AddDailyReportsTable.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216152746_AddDailyReportsTable.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddDailyReportsTable : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251216204600_AddAllNewFeatures.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216204600_AddAllNewFeatures.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251216204600_AddAllNewFeatures.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216204600_AddAllNewFeatures.Designer.cs index d795020..7e92144 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216204600_AddAllNewFeatures.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216204600_AddAllNewFeatures.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251216204600_AddAllNewFeatures")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251216204600_AddAllNewFeatures.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216204600_AddAllNewFeatures.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251216204600_AddAllNewFeatures.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216204600_AddAllNewFeatures.cs index 1448676..084e286 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251216204600_AddAllNewFeatures.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251216204600_AddAllNewFeatures.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddAllNewFeatures : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs index 542092a..fbf8610 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20251217154130_AddDeviceTokenAndPhoneFields")] diff --git a/src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.cs similarity index 97% rename from src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.cs index 2f2f2eb..d37350b 100644 --- a/src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.cs @@ -1,9 +1,9 @@ -using System; +using System; using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddDeviceTokenAndPhoneFields : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.Designer.cs index 3db7e05..665168d 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20260106150615_POWER_VOLTAGE_FIELDS")] diff --git a/src/GreenHome.Infrastructure/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.cs similarity index 90% rename from src/GreenHome.Infrastructure/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.cs index edea11b..93f8af1 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106150615_POWER_VOLTAGE_FIELDS.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class POWER_VOLTAGE_FIELDS : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20260106151206_FIX_VOLTAGE_TYPE.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106151206_FIX_VOLTAGE_TYPE.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20260106151206_FIX_VOLTAGE_TYPE.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106151206_FIX_VOLTAGE_TYPE.Designer.cs index 75b1669..2a9afd4 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260106151206_FIX_VOLTAGE_TYPE.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106151206_FIX_VOLTAGE_TYPE.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20260106151206_FIX_VOLTAGE_TYPE")] diff --git a/src/GreenHome.Infrastructure/Migrations/20260106151206_FIX_VOLTAGE_TYPE.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106151206_FIX_VOLTAGE_TYPE.cs similarity index 90% rename from src/GreenHome.Infrastructure/Migrations/20260106151206_FIX_VOLTAGE_TYPE.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106151206_FIX_VOLTAGE_TYPE.cs index 0007b8d..799e58c 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260106151206_FIX_VOLTAGE_TYPE.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260106151206_FIX_VOLTAGE_TYPE.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class FIX_VOLTAGE_TYPE : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20260107144244_AddSmsRecievers.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260107144244_AddSmsRecievers.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20260107144244_AddSmsRecievers.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260107144244_AddSmsRecievers.Designer.cs index f8fe4b3..60f356f 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260107144244_AddSmsRecievers.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260107144244_AddSmsRecievers.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20260107144244_AddSmsRecievers")] diff --git a/src/GreenHome.Infrastructure/Migrations/20260107144244_AddSmsRecievers.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260107144244_AddSmsRecievers.cs similarity index 94% rename from src/GreenHome.Infrastructure/Migrations/20260107144244_AddSmsRecievers.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260107144244_AddSmsRecievers.cs index a7e92dc..34a79b3 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260107144244_AddSmsRecievers.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260107144244_AddSmsRecievers.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddSmsRecievers : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20260113032844_AddBatteryInfoFields.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260113032844_AddBatteryInfoFields.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20260113032844_AddBatteryInfoFields.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260113032844_AddBatteryInfoFields.Designer.cs index 55a9ccc..7c78406 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260113032844_AddBatteryInfoFields.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260113032844_AddBatteryInfoFields.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20260113032844_AddBatteryInfoFields")] diff --git a/src/GreenHome.Infrastructure/Migrations/20260113032844_AddBatteryInfoFields.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260113032844_AddBatteryInfoFields.cs similarity index 90% rename from src/GreenHome.Infrastructure/Migrations/20260113032844_AddBatteryInfoFields.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260113032844_AddBatteryInfoFields.cs index 20bd24a..b99a42a 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260113032844_AddBatteryInfoFields.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260113032844_AddBatteryInfoFields.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddBatteryInfoFields : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs similarity index 99% rename from src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs index c3a83c6..8d29f5d 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260428054608_AddRelayCountToDeviceSettings.Designer.cs @@ -1,6 +1,6 @@ -// +// using System; -using GreenHome.Infrastructure; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { [DbContext(typeof(GreenHomeDbContext))] [Migration("20260428054608_AddRelayCountToDeviceSettings")] diff --git a/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs similarity index 87% rename from src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs index 1a364a7..7a7bdf0 100644 --- a/src/GreenHome.Infrastructure/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260428054608_AddRelayCountToDeviceSettings.cs @@ -1,8 +1,8 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace GreenHome.Infrastructure.Migrations +namespace GreenHome.Infrastructure.Data.Migrations { /// public partial class AddRelayCountToDeviceSettings : Migration diff --git a/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/GreenHomeDbContextModelSnapshot.cs similarity index 95% rename from src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs rename to src/GreenHome.Infrastructure/Data/Migrations/Migrations/GreenHomeDbContextModelSnapshot.cs index 9e1dbb6..ec04636 100644 --- a/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs +++ b/src/GreenHome.Infrastructure/Data/Migrations/Migrations/GreenHomeDbContextModelSnapshot.cs @@ -1,1248 +1,1307 @@ -// -using System; -using GreenHome.Infrastructure; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace GreenHome.Infrastructure.Migrations -{ - [DbContext(typeof(GreenHomeDbContext))] - partial class GreenHomeDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "9.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("GreenHome.Domain.AIQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("Answer") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CompletionTokens") - .HasColumnType("int"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("Model") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("PromptTokens") - .HasColumnType("int"); - - b.Property("Question") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("ResponseTimeMs") - .HasColumnType("bigint"); - - b.Property("Temperature") - .HasColumnType("float"); - - b.Property("TotalTokens") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("DeviceId"); - - b.HasIndex("UserId"); - - b.ToTable("AIQueries", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertCondition", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("CallCooldownMinutes") - .HasColumnType("int"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("IsEnabled") - .HasColumnType("bit"); - - b.Property("NotificationType") - .HasColumnType("int"); - - b.Property("SmsCooldownMinutes") - .HasColumnType("int"); - - b.Property("TimeType") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("DeviceId"); - - b.ToTable("AlertConditions", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("AlertConditionId") - .HasColumnType("int"); - - b.Property("AlertType") - .HasColumnType("int"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("ErrorMessage") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("NotificationType") - .HasColumnType("int"); - - b.Property("PhoneNumber") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("nvarchar(20)"); - - b.Property("ProcessingTimeMs") - .HasColumnType("bigint"); - - b.Property("SentAt") - .HasColumnType("datetime2"); - - b.Property("Status") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("AlertConditionId"); - - b.HasIndex("AlertType"); - - b.HasIndex("Status"); - - b.HasIndex("DeviceId", "SentAt"); - - b.HasIndex("UserId", "SentAt"); - - b.ToTable("AlertLogs", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertNotification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("AlertConditionId") - .HasColumnType("int"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("ErrorMessage") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("IsSent") - .HasColumnType("bit"); - - b.Property("Message") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("MessageOutboxIds") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("NotificationType") - .HasColumnType("int"); - - b.Property("SentAt") - .HasColumnType("datetime2"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("AlertConditionId"); - - b.HasIndex("UserId"); - - b.HasIndex("DeviceId", "UserId", "AlertConditionId", "SentAt"); - - b.ToTable("AlertNotifications", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertRule", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("AlertConditionId") - .HasColumnType("int"); - - b.Property("ComparisonType") - .HasColumnType("int"); - - b.Property("Order") - .HasColumnType("int"); - - b.Property("SensorType") - .HasColumnType("int"); - - b.Property("Value1") - .HasColumnType("decimal(18,2)"); - - b.Property("Value2") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("AlertConditionId"); - - b.ToTable("AlertRules", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.Checklist", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("CreatedByUserId") - .HasColumnType("int"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("IsActive") - .HasColumnType("bit"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.HasKey("Id"); - - b.HasIndex("CreatedByUserId"); - - b.HasIndex("DeviceId", "IsActive"); - - b.ToTable("Checklists", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ChecklistId") - .HasColumnType("int"); - - b.Property("CompletedAt") - .HasColumnType("datetime2"); - - b.Property("CompletedByUserId") - .HasColumnType("int"); - - b.Property("Notes") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("PersianDate") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.HasKey("Id"); - - b.HasIndex("CompletedByUserId"); - - b.HasIndex("ChecklistId", "PersianDate"); - - b.ToTable("ChecklistCompletions", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.ChecklistItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ChecklistId") - .HasColumnType("int"); - - b.Property("Description") - .HasMaxLength(1000) - .HasColumnType("nvarchar(1000)"); - - b.Property("IsRequired") - .HasColumnType("bit"); - - b.Property("Order") - .HasColumnType("int"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(300) - .HasColumnType("nvarchar(300)"); - - b.HasKey("Id"); - - b.HasIndex("ChecklistId", "Order"); - - b.ToTable("ChecklistItems", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.ChecklistItemCompletion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ChecklistCompletionId") - .HasColumnType("int"); - - b.Property("ChecklistItemId") - .HasColumnType("int"); - - b.Property("IsChecked") - .HasColumnType("bit"); - - b.Property("Note") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.HasKey("Id"); - - b.HasIndex("ChecklistCompletionId"); - - b.HasIndex("ChecklistItemId"); - - b.ToTable("ChecklistItemCompletions", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.DailyReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("Analysis") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("CompletionTokens") - .HasColumnType("int"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("Model") - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("PersianDate") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("PersianDay") - .HasColumnType("int"); - - b.Property("PersianMonth") - .HasColumnType("int"); - - b.Property("PersianYear") - .HasColumnType("int"); - - b.Property("PromptTokens") - .HasColumnType("int"); - - b.Property("RecordCount") - .HasColumnType("int"); - - b.Property("ResponseTimeMs") - .HasColumnType("bigint"); - - b.Property("SampledRecordCount") - .HasColumnType("int"); - - b.Property("TotalTokens") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("CreatedAt"); - - b.HasIndex("DeviceId", "PersianDate") - .IsUnique(); - - b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); - - b.ToTable("DailyReports", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.Device", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("DeviceName") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("Location") - .IsRequired() - .HasMaxLength(250) - .HasColumnType("nvarchar(250)"); - - b.Property("NeshanLocation") - .IsRequired() - .HasMaxLength(80) - .HasColumnType("nvarchar(80)"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("Devices", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.DevicePost", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("AuthorUserId") - .HasColumnType("int"); - - b.Property("Content") - .IsRequired() - .HasMaxLength(5000) - .HasColumnType("nvarchar(max)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("AuthorUserId"); - - b.HasIndex("DeviceId", "CreatedAt"); - - b.ToTable("DevicePosts", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.DevicePostImage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ContentType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("DevicePostId") - .HasColumnType("int"); - - b.Property("FileName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("FilePath") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FileSize") - .HasColumnType("bigint"); - - b.Property("UploadedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("DevicePostId"); - - b.ToTable("DevicePostImages", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("AreaSquareMeters") - .HasColumnType("decimal(18,2)"); - - b.Property("City") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("DeviceId1") - .HasColumnType("int"); - - b.Property("DevicePhoneNumber") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Latitude") - .HasColumnType("decimal(9,6)"); - - b.Property("Longitude") - .HasColumnType("decimal(9,6)"); - - b.Property("MinimumCallIntervalMinutes") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(60); - - b.Property("MinimumSmsIntervalMinutes") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(15); - - b.Property("ProductType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Province") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("RelayCount") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(4); - - b.Property("SimCardType") - .HasColumnType("int"); - - b.Property("SmsRecievers") - .IsRequired() - .HasMaxLength(120) - .HasColumnType("nvarchar(120)"); - - b.Property("TokenCode") - .HasColumnType("nvarchar(max)"); - - b.Property("TokenExpiresAt") - .HasColumnType("datetime2"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.Property("UploadIntervalMin") - .HasColumnType("int"); - - b.Property("VerificationCode") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.HasIndex("DeviceId") - .IsUnique(); - - b.HasIndex("DeviceId1"); - - b.ToTable("DeviceSettings", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.DeviceUser", b => - { - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.Property("ReceiveAlerts") - .ValueGeneratedOnAdd() - .HasColumnType("bit") - .HasDefaultValue(true); - - b.HasKey("DeviceId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("DeviceUsers", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.ReportImage", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("ContentType") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Description") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FileName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("nvarchar(255)"); - - b.Property("FilePath") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); - - b.Property("FileSize") - .HasColumnType("bigint"); - - b.Property("UploadedAt") - .HasColumnType("datetime2"); - - b.Property("UserDailyReportId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("UserDailyReportId"); - - b.ToTable("ReportImages", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("BatteryPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("BatteryVoltage") - .HasColumnType("decimal(18,2)"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("GasPPM") - .HasColumnType("int"); - - b.Property("HumidityPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("Lux") - .HasColumnType("decimal(18,2)"); - - b.Property("PersianDate") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("PersianMonth") - .HasColumnType("int"); - - b.Property("PersianYear") - .HasColumnType("int"); - - b.Property("Power") - .HasColumnType("tinyint"); - - b.Property("ServerTimestampUtc") - .HasColumnType("datetime2"); - - b.Property("SoilPercent") - .HasColumnType("decimal(18,2)"); - - b.Property("TemperatureC") - .HasColumnType("decimal(18,2)"); - - b.Property("TimestampUtc") - .HasColumnType("datetime2"); - - b.Property("Voltage") - .HasColumnType("decimal(18,2)"); - - b.HasKey("Id"); - - b.HasIndex("DeviceId", "ServerTimestampUtc"); - - b.HasIndex("DeviceId", "TimestampUtc"); - - b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); - - b.ToTable("Telemetry", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("Family") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("LastLoginAt") - .HasColumnType("datetime2"); - - b.Property("Mobile") - .IsRequired() - .HasMaxLength(11) - .HasColumnType("nvarchar(11)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); - - b.Property("Role") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("Mobile") - .IsUnique(); - - b.ToTable("Users", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("DeviceId") - .HasColumnType("int"); - - b.Property("Notes") - .HasMaxLength(2000) - .HasColumnType("nvarchar(2000)"); - - b.Property("Observations") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Operations") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("PersianDate") - .IsRequired() - .HasMaxLength(10) - .HasColumnType("nvarchar(10)"); - - b.Property("PersianDay") - .HasColumnType("int"); - - b.Property("PersianMonth") - .HasColumnType("int"); - - b.Property("PersianYear") - .HasColumnType("int"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("nvarchar(200)"); - - b.Property("UpdatedAt") - .HasColumnType("datetime2"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.HasIndex("DeviceId", "PersianDate"); - - b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); - - b.ToTable("UserDailyReports", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.VerificationCode", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("Code") - .IsRequired() - .HasMaxLength(4) - .HasColumnType("nvarchar(4)"); - - b.Property("CreatedAt") - .HasColumnType("datetime2"); - - b.Property("ExpiresAt") - .HasColumnType("datetime2"); - - b.Property("IsUsed") - .HasColumnType("bit"); - - b.Property("Mobile") - .IsRequired() - .HasMaxLength(11) - .HasColumnType("nvarchar(11)"); - - b.Property("UsedAt") - .HasColumnType("datetime2"); - - b.HasKey("Id"); - - b.HasIndex("Mobile", "Code", "IsUsed"); - - b.ToTable("VerificationCodes", (string)null); - }); - - modelBuilder.Entity("GreenHome.Domain.AIQuery", b => - { - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("GreenHome.Domain.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull); - - b.Navigation("Device"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertCondition", b => - { - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Device"); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertLog", b => - { - b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition") - .WithMany() - .HasForeignKey("AlertConditionId") - .OnDelete(DeleteBehavior.SetNull); - - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("GreenHome.Domain.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("AlertCondition"); - - b.Navigation("Device"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertNotification", b => - { - b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition") - .WithMany() - .HasForeignKey("AlertConditionId") - .OnDelete(DeleteBehavior.Restrict); - - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("GreenHome.Domain.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("AlertCondition"); - - b.Navigation("Device"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertRule", b => - { - b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition") - .WithMany("Rules") - .HasForeignKey("AlertConditionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("AlertCondition"); - }); - - modelBuilder.Entity("GreenHome.Domain.Checklist", b => - { - b.HasOne("GreenHome.Domain.User", "CreatedByUser") - .WithMany() - .HasForeignKey("CreatedByUserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("CreatedByUser"); - - b.Navigation("Device"); - }); - - modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b => - { - b.HasOne("GreenHome.Domain.Checklist", "Checklist") - .WithMany("Completions") - .HasForeignKey("ChecklistId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("GreenHome.Domain.User", "CompletedByUser") - .WithMany() - .HasForeignKey("CompletedByUserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Checklist"); - - b.Navigation("CompletedByUser"); - }); - - modelBuilder.Entity("GreenHome.Domain.ChecklistItem", b => - { - b.HasOne("GreenHome.Domain.Checklist", "Checklist") - .WithMany("Items") - .HasForeignKey("ChecklistId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Checklist"); - }); - - modelBuilder.Entity("GreenHome.Domain.ChecklistItemCompletion", b => - { - b.HasOne("GreenHome.Domain.ChecklistCompletion", "ChecklistCompletion") - .WithMany("ItemCompletions") - .HasForeignKey("ChecklistCompletionId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("GreenHome.Domain.ChecklistItem", "ChecklistItem") - .WithMany() - .HasForeignKey("ChecklistItemId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("ChecklistCompletion"); - - b.Navigation("ChecklistItem"); - }); - - modelBuilder.Entity("GreenHome.Domain.DailyReport", b => - { - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Device"); - }); - - modelBuilder.Entity("GreenHome.Domain.Device", b => - { - b.HasOne("GreenHome.Domain.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("GreenHome.Domain.DevicePost", b => - { - b.HasOne("GreenHome.Domain.User", "AuthorUser") - .WithMany() - .HasForeignKey("AuthorUserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("AuthorUser"); - - b.Navigation("Device"); - }); - - modelBuilder.Entity("GreenHome.Domain.DevicePostImage", b => - { - b.HasOne("GreenHome.Domain.DevicePost", "DevicePost") - .WithMany("Images") - .HasForeignKey("DevicePostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DevicePost"); - }); - - modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => - { - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("GreenHome.Domain.Device", null) - .WithMany("DeviceSettings") - .HasForeignKey("DeviceId1"); - - b.Navigation("Device"); - }); - - modelBuilder.Entity("GreenHome.Domain.DeviceUser", b => - { - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany("DeviceUsers") - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("GreenHome.Domain.User", "User") - .WithMany("DeviceUsers") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Device"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("GreenHome.Domain.ReportImage", b => - { - b.HasOne("GreenHome.Domain.UserDailyReport", "UserDailyReport") - .WithMany("Images") - .HasForeignKey("UserDailyReportId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("UserDailyReport"); - }); - - modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b => - { - b.HasOne("GreenHome.Domain.Device", "Device") - .WithMany() - .HasForeignKey("DeviceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("GreenHome.Domain.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Device"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("GreenHome.Domain.AlertCondition", b => - { - b.Navigation("Rules"); - }); - - modelBuilder.Entity("GreenHome.Domain.Checklist", b => - { - b.Navigation("Completions"); - - b.Navigation("Items"); - }); - - modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b => - { - b.Navigation("ItemCompletions"); - }); - - modelBuilder.Entity("GreenHome.Domain.Device", b => - { - b.Navigation("DeviceSettings"); - - b.Navigation("DeviceUsers"); - }); - - modelBuilder.Entity("GreenHome.Domain.DevicePost", b => - { - b.Navigation("Images"); - }); - - modelBuilder.Entity("GreenHome.Domain.User", b => - { - b.Navigation("DeviceUsers"); - }); - - modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b => - { - b.Navigation("Images"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using GreenHome.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace GreenHome.Infrastructure.Data.Migrations +{ + [DbContext(typeof(GreenHomeDbContext))] + partial class GreenHomeDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("GreenHome.Domain.AIQuery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Answer") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompletionTokens") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("Model") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PromptTokens") + .HasColumnType("int"); + + b.Property("Question") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResponseTimeMs") + .HasColumnType("bigint"); + + b.Property("Temperature") + .HasColumnType("float"); + + b.Property("TotalTokens") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DeviceId"); + + b.HasIndex("UserId"); + + b.ToTable("AIQueries", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertCondition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CallCooldownMinutes") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("IsEnabled") + .HasColumnType("bit"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("SmsCooldownMinutes") + .HasColumnType("int"); + + b.Property("TimeType") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.ToTable("AlertConditions", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AlertConditionId") + .HasColumnType("int"); + + b.Property("AlertType") + .HasColumnType("int"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("PhoneNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("ProcessingTimeMs") + .HasColumnType("bigint"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AlertConditionId"); + + b.HasIndex("AlertType"); + + b.HasIndex("Status"); + + b.HasIndex("DeviceId", "SentAt"); + + b.HasIndex("UserId", "SentAt"); + + b.ToTable("AlertLogs", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertNotification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AlertConditionId") + .HasColumnType("int"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsSent") + .HasColumnType("bit"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("MessageOutboxIds") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("NotificationType") + .HasColumnType("int"); + + b.Property("SentAt") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AlertConditionId"); + + b.HasIndex("UserId"); + + b.HasIndex("DeviceId", "UserId", "AlertConditionId", "SentAt"); + + b.ToTable("AlertNotifications", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AlertConditionId") + .HasColumnType("int"); + + b.Property("ComparisonType") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("SensorType") + .HasColumnType("int"); + + b.Property("Value1") + .HasColumnType("decimal(18,2)"); + + b.Property("Value2") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("AlertConditionId"); + + b.ToTable("AlertRules", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.Checklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedByUserId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("DeviceId", "IsActive"); + + b.ToTable("Checklists", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChecklistId") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CompletedByUserId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("PersianDate") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.HasKey("Id"); + + b.HasIndex("CompletedByUserId"); + + b.HasIndex("ChecklistId", "PersianDate"); + + b.ToTable("ChecklistCompletions", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.ChecklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChecklistId") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsRequired") + .HasColumnType("bit"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.HasKey("Id"); + + b.HasIndex("ChecklistId", "Order"); + + b.ToTable("ChecklistItems", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.ChecklistItemCompletion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChecklistCompletionId") + .HasColumnType("int"); + + b.Property("ChecklistItemId") + .HasColumnType("int"); + + b.Property("IsChecked") + .HasColumnType("bit"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("ChecklistCompletionId"); + + b.HasIndex("ChecklistItemId"); + + b.ToTable("ChecklistItemCompletions", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.DailyReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Analysis") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompletionTokens") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("Model") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("PersianDate") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PersianDay") + .HasColumnType("int"); + + b.Property("PersianMonth") + .HasColumnType("int"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("PromptTokens") + .HasColumnType("int"); + + b.Property("RecordCount") + .HasColumnType("int"); + + b.Property("ResponseTimeMs") + .HasColumnType("bigint"); + + b.Property("SampledRecordCount") + .HasColumnType("int"); + + b.Property("TotalTokens") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DeviceId", "PersianDate") + .IsUnique(); + + b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); + + b.ToTable("DailyReports", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("DeviceName") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("nvarchar(250)"); + + b.Property("NeshanLocation") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("nvarchar(80)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Devices", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.DevicePost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(5000) + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId"); + + b.HasIndex("DeviceId", "CreatedAt"); + + b.ToTable("DevicePosts", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.DevicePostImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("DevicePostId") + .HasColumnType("int"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("UploadedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("DevicePostId"); + + b.ToTable("DevicePostImages", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AreaSquareMeters") + .HasColumnType("decimal(18,2)"); + + b.Property("City") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("DeviceId1") + .HasColumnType("int"); + + b.Property("DevicePhoneNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .HasColumnType("decimal(9,6)"); + + b.Property("Longitude") + .HasColumnType("decimal(9,6)"); + + b.Property("MinimumCallIntervalMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(60); + + b.Property("MinimumSmsIntervalMinutes") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(15); + + b.Property("ProductType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Province") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RelayCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(4); + + b.Property("SimCardType") + .HasColumnType("int"); + + b.Property("SmsRecievers") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("TokenCode") + .HasColumnType("nvarchar(max)"); + + b.Property("TokenExpiresAt") + .HasColumnType("datetime2"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UploadIntervalMin") + .HasColumnType("int"); + + b.Property("VerificationCode") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId") + .IsUnique(); + + b.HasIndex("DeviceId1"); + + b.ToTable("DeviceSettings", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.DeviceUser", b => + { + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("ReceiveAlerts") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.HasKey("DeviceId", "UserId"); + + b.HasIndex("UserId"); + + b.ToTable("DeviceUsers", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.PowerOutageLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CurrentPower") + .HasColumnType("tinyint"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("EventType") + .HasColumnType("int"); + + b.Property("PersianDate") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PersianMonth") + .HasColumnType("int"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("PreviousPower") + .HasColumnType("tinyint"); + + b.Property("ServerTimestampUtc") + .HasColumnType("datetime2"); + + b.Property("TimestampUtc") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("EventType"); + + b.HasIndex("DeviceId", "TimestampUtc"); + + b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); + + b.ToTable("PowerOutageLogs", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.ReportImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("UploadedAt") + .HasColumnType("datetime2"); + + b.Property("UserDailyReportId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserDailyReportId"); + + b.ToTable("ReportImages", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BatteryPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("BatteryVoltage") + .HasColumnType("decimal(18,2)"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("GasPPM") + .HasColumnType("int"); + + b.Property("HumidityPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("Lux") + .HasColumnType("decimal(18,2)"); + + b.Property("PersianDate") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PersianMonth") + .HasColumnType("int"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("Power") + .HasColumnType("tinyint"); + + b.Property("ServerTimestampUtc") + .HasColumnType("datetime2"); + + b.Property("SoilPercent") + .HasColumnType("decimal(18,2)"); + + b.Property("TemperatureC") + .HasColumnType("decimal(18,2)"); + + b.Property("TimestampUtc") + .HasColumnType("datetime2"); + + b.Property("Voltage") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId", "ServerTimestampUtc"); + + b.HasIndex("DeviceId", "TimestampUtc"); + + b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); + + b.ToTable("Telemetry", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Family") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LastLoginAt") + .HasColumnType("datetime2"); + + b.Property("Mobile") + .IsRequired() + .HasMaxLength(11) + .HasColumnType("nvarchar(11)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Role") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Mobile") + .IsUnique(); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DeviceId") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Observations") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Operations") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PersianDate") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PersianDay") + .HasColumnType("int"); + + b.Property("PersianMonth") + .HasColumnType("int"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("DeviceId", "PersianDate"); + + b.HasIndex("DeviceId", "PersianYear", "PersianMonth"); + + b.ToTable("UserDailyReports", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.VerificationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Code") + .IsRequired() + .HasMaxLength(4) + .HasColumnType("nvarchar(4)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("Mobile") + .IsRequired() + .HasMaxLength(11) + .HasColumnType("nvarchar(11)"); + + b.Property("UsedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("Mobile", "Code", "IsUsed"); + + b.ToTable("VerificationCodes", (string)null); + }); + + modelBuilder.Entity("GreenHome.Domain.AIQuery", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GreenHome.Domain.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertCondition", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertLog", b => + { + b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition") + .WithMany() + .HasForeignKey("AlertConditionId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("GreenHome.Domain.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AlertCondition"); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertNotification", b => + { + b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition") + .WithMany() + .HasForeignKey("AlertConditionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("GreenHome.Domain.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AlertCondition"); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertRule", b => + { + b.HasOne("GreenHome.Domain.AlertCondition", "AlertCondition") + .WithMany("Rules") + .HasForeignKey("AlertConditionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AlertCondition"); + }); + + modelBuilder.Entity("GreenHome.Domain.Checklist", b => + { + b.HasOne("GreenHome.Domain.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b => + { + b.HasOne("GreenHome.Domain.Checklist", "Checklist") + .WithMany("Completions") + .HasForeignKey("ChecklistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GreenHome.Domain.User", "CompletedByUser") + .WithMany() + .HasForeignKey("CompletedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Checklist"); + + b.Navigation("CompletedByUser"); + }); + + modelBuilder.Entity("GreenHome.Domain.ChecklistItem", b => + { + b.HasOne("GreenHome.Domain.Checklist", "Checklist") + .WithMany("Items") + .HasForeignKey("ChecklistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Checklist"); + }); + + modelBuilder.Entity("GreenHome.Domain.ChecklistItemCompletion", b => + { + b.HasOne("GreenHome.Domain.ChecklistCompletion", "ChecklistCompletion") + .WithMany("ItemCompletions") + .HasForeignKey("ChecklistCompletionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GreenHome.Domain.ChecklistItem", "ChecklistItem") + .WithMany() + .HasForeignKey("ChecklistItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ChecklistCompletion"); + + b.Navigation("ChecklistItem"); + }); + + modelBuilder.Entity("GreenHome.Domain.DailyReport", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("GreenHome.Domain.Device", b => + { + b.HasOne("GreenHome.Domain.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GreenHome.Domain.DevicePost", b => + { + b.HasOne("GreenHome.Domain.User", "AuthorUser") + .WithMany() + .HasForeignKey("AuthorUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AuthorUser"); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("GreenHome.Domain.DevicePostImage", b => + { + b.HasOne("GreenHome.Domain.DevicePost", "DevicePost") + .WithMany("Images") + .HasForeignKey("DevicePostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DevicePost"); + }); + + modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GreenHome.Domain.Device", null) + .WithMany("DeviceSettings") + .HasForeignKey("DeviceId1"); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("GreenHome.Domain.DeviceUser", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany("DeviceUsers") + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GreenHome.Domain.User", "User") + .WithMany("DeviceUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GreenHome.Domain.PowerOutageLog", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("GreenHome.Domain.ReportImage", b => + { + b.HasOne("GreenHome.Domain.UserDailyReport", "UserDailyReport") + .WithMany("Images") + .HasForeignKey("UserDailyReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("UserDailyReport"); + }); + + modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b => + { + b.HasOne("GreenHome.Domain.Device", "Device") + .WithMany() + .HasForeignKey("DeviceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GreenHome.Domain.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Device"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GreenHome.Domain.AlertCondition", b => + { + b.Navigation("Rules"); + }); + + modelBuilder.Entity("GreenHome.Domain.Checklist", b => + { + b.Navigation("Completions"); + + b.Navigation("Items"); + }); + + modelBuilder.Entity("GreenHome.Domain.ChecklistCompletion", b => + { + b.Navigation("ItemCompletions"); + }); + + modelBuilder.Entity("GreenHome.Domain.Device", b => + { + b.Navigation("DeviceSettings"); + + b.Navigation("DeviceUsers"); + }); + + modelBuilder.Entity("GreenHome.Domain.DevicePost", b => + { + b.Navigation("Images"); + }); + + modelBuilder.Entity("GreenHome.Domain.User", b => + { + b.Navigation("DeviceUsers"); + }); + + modelBuilder.Entity("GreenHome.Domain.UserDailyReport", b => + { + b.Navigation("Images"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/GreenHome.Infrastructure/DependencyInjection.cs b/src/GreenHome.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..1b9e6e2 --- /dev/null +++ b/src/GreenHome.Infrastructure/DependencyInjection.cs @@ -0,0 +1,70 @@ +using GreenHome.Application; +using GreenHome.Infrastructure.Data; +using GreenHome.Infrastructure.Services.AI; +using GreenHome.Infrastructure.Services.Alert; +using GreenHome.Infrastructure.Services.Auth; +using GreenHome.Infrastructure.Services.Checklist; +using GreenHome.Infrastructure.Services.Device; +using GreenHome.Infrastructure.Services.PowerOutage; +using GreenHome.Infrastructure.Services.Report; +using GreenHome.Infrastructure.Services.Sun; +using GreenHome.Infrastructure.Services.Telemetry; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace GreenHome.Infrastructure; + +/// +/// Registers all Infrastructure-layer services with the DI container. +/// Call this from Program.cs to keep startup clean. +/// +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure( + this IServiceCollection services, + IConfiguration configuration) + { + // Entity Framework + services.AddDbContext(options => + { + options.UseSqlServer(configuration.GetConnectionString("Default")); + }); + + // Auth + services.AddScoped(); + + // Alert + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // Device + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // Telemetry + services.AddScoped(); + + // Reports + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // Checklist + services.AddScoped(); + + // Power Outage + services.AddScoped(); + + // AI + services.AddScoped(); + + // Sun Calculator + services.AddScoped(); + + return services; + } +} diff --git a/src/GreenHome.Infrastructure/Extensions/PersianDateHelper.cs b/src/GreenHome.Infrastructure/Extensions/PersianDateHelper.cs new file mode 100644 index 0000000..6702486 --- /dev/null +++ b/src/GreenHome.Infrastructure/Extensions/PersianDateHelper.cs @@ -0,0 +1,51 @@ +using System.Globalization; + +namespace GreenHome.Infrastructure.Extensions; + +/// +/// Helper methods for Persian (Solar Hijri) date calculations. +/// Eliminates duplicate PersianCalendar code across services. +/// +public static class PersianDateHelper +{ + private static readonly PersianCalendar PersianCalendar = new(); + + /// + /// Extracts Persian year, month, day from a DateTime and returns the formatted Persian date string (yyyy/MM/dd). + /// + public static (int Year, int Month, int Day, string PersianDate) ToPersianDate(this DateTime dateTime) + { + var localDt = dateTime.Kind == DateTimeKind.Utc ? dateTime.ToLocalTime() : dateTime; + var year = PersianCalendar.GetYear(localDt); + var month = PersianCalendar.GetMonth(localDt); + var day = PersianCalendar.GetDayOfMonth(localDt); + var persianDate = $"{year:0000}/{month:00}/{day:00}"; + return (year, month, day, persianDate); + } + + /// + /// Validates a Persian date string in yyyy/MM/dd format and extracts year, month, day. + /// + public static bool TryParsePersianDate(string? persianDate, out int year, out int month, out int day) + { + year = month = day = 0; + + if (string.IsNullOrWhiteSpace(persianDate)) + return false; + + var parts = persianDate.Split('/'); + if (parts.Length != 3) + return false; + + if (!int.TryParse(parts[0], out year) || year < 1300 || year > 1500) + return false; + + if (!int.TryParse(parts[1], out month) || month < 1 || month > 12) + return false; + + if (!int.TryParse(parts[2], out day) || day < 1 || day > 31) + return false; + + return true; + } +} diff --git a/src/GreenHome.Infrastructure/AIQueryService.cs b/src/GreenHome.Infrastructure/Services/AI/AIQueryService.cs similarity index 69% rename from src/GreenHome.Infrastructure/AIQueryService.cs rename to src/GreenHome.Infrastructure/Services/AI/AIQueryService.cs index 9cd4de1..68817f5 100644 --- a/src/GreenHome.Infrastructure/AIQueryService.cs +++ b/src/GreenHome.Infrastructure/Services/AI/AIQueryService.cs @@ -1,48 +1,49 @@ using GreenHome.Application; using GreenHome.Domain; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.AI; public sealed class AIQueryService : IAIQueryService { - private readonly GreenHomeDbContext dbContext; - private readonly ILogger logger; + private readonly GreenHomeDbContext _dbContext; + private readonly ILogger _logger; public AIQueryService( GreenHomeDbContext dbContext, ILogger logger) { - this.dbContext = dbContext; - this.logger = logger; + _dbContext = dbContext; + _logger = logger; } public async Task SaveQueryAsync(AIQuery query, CancellationToken cancellationToken = default) { try { - dbContext.AIQueries.Add(query); - await dbContext.SaveChangesAsync(cancellationToken); - - logger.LogInformation("AI query saved: {QueryId}, Tokens: {TotalTokens}", + _dbContext.AIQueries.Add(query); + await _dbContext.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("AI query saved: {QueryId}, Tokens: {TotalTokens}", query.Id, query.TotalTokens); - + return query; } catch (Exception ex) { - logger.LogError(ex, "Error saving AI query"); + _logger.LogError(ex, "Error saving AI query"); throw; } } public async Task> GetDeviceQueriesAsync( - int deviceId, - int take = 50, + int deviceId, + int take = 50, CancellationToken cancellationToken = default) { - return await dbContext.AIQueries + return await _dbContext.AIQueries .Where(q => q.DeviceId == deviceId) .OrderByDescending(q => q.CreatedAt) .Take(take) @@ -50,11 +51,11 @@ public sealed class AIQueryService : IAIQueryService } public async Task> GetUserQueriesAsync( - int userId, - int take = 50, + int userId, + int take = 50, CancellationToken cancellationToken = default) { - return await dbContext.AIQueries + return await _dbContext.AIQueries .Where(q => q.UserId == userId) .OrderByDescending(q => q.CreatedAt) .Take(take) @@ -62,28 +63,28 @@ public sealed class AIQueryService : IAIQueryService } public async Task GetDeviceTotalTokensAsync( - int deviceId, + int deviceId, CancellationToken cancellationToken = default) { - return await dbContext.AIQueries + return await _dbContext.AIQueries .Where(q => q.DeviceId == deviceId) .SumAsync(q => q.TotalTokens, cancellationToken); } public async Task GetUserTotalTokensAsync( - int userId, + int userId, CancellationToken cancellationToken = default) { - return await dbContext.AIQueries + return await _dbContext.AIQueries .Where(q => q.UserId == userId) .SumAsync(q => q.TotalTokens, cancellationToken); } public async Task> GetRecentQueriesAsync( - int take = 20, + int take = 20, CancellationToken cancellationToken = default) { - return await dbContext.AIQueries + return await _dbContext.AIQueries .Include(q => q.Device) .Include(q => q.User) .OrderByDescending(q => q.CreatedAt) @@ -94,15 +95,15 @@ public sealed class AIQueryService : IAIQueryService public async Task GetStatsAsync(CancellationToken cancellationToken = default) { var today = DateTime.UtcNow.Date; - - var allQueries = await dbContext.AIQueries - .Select(q => new - { - q.TotalTokens, - q.PromptTokens, - q.CompletionTokens, - q.ResponseTimeMs, - q.CreatedAt + + var allQueries = await _dbContext.AIQueries + .Select(q => new + { + q.TotalTokens, + q.PromptTokens, + q.CompletionTokens, + q.ResponseTimeMs, + q.CreatedAt }) .ToListAsync(cancellationToken); @@ -114,13 +115,12 @@ public sealed class AIQueryService : IAIQueryService TotalTokensUsed = allQueries.Sum(q => q.TotalTokens), TotalPromptTokens = allQueries.Sum(q => q.PromptTokens), TotalCompletionTokens = allQueries.Sum(q => q.CompletionTokens), - AverageResponseTimeMs = allQueries.Any() + AverageResponseTimeMs = allQueries.Any() ? allQueries.Where(q => q.ResponseTimeMs.HasValue) - .Average(q => q.ResponseTimeMs ?? 0) + .Average(q => q.ResponseTimeMs ?? 0) : 0, TodayQueries = todayQueries.Count, TodayTokens = todayQueries.Sum(q => q.TotalTokens) }; } } - diff --git a/src/GreenHome.Infrastructure/AlertConditionService.cs b/src/GreenHome.Infrastructure/Services/Alert/AlertConditionService.cs similarity index 73% rename from src/GreenHome.Infrastructure/AlertConditionService.cs rename to src/GreenHome.Infrastructure/Services/Alert/AlertConditionService.cs index c09ba7f..21a11c5 100644 --- a/src/GreenHome.Infrastructure/AlertConditionService.cs +++ b/src/GreenHome.Infrastructure/Services/Alert/AlertConditionService.cs @@ -1,23 +1,24 @@ using AutoMapper; using GreenHome.Application; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Alert; public sealed class AlertConditionService : IAlertConditionService { - private readonly GreenHomeDbContext dbContext; - private readonly IMapper mapper; + private readonly GreenHomeDbContext _dbContext; + private readonly IMapper _mapper; public AlertConditionService(GreenHomeDbContext dbContext, IMapper mapper) { - this.dbContext = dbContext; - this.mapper = mapper; + _dbContext = dbContext; + _mapper = mapper; } public async Task> GetByDeviceIdAsync(int deviceId, CancellationToken cancellationToken) { - var conditions = await dbContext.AlertConditions + var conditions = await _dbContext.AlertConditions .Include(x => x.Device) .Include(x => x.Rules) .Where(x => x.DeviceId == deviceId) @@ -25,18 +26,18 @@ public sealed class AlertConditionService : IAlertConditionService .AsNoTracking() .ToListAsync(cancellationToken); - return mapper.Map>(conditions); + return _mapper.Map>(conditions); } public async Task GetByIdAsync(int id, CancellationToken cancellationToken) { - var condition = await dbContext.AlertConditions + var condition = await _dbContext.AlertConditions .Include(x => x.Device) .Include(x => x.Rules) .AsNoTracking() .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); - return condition != null ? mapper.Map(condition) : null; + return condition != null ? _mapper.Map(condition) : null; } public async Task CreateAsync(CreateAlertConditionRequest request, CancellationToken cancellationToken) @@ -53,29 +54,26 @@ public sealed class AlertConditionService : IAlertConditionService UpdatedAt = DateTime.UtcNow }; - // Add rules - var rules = mapper.Map>(request.Rules); + var rules = _mapper.Map>(request.Rules); foreach (var rule in rules) { condition.Rules.Add(rule); } - dbContext.AlertConditions.Add(condition); - await dbContext.SaveChangesAsync(cancellationToken); + _dbContext.AlertConditions.Add(condition); + await _dbContext.SaveChangesAsync(cancellationToken); return condition.Id; } public async Task UpdateAsync(UpdateAlertConditionRequest request, CancellationToken cancellationToken) { - var condition = await dbContext.AlertConditions + var condition = await _dbContext.AlertConditions .Include(x => x.Rules) .FirstOrDefaultAsync(x => x.Id == request.Id, cancellationToken); if (condition == null) - { throw new InvalidOperationException($"Alert condition not found: {request.Id}"); - } condition.NotificationType = request.NotificationType; condition.TimeType = request.TimeType; @@ -84,53 +82,48 @@ public sealed class AlertConditionService : IAlertConditionService condition.IsEnabled = request.IsEnabled; condition.UpdatedAt = DateTime.UtcNow; - // Remove old rules and add new ones - dbContext.AlertRules.RemoveRange(condition.Rules); + _dbContext.AlertRules.RemoveRange(condition.Rules); condition.Rules.Clear(); - var newRules = mapper.Map>(request.Rules); + var newRules = _mapper.Map>(request.Rules); foreach (var rule in newRules) { condition.Rules.Add(rule); } - await dbContext.SaveChangesAsync(cancellationToken); + await _dbContext.SaveChangesAsync(cancellationToken); } public async Task DeleteAsync(int id, CancellationToken cancellationToken) { - var condition = await dbContext.AlertConditions + var condition = await _dbContext.AlertConditions .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); if (condition == null) - { throw new InvalidOperationException($"Alert condition not found: {id}"); - } - dbContext.AlertConditions.Remove(condition); - await dbContext.SaveChangesAsync(cancellationToken); + _dbContext.AlertConditions.Remove(condition); + await _dbContext.SaveChangesAsync(cancellationToken); } public async Task ToggleEnabledAsync(int id, bool isEnabled, CancellationToken cancellationToken) { - var condition = await dbContext.AlertConditions + var condition = await _dbContext.AlertConditions .FirstOrDefaultAsync(x => x.Id == id, cancellationToken); if (condition == null) - { return false; - } condition.IsEnabled = isEnabled; condition.UpdatedAt = DateTime.UtcNow; - - await dbContext.SaveChangesAsync(cancellationToken); + + await _dbContext.SaveChangesAsync(cancellationToken); return true; } public async Task> GetRelayConditionsAsync(int deviceId, CancellationToken cancellationToken) { - var conditions = await dbContext.AlertConditions + var conditions = await _dbContext.AlertConditions .Include(x => x.Rules) .Where(x => x.DeviceId == deviceId && (int)x.NotificationType > 0) .OrderByDescending(x => x.CreatedAt) @@ -156,31 +149,23 @@ public sealed class AlertConditionService : IAlertConditionService public async Task> GetUpdatedRelayConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken) { - // اگر lastUpdateDate null باشه، یعنی کلاینت تا حالا هیچ داده‌ای نگرفته، همه رو برگردون if (lastUpdateDate == null) - { return await GetRelayConditionsAsync(deviceId, cancellationToken); - } - var hasUpdates = await dbContext.AlertConditions + var hasUpdates = await _dbContext.AlertConditions .AnyAsync(x => x.DeviceId == deviceId && (int)x.NotificationType > 0 && x.UpdatedAt > lastUpdateDate, cancellationToken); if (!hasUpdates) - { return Array.Empty(); - } return await GetRelayConditionsAsync(deviceId, cancellationToken); } public async Task HasUpdatedConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken) { - if(lastUpdateDate == null) return true; + if (lastUpdateDate == null) return true; - var hasUpdates = await dbContext.AlertConditions + return await _dbContext.AlertConditions .AnyAsync(x => x.DeviceId == deviceId && (int)x.NotificationType > 0 && x.UpdatedAt > lastUpdateDate, cancellationToken); - - return hasUpdates; } } - diff --git a/src/GreenHome.Infrastructure/AlertLogService.cs b/src/GreenHome.Infrastructure/Services/Alert/AlertLogService.cs similarity index 81% rename from src/GreenHome.Infrastructure/AlertLogService.cs rename to src/GreenHome.Infrastructure/Services/Alert/AlertLogService.cs index 2699de1..23a43d2 100644 --- a/src/GreenHome.Infrastructure/AlertLogService.cs +++ b/src/GreenHome.Infrastructure/Services/Alert/AlertLogService.cs @@ -1,65 +1,51 @@ using AutoMapper; using GreenHome.Application; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Alert; public sealed class AlertLogService : IAlertLogService { - private readonly GreenHomeDbContext _context; + private readonly GreenHomeDbContext _dbContext; private readonly IMapper _mapper; public AlertLogService(GreenHomeDbContext context, IMapper mapper) { - _context = context; + _dbContext = context; _mapper = mapper; } public async Task> GetAlertLogsAsync( - AlertLogFilter filter, + AlertLogFilter filter, CancellationToken cancellationToken) { - var query = _context.AlertLogs + var query = _dbContext.AlertLogs .Include(x => x.Device) .Include(x => x.User) .AsNoTracking() .AsQueryable(); - // Apply filters if (filter.DeviceId.HasValue) - { query = query.Where(x => x.DeviceId == filter.DeviceId.Value); - } if (filter.UserId.HasValue) - { query = query.Where(x => x.UserId == filter.UserId.Value); - } if (filter.AlertType.HasValue) - { query = query.Where(x => x.AlertType == filter.AlertType.Value); - } if (filter.Status.HasValue) - { query = query.Where(x => x.Status == filter.Status.Value); - } if (filter.StartDate.HasValue) - { query = query.Where(x => x.SentAt >= filter.StartDate.Value); - } if (filter.EndDate.HasValue) - { query = query.Where(x => x.SentAt <= filter.EndDate.Value); - } - // Get total count var totalCount = await query.CountAsync(cancellationToken); - // Apply pagination and ordering var items = await query .OrderByDescending(x => x.SentAt) .Skip((filter.Page - 1) * filter.PageSize) @@ -79,7 +65,7 @@ public sealed class AlertLogService : IAlertLogService public async Task GetAlertLogByIdAsync(int id, CancellationToken cancellationToken) { - var log = await _context.AlertLogs + var log = await _dbContext.AlertLogs .Include(x => x.Device) .Include(x => x.User) .Include(x => x.AlertCondition) @@ -92,9 +78,8 @@ public sealed class AlertLogService : IAlertLogService public async Task CreateAlertLogAsync(AlertLogDto dto, CancellationToken cancellationToken) { var entity = _mapper.Map(dto); - _context.AlertLogs.Add(entity); - await _context.SaveChangesAsync(cancellationToken); + _dbContext.AlertLogs.Add(entity); + await _dbContext.SaveChangesAsync(cancellationToken); return entity.Id; } } - diff --git a/src/GreenHome.Infrastructure/AlertService.cs b/src/GreenHome.Infrastructure/Services/Alert/AlertService.cs similarity index 68% rename from src/GreenHome.Infrastructure/AlertService.cs rename to src/GreenHome.Infrastructure/Services/Alert/AlertService.cs index cac755b..250418a 100644 --- a/src/GreenHome.Infrastructure/AlertService.cs +++ b/src/GreenHome.Infrastructure/Services/Alert/AlertService.cs @@ -1,24 +1,23 @@ using GreenHome.Application; using GreenHome.Domain; +using GreenHome.Infrastructure.Data; using GreenHome.Sms.Ippanel; using GreenHome.VoiceCall.Avanak; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; using Microsoft.Extensions.Logging; -using System.Globalization; using System.Text.Json; -using static GreenHome.Sms.Ippanel.IppanelSmsService; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Alert; public sealed class AlertService : IAlertService { - private readonly GreenHomeDbContext dbContext; - private readonly IDeviceSettingsService deviceSettingsService; - private readonly ISmsService smsService; - private readonly IVoiceCallService voiceCallService; - private readonly ISunCalculatorService sunCalculatorService; - private readonly ILogger logger; + private readonly GreenHomeDbContext _dbContext; + private readonly IDeviceSettingsService _deviceSettingsService; + private readonly ISmsService _smsService; + private readonly IVoiceCallService _voiceCallService; + private readonly ISunCalculatorService _sunCalculatorService; + private readonly IPowerOutageLogService _powerOutageLogService; + private readonly ILogger _logger; public AlertService( GreenHomeDbContext dbContext, @@ -26,20 +25,21 @@ public sealed class AlertService : IAlertService ISmsService smsService, IVoiceCallService voiceCallService, ISunCalculatorService sunCalculatorService, + IPowerOutageLogService powerOutageLogService, ILogger logger) { - this.dbContext = dbContext; - this.deviceSettingsService = deviceSettingsService; - this.smsService = smsService; - this.voiceCallService = voiceCallService; - this.sunCalculatorService = sunCalculatorService; - this.logger = logger; + _dbContext = dbContext; + _deviceSettingsService = deviceSettingsService; + _smsService = smsService; + _voiceCallService = voiceCallService; + _sunCalculatorService = sunCalculatorService; + _powerOutageLogService = powerOutageLogService; + _logger = logger; } - public async Task CheckAndSendAlertsAsync(int deviceId, TelemetryDto telemetry, CancellationToken cancellationToken) + public async Task CheckAndSendAlertsAsync(int deviceId, TelemetryDto telemetry, CancellationToken cancellationToken) { - // Get device with all users who should receive alerts - var device = await dbContext.Devices + var device = await _dbContext.Devices .Include(d => d.User) .Include(d => d.DeviceUsers.Where(du => du.ReceiveAlerts)) .ThenInclude(du => du.User) @@ -47,105 +47,104 @@ public sealed class AlertService : IAlertService if (device == null) { - logger.LogWarning("Device not found: DeviceId={DeviceId}", deviceId); + _logger.LogWarning("Device not found: DeviceId={DeviceId}", deviceId); return null; } - // Get device settings for location - var settings = await deviceSettingsService.GetByDeviceIdAsync(deviceId, cancellationToken); + var settings = await _deviceSettingsService.GetByDeviceIdAsync(deviceId, cancellationToken); - - var recievers = device.User.Mobile; - recievers += "," + (settings?.SmsRecievers ?? ""); - - recievers = string.Join(",", - recievers + var recievers = string.Join(",", + (device.User.Mobile + "," + (settings?.SmsRecievers ?? "")) .Split(',') .Select(x => x.Trim()) .Where(x => x.Length > 0) + .Distinct() ); - // Get all users who should receive alerts - // var usersToAlert = device.DeviceUsers - // .Where(du => du.ReceiveAlerts) - // .Select(du => du.User) - // .ToList(); if (recievers.Length == 0) { - logger.LogInformation("No users with ReceiveAlerts enabled for device: DeviceId={DeviceId}", deviceId); + _logger.LogInformation("No users with ReceiveAlerts enabled for device: DeviceId={DeviceId}", deviceId); return null; } - // Get all enabled alert conditions for this device - var conditions = await dbContext.AlertConditions + var conditions = await _dbContext.AlertConditions .Include(c => c.Rules) .Where(c => c.DeviceId == deviceId && c.IsEnabled) .ToListAsync(cancellationToken); if (!conditions.Any()) { - logger.LogDebug("No enabled alert conditions for device: DeviceId={DeviceId}", deviceId); - //return null; + _logger.LogDebug("No enabled alert conditions for device: DeviceId={DeviceId}", deviceId); } - // Determine if it's daytime or nighttime bool? isDaytime = null; if (settings?.Latitude != null && settings.Longitude != null) { - isDaytime = sunCalculatorService.IsDaytime(DateTime.UtcNow, settings.Latitude.Value, settings.Longitude.Value); + isDaytime = _sunCalculatorService.IsDaytime(DateTime.UtcNow, settings.Latitude.Value, settings.Longitude.Value); } List messages = []; - // Check each condition foreach (var condition in conditions) { - // Check time type filter if (condition.TimeType == Domain.AlertTimeType.Day && isDaytime == false) - { - continue; // This condition is for daytime only, but it's nighttime - } + continue; if (condition.TimeType == Domain.AlertTimeType.Night && isDaytime == true) - { - continue; // This condition is for nighttime only, but it's daytime - } + continue; - // Check if all rules match (AND logic) var allRulesMatch = condition.Rules.All(rule => CheckRule(rule, telemetry)); if (allRulesMatch && condition.Rules.Count != 0) { - // All rules passed, send alert if cooldown period has passed - // await SendAlertForConditionAsync(condition, device, usersToAlert, telemetry, cancellationToken); AlertNotification notif = await GetAlertMessage(condition, device, device.User, telemetry, cancellationToken); - if(notif != null && !string.IsNullOrEmpty(notif.Message)) + if (notif != null && !string.IsNullOrEmpty(notif.Message)) { messages.Add(notif.Message); - await dbContext.AlertNotifications.AddAsync(notif, cancellationToken); + await _dbContext.AlertNotifications.AddAsync(notif, cancellationToken); } - // return message; } } - await dbContext.SaveChangesAsync(cancellationToken); + await _dbContext.SaveChangesAsync(cancellationToken); - if(telemetry.OldPower == 0 && telemetry.Power == 1) + var result = new AlertCheckResult(); + + if (telemetry.OldPower == 0 && telemetry.Power == 1) { + result.PowerStatus = "1"; messages.Add($"برق دستگاه {device.DeviceName} متصل شد"); + await _powerOutageLogService.LogPowerChangeAsync( + deviceId, + telemetry.OldPower.Value, + telemetry.Power.Value, + telemetry.TimestampUtc ?? DateTime.UtcNow, + cancellationToken); } - else if(telemetry.OldPower == 1 && telemetry.Power == 0) + else if (telemetry.OldPower == 1 && telemetry.Power == 0) { + result.PowerStatus = "0"; messages.Add($"برق دستگاه {device.DeviceName} قطع شد"); + await _powerOutageLogService.LogPowerChangeAsync( + deviceId, + telemetry.OldPower.Value, + telemetry.Power.Value, + telemetry.TimestampUtc ?? DateTime.UtcNow, + cancellationToken); } - if(messages.Any()) + if (messages.Any()) { - return $"tt{recievers}#{string.Join("@", messages)}"; + result.Messages = messages; + result.Recipients = recievers; + return result; } + + if (result.PowerStatus != null) + return result; + return null; } private bool CheckRule(Domain.AlertRule rule, TelemetryDto telemetry) { - // Get sensor value var sensorValue = rule.SensorType switch { Domain.SensorType.Temperature => telemetry.TemperatureC, @@ -156,7 +155,6 @@ public sealed class AlertService : IAlertService _ => 0m }; - // Check comparison return rule.ComparisonType switch { Domain.ComparisonType.GreaterThan => sensorValue > rule.Value1, @@ -174,17 +172,15 @@ public sealed class AlertService : IAlertService TelemetryDto telemetry, CancellationToken cancellationToken) { - // Determine cooldown based on notification type var cooldownMinutes = condition.NotificationType == Domain.AlertNotificationType.Call ? condition.CallCooldownMinutes : condition.SmsCooldownMinutes; - // Build alert message once var message = BuildAlertMessage(condition, device.DeviceName, telemetry); var sentAt = DateTime.UtcNow; var cooldownTime = sentAt.AddMinutes(-cooldownMinutes); - var recentAlert = await dbContext.AlertNotifications + var recentAlert = await _dbContext.AlertNotifications .Where(a => a.DeviceId == device.Id && a.UserId == user.Id && a.AlertConditionId == condition.Id && @@ -193,7 +189,7 @@ public sealed class AlertService : IAlertService if (recentAlert != null) { - logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, UserId={UserId}, ConditionId={ConditionId}", + _logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, UserId={UserId}, ConditionId={ConditionId}", device.Id, user.Id, condition.Id); return null; } @@ -205,14 +201,9 @@ public sealed class AlertService : IAlertService AlertConditionId = condition.Id, NotificationType = condition.NotificationType, Message = message, - //MessageOutboxIds = messageOutboxIds, - //ErrorMessage = errorMessage, SentAt = sentAt, - //IsSent = true }; - // dbContext.AlertNotifications.Add(notification); - return notification; } @@ -223,21 +214,17 @@ public sealed class AlertService : IAlertService TelemetryDto telemetry, CancellationToken cancellationToken) { - // Determine cooldown based on notification type var cooldownMinutes = condition.NotificationType == Domain.AlertNotificationType.Call ? condition.CallCooldownMinutes : condition.SmsCooldownMinutes; - // Build alert message once var message = BuildAlertMessage(condition, device.DeviceName, telemetry); var sentAt = DateTime.UtcNow; - // Send alert to each user foreach (var user in usersToAlert) { - // Check if alert was sent recently to this user var cooldownTime = sentAt.AddMinutes(-cooldownMinutes); - var recentAlert = await dbContext.AlertNotifications + var recentAlert = await _dbContext.AlertNotifications .Where(a => a.DeviceId == device.Id && a.UserId == user.Id && a.AlertConditionId == condition.Id && @@ -246,12 +233,11 @@ public sealed class AlertService : IAlertService if (recentAlert != null) { - logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, UserId={UserId}, ConditionId={ConditionId}", + _logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, UserId={UserId}, ConditionId={ConditionId}", device.Id, user.Id, condition.Id); continue; } - // Send notification var startTime = DateTime.UtcNow; string? messageOutboxIds = null; string? errorMessage = null; @@ -263,7 +249,7 @@ public sealed class AlertService : IAlertService { (isSent, messageOutboxIds, errorMessage) = await SendSmsAlertAsync(user.Mobile, device.DeviceName, message, cancellationToken); } - else // Call + else { (isSent, messageOutboxIds, errorMessage) = await SendCallAlertAsync(user.Mobile, device.DeviceName, message, cancellationToken); } @@ -272,17 +258,14 @@ public sealed class AlertService : IAlertService { errorMessage = $"Exception: {ex.Message}"; if (ex.InnerException != null) - { errorMessage += $" | InnerException: {ex.InnerException.Message}"; - } isSent = false; - logger.LogError(ex, "Failed to send alert: DeviceId={DeviceId}, UserId={UserId}, ConditionId={ConditionId}", + _logger.LogError(ex, "Failed to send alert: DeviceId={DeviceId}, UserId={UserId}, ConditionId={ConditionId}", device.Id, user.Id, condition.Id); } var processingTime = (long)(DateTime.UtcNow - startTime).TotalMilliseconds; - // Save notification to database (old table for backwards compatibility) var notification = new Domain.AlertNotification { DeviceId = device.Id, @@ -296,9 +279,8 @@ public sealed class AlertService : IAlertService IsSent = isSent }; - dbContext.AlertNotifications.Add(notification); + _dbContext.AlertNotifications.Add(notification); - // Log the alert var alertLog = new Domain.AlertLog { DeviceId = device.Id, @@ -314,10 +296,10 @@ public sealed class AlertService : IAlertService ProcessingTimeMs = processingTime }; - dbContext.AlertLogs.Add(alertLog); + _dbContext.AlertLogs.Add(alertLog); } - await dbContext.SaveChangesAsync(cancellationToken); + await _dbContext.SaveChangesAsync(cancellationToken); } private string BuildAlertMessage(Domain.AlertCondition condition, string deviceName, TelemetryDto telemetry) @@ -380,7 +362,7 @@ public sealed class AlertService : IAlertService { try { - var smsResponse = await smsService.SendPatternSmsAsync(new PatternSmsRequest + var smsResponse = await _smsService.SendPatternSmsAsync(new PatternSmsRequest { Recipients = [mobile], PatternCode = "64di3w9kb0fxvif", @@ -397,32 +379,28 @@ public sealed class AlertService : IAlertService smsResponse.Data.MessageOutboxIds.Count > 0) { var outboxIds = JsonSerializer.Serialize(smsResponse.Data.MessageOutboxIds); - logger.LogInformation("Alert SMS sent: Mobile={Mobile}, OutboxIds={OutboxIds}", mobile, outboxIds); + _logger.LogInformation("Alert SMS sent: Mobile={Mobile}, OutboxIds={OutboxIds}", mobile, outboxIds); return (true, outboxIds, null); } else { var errors = new List(); if (!string.IsNullOrWhiteSpace(smsResponse?.Meta.Message)) - { errors.Add(smsResponse.Meta.Message); - } if (smsResponse?.Meta.Errors != null) { foreach (var error in smsResponse.Meta.Errors) - { errors.Add($"{error.Key}: {string.Join(", ", error.Value)}"); - } } var errorMsg = errors.Count > 0 ? string.Join(" | ", errors) : "Unknown SMS error"; - logger.LogWarning("Alert SMS failed: Mobile={Mobile}, Error={Error}", mobile, errorMsg); + _logger.LogWarning("Alert SMS failed: Mobile={Mobile}, Error={Error}", mobile, errorMsg); return (false, null, errorMsg); } } catch (Exception ex) { var errorMsg = $"Exception: {ex.Message}"; - logger.LogError(ex, "Exception sending SMS alert: Mobile={Mobile}", mobile); + _logger.LogError(ex, "Exception sending SMS alert: Mobile={Mobile}", mobile); return (false, null, errorMsg); } } @@ -435,38 +413,30 @@ public sealed class AlertService : IAlertService { try { - // TODO: Implement voice call integration - // For now, just log and return success - logger.LogInformation("Voice call alert requested: Mobile={Mobile}, Message={Message}", mobile, message); - - // Placeholder: In real implementation, call voiceCallService here - // var callResponse = await voiceCallService.MakeCallAsync(mobile, message, cancellationToken); - + _logger.LogInformation("Voice call alert requested: Mobile={Mobile}, Message={Message}", mobile, message); return (true, null, "Voice call not yet implemented"); } catch (Exception ex) { var errorMsg = $"Exception: {ex.Message}"; - logger.LogError(ex, "Exception sending call alert: Mobile={Mobile}", mobile); + _logger.LogError(ex, "Exception sending call alert: Mobile={Mobile}", mobile); return (false, null, errorMsg); } } public async Task SendPowerOutageAlertAsync(int deviceId, CancellationToken cancellationToken) { - // Get device with all users who should receive alerts - var device = await dbContext.Devices + var device = await _dbContext.Devices .Include(d => d.DeviceUsers.Where(du => du.ReceiveAlerts)) .ThenInclude(du => du.User) .FirstOrDefaultAsync(d => d.Id == deviceId, cancellationToken); if (device == null) { - logger.LogWarning("Device not found for power outage alert: DeviceId={DeviceId}", deviceId); + _logger.LogWarning("Device not found for power outage alert: DeviceId={DeviceId}", deviceId); throw new InvalidOperationException($"دستگاه با شناسه {deviceId} یافت نشد"); } - // Get all users who should receive alerts var usersToAlert = device.DeviceUsers .Where(du => du.ReceiveAlerts) .Select(du => du.User) @@ -474,32 +444,22 @@ public sealed class AlertService : IAlertService if (usersToAlert.Count == 0) { - logger.LogInformation("No users with ReceiveAlerts enabled for power outage: DeviceId={DeviceId}", deviceId); + _logger.LogInformation("No users with ReceiveAlerts enabled for power outage: DeviceId={DeviceId}", deviceId); return; } var message = $"⚠️ هشدار قطع برق! دستگاه {device.DeviceName} از برق قطع شده است."; var sentAt = DateTime.UtcNow; - // Send to all users (both SMS and Call for power outage - it's critical!) foreach (var user in usersToAlert) { - // Send SMS - await SendPowerOutageNotificationAsync( - device, user, message, sentAt, - Domain.AlertNotificationType.SMS, - cancellationToken); - - // Send Call (important alert) - await SendPowerOutageNotificationAsync( - device, user, message, sentAt, - Domain.AlertNotificationType.Call, - cancellationToken); + await SendPowerOutageNotificationAsync(device, user, message, sentAt, Domain.AlertNotificationType.SMS, cancellationToken); + await SendPowerOutageNotificationAsync(device, user, message, sentAt, Domain.AlertNotificationType.Call, cancellationToken); } - await dbContext.SaveChangesAsync(cancellationToken); - - logger.LogInformation("Power outage alerts sent to {Count} users for device {DeviceId}", + await _dbContext.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Power outage alerts sent to {Count} users for device {DeviceId}", usersToAlert.Count, deviceId); } @@ -519,31 +479,22 @@ public sealed class AlertService : IAlertService try { if (notificationType == Domain.AlertNotificationType.SMS) - { - (isSent, messageOutboxIds, errorMessage) = await SendSmsAlertAsync( - user.Mobile, device.DeviceName, message, cancellationToken); - } + (isSent, messageOutboxIds, errorMessage) = await SendSmsAlertAsync(user.Mobile, device.DeviceName, message, cancellationToken); else - { - (isSent, messageOutboxIds, errorMessage) = await SendCallAlertAsync( - user.Mobile, device.DeviceName, message, cancellationToken); - } + (isSent, messageOutboxIds, errorMessage) = await SendCallAlertAsync(user.Mobile, device.DeviceName, message, cancellationToken); } catch (Exception ex) { errorMessage = $"Exception: {ex.Message}"; if (ex.InnerException != null) - { errorMessage += $" | InnerException: {ex.InnerException.Message}"; - } isSent = false; - logger.LogError(ex, "Failed to send power outage alert: DeviceId={DeviceId}, UserId={UserId}, Type={Type}", + _logger.LogError(ex, "Failed to send power outage alert: DeviceId={DeviceId}, UserId={UserId}, Type={Type}", device.Id, user.Id, notificationType); } var processingTime = (long)(DateTime.UtcNow - startTime).TotalMilliseconds; - // Save notification (old table) var notification = new Domain.AlertNotification { DeviceId = device.Id, @@ -557,9 +508,8 @@ public sealed class AlertService : IAlertService IsSent = isSent }; - dbContext.AlertNotifications.Add(notification); + _dbContext.AlertNotifications.Add(notification); - // Log the alert var alertLog = new Domain.AlertLog { DeviceId = device.Id, @@ -575,7 +525,6 @@ public sealed class AlertService : IAlertService ProcessingTimeMs = processingTime }; - dbContext.AlertLogs.Add(alertLog); + _dbContext.AlertLogs.Add(alertLog); } } - diff --git a/src/GreenHome.Infrastructure/AuthService.cs b/src/GreenHome.Infrastructure/Services/Auth/AuthService.cs similarity index 79% rename from src/GreenHome.Infrastructure/AuthService.cs rename to src/GreenHome.Infrastructure/Services/Auth/AuthService.cs index 8580fc4..7907be5 100644 --- a/src/GreenHome.Infrastructure/AuthService.cs +++ b/src/GreenHome.Infrastructure/Services/Auth/AuthService.cs @@ -1,17 +1,18 @@ using GreenHome.Application; +using GreenHome.Infrastructure.Data; using GreenHome.Sms.Ippanel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System.Security.Cryptography; using System.Text; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Auth; public sealed class AuthService : IAuthService { - private readonly GreenHomeDbContext dbContext; - private readonly ISmsService smsService; - private readonly ILogger logger; + private readonly GreenHomeDbContext _dbContext; + private readonly ISmsService _smsService; + private readonly ILogger _logger; private const int CodeExpirationMinutes = 5; private const int ResendCooldownSeconds = 120; @@ -20,16 +21,15 @@ public sealed class AuthService : IAuthService ISmsService smsService, ILogger logger) { - this.dbContext = dbContext; - this.smsService = smsService; - this.logger = logger; + _dbContext = dbContext; + _smsService = smsService; + _logger = logger; } public async Task SendVerificationCodeAsync( SendCodeRequest request, CancellationToken cancellationToken) { - // Normalize mobile number (remove spaces, dashes, etc.) var mobile = NormalizeMobile(request.Mobile); if (string.IsNullOrWhiteSpace(mobile) || mobile.Length != 11 || !mobile.StartsWith("09")) @@ -41,11 +41,10 @@ public sealed class AuthService : IAuthService }; } - // Check if we can resend (cooldown period) var canResend = await CanResendCodeAsync(mobile, cancellationToken); if (!canResend) { - var lastCode = await dbContext.VerificationCodes + var lastCode = await _dbContext.VerificationCodes .Where(v => v.Mobile == mobile && !v.IsUsed) .OrderByDescending(v => v.CreatedAt) .FirstOrDefaultAsync(cancellationToken); @@ -62,11 +61,9 @@ public sealed class AuthService : IAuthService } } - // Generate 4-digit code var code = GenerateCode(); - // Invalidate previous unused codes for this mobile - var previousCodes = await dbContext.VerificationCodes + var previousCodes = await _dbContext.VerificationCodes .Where(v => v.Mobile == mobile && !v.IsUsed) .ToListAsync(cancellationToken); @@ -76,7 +73,6 @@ public sealed class AuthService : IAuthService prevCode.UsedAt = DateTime.UtcNow; } - // Create new verification code var verificationCode = new Domain.VerificationCode { Mobile = mobile, @@ -86,25 +82,23 @@ public sealed class AuthService : IAuthService IsUsed = false }; - dbContext.VerificationCodes.Add(verificationCode); - await dbContext.SaveChangesAsync(cancellationToken); + _dbContext.VerificationCodes.Add(verificationCode); + await _dbContext.SaveChangesAsync(cancellationToken); - // Send SMS try { - await smsService.SendPatternSmsAsync(new PatternSmsRequest + await _smsService.SendPatternSmsAsync(new PatternSmsRequest { Recipients = [mobile], PatternCode = "ruvpjx7lajne1dx", Variables = new Dictionary { { "code", code } } }, cancellationToken); - logger.LogInformation("Verification code sent to {Mobile}", mobile); + _logger.LogInformation("Verification code sent to {Mobile}", mobile); } catch (Exception ex) { - logger.LogError(ex, "Failed to send SMS to {Mobile}", mobile); - // Continue even if SMS fails (for development/testing) + _logger.LogError(ex, "Failed to send SMS to {Mobile}", mobile); } return new SendCodeResponse @@ -131,8 +125,7 @@ public sealed class AuthService : IAuthService }; } - // Find valid verification code - var verificationCode = await dbContext.VerificationCodes + var verificationCode = await _dbContext.VerificationCodes .Where(v => v.Mobile == mobile && v.Code == code && !v.IsUsed && v.ExpiresAt > DateTime.UtcNow) .OrderByDescending(v => v.CreatedAt) .FirstOrDefaultAsync(cancellationToken); @@ -146,12 +139,10 @@ public sealed class AuthService : IAuthService }; } - // Mark code as used verificationCode.IsUsed = true; verificationCode.UsedAt = DateTime.UtcNow; - // Find or create user - var user = await dbContext.Users + var user = await _dbContext.Users .FirstOrDefaultAsync(u => u.Mobile == mobile, cancellationToken); if (user == null) @@ -162,16 +153,15 @@ public sealed class AuthService : IAuthService CreatedAt = DateTime.UtcNow, LastLoginAt = DateTime.UtcNow }; - dbContext.Users.Add(user); + _dbContext.Users.Add(user); } else { user.LastLoginAt = DateTime.UtcNow; } - await dbContext.SaveChangesAsync(cancellationToken); + await _dbContext.SaveChangesAsync(cancellationToken); - // Generate simple token (in production, use JWT) var token = GenerateToken(user.Id, mobile); return new VerifyCodeResponse @@ -193,7 +183,7 @@ public sealed class AuthService : IAuthService public async Task CanResendCodeAsync(string mobile, CancellationToken cancellationToken) { var normalizedMobile = NormalizeMobile(mobile); - var lastCode = await dbContext.VerificationCodes + var lastCode = await _dbContext.VerificationCodes .Where(v => v.Mobile == normalizedMobile && !v.IsUsed) .OrderByDescending(v => v.CreatedAt) .FirstOrDefaultAsync(cancellationToken); @@ -213,7 +203,6 @@ public sealed class AuthService : IAuthService private static string GenerateToken(int userId, string mobile) { - // Simple token generation (in production, use JWT) var data = $"{userId}:{mobile}:{DateTime.UtcNow:yyyyMMddHHmmss}"; var bytes = Encoding.UTF8.GetBytes(data); var hash = SHA256.HashData(bytes); @@ -225,10 +214,8 @@ public sealed class AuthService : IAuthService if (string.IsNullOrWhiteSpace(mobile)) return string.Empty; - // Remove all non-digit characters var normalized = new string(mobile.Where(char.IsDigit).ToArray()); - // Convert to standard format (09xxxxxxxxx) if (normalized.StartsWith("9") && normalized.Length == 10) normalized = "0" + normalized; else if (normalized.StartsWith("0098") && normalized.Length == 13) @@ -239,4 +226,3 @@ public sealed class AuthService : IAuthService return normalized; } } - diff --git a/src/GreenHome.Infrastructure/ChecklistService.cs b/src/GreenHome.Infrastructure/Services/Checklist/ChecklistService.cs similarity index 98% rename from src/GreenHome.Infrastructure/ChecklistService.cs rename to src/GreenHome.Infrastructure/Services/Checklist/ChecklistService.cs index a15f45e..286a93a 100644 --- a/src/GreenHome.Infrastructure/ChecklistService.cs +++ b/src/GreenHome.Infrastructure/Services/Checklist/ChecklistService.cs @@ -1,8 +1,9 @@ using AutoMapper; using GreenHome.Application; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Checklist; public sealed class ChecklistService : IChecklistService { @@ -149,4 +150,3 @@ public sealed class ChecklistService : IChecklistService return completion.Id; } } - diff --git a/src/GreenHome.Infrastructure/DevicePostService.cs b/src/GreenHome.Infrastructure/Services/Device/DevicePostService.cs similarity index 78% rename from src/GreenHome.Infrastructure/DevicePostService.cs rename to src/GreenHome.Infrastructure/Services/Device/DevicePostService.cs index 39b9139..f6e9e8d 100644 --- a/src/GreenHome.Infrastructure/DevicePostService.cs +++ b/src/GreenHome.Infrastructure/Services/Device/DevicePostService.cs @@ -1,17 +1,18 @@ using AutoMapper; using GreenHome.Application; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Device; public sealed class DevicePostService : IDevicePostService { - private readonly GreenHomeDbContext _context; + private readonly GreenHomeDbContext _dbContext; private readonly IMapper _mapper; public DevicePostService(GreenHomeDbContext context, IMapper mapper) { - _context = context; + _dbContext = context; _mapper = mapper; } @@ -19,16 +20,14 @@ public sealed class DevicePostService : IDevicePostService DevicePostFilter filter, CancellationToken cancellationToken) { - var query = _context.DevicePosts + var query = _dbContext.DevicePosts .Include(p => p.AuthorUser) .Include(p => p.Images) .AsNoTracking() .Where(p => p.DeviceId == filter.DeviceId); if (filter.AuthorUserId.HasValue) - { query = query.Where(p => p.AuthorUserId == filter.AuthorUserId.Value); - } var totalCount = await query.CountAsync(cancellationToken); @@ -51,7 +50,7 @@ public sealed class DevicePostService : IDevicePostService public async Task GetPostByIdAsync(int id, CancellationToken cancellationToken) { - var post = await _context.DevicePosts + var post = await _dbContext.DevicePosts .Include(p => p.AuthorUser) .Include(p => p.Images) .AsNoTracking() @@ -64,12 +63,9 @@ public sealed class DevicePostService : IDevicePostService CreateDevicePostRequest request, CancellationToken cancellationToken) { - // Verify user has access to device var hasAccess = await CanUserAccessDeviceAsync(request.AuthorUserId, request.DeviceId, cancellationToken); if (!hasAccess) - { throw new UnauthorizedAccessException("کاربر به این دستگاه دسترسی ندارد"); - } var post = new Domain.DevicePost { @@ -79,8 +75,8 @@ public sealed class DevicePostService : IDevicePostService CreatedAt = DateTime.UtcNow }; - _context.DevicePosts.Add(post); - await _context.SaveChangesAsync(cancellationToken); + _dbContext.DevicePosts.Add(post); + await _dbContext.SaveChangesAsync(cancellationToken); return post.Id; } @@ -89,33 +85,29 @@ public sealed class DevicePostService : IDevicePostService UpdateDevicePostRequest request, CancellationToken cancellationToken) { - var post = await _context.DevicePosts + var post = await _dbContext.DevicePosts .FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken); if (post == null) - { throw new InvalidOperationException($"پست با شناسه {request.Id} یافت نشد"); - } post.Content = request.Content; post.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(cancellationToken); + await _dbContext.SaveChangesAsync(cancellationToken); } public async Task DeletePostAsync(int id, CancellationToken cancellationToken) { - var post = await _context.DevicePosts + var post = await _dbContext.DevicePosts .Include(p => p.Images) .FirstOrDefaultAsync(p => p.Id == id, cancellationToken); if (post == null) - { throw new InvalidOperationException($"پست با شناسه {id} یافت نشد"); - } - _context.DevicePosts.Remove(post); - await _context.SaveChangesAsync(cancellationToken); + _dbContext.DevicePosts.Remove(post); + await _dbContext.SaveChangesAsync(cancellationToken); } public async Task AddImageToPostAsync( @@ -126,13 +118,11 @@ public sealed class DevicePostService : IDevicePostService long fileSize, CancellationToken cancellationToken) { - var post = await _context.DevicePosts + var post = await _dbContext.DevicePosts .FirstOrDefaultAsync(p => p.Id == postId, cancellationToken); if (post == null) - { throw new InvalidOperationException($"پست با شناسه {postId} یافت نشد"); - } var image = new Domain.DevicePostImage { @@ -144,24 +134,22 @@ public sealed class DevicePostService : IDevicePostService UploadedAt = DateTime.UtcNow }; - _context.DevicePostImages.Add(image); - await _context.SaveChangesAsync(cancellationToken); + _dbContext.DevicePostImages.Add(image); + await _dbContext.SaveChangesAsync(cancellationToken); return image.Id; } public async Task DeleteImageAsync(int imageId, CancellationToken cancellationToken) { - var image = await _context.DevicePostImages + var image = await _dbContext.DevicePostImages .FirstOrDefaultAsync(i => i.Id == imageId, cancellationToken); if (image == null) - { throw new InvalidOperationException($"تصویر با شناسه {imageId} یافت نشد"); - } - _context.DevicePostImages.Remove(image); - await _context.SaveChangesAsync(cancellationToken); + _dbContext.DevicePostImages.Remove(image); + await _dbContext.SaveChangesAsync(cancellationToken); } public async Task CanUserAccessDeviceAsync( @@ -169,17 +157,15 @@ public sealed class DevicePostService : IDevicePostService int deviceId, CancellationToken cancellationToken) { - // Check if user is the device owner or has access through DeviceUsers - var hasAccess = await _context.Devices + var hasAccess = await _dbContext.Devices .AnyAsync(d => d.Id == deviceId && d.UserId == userId, cancellationToken); if (!hasAccess) { - hasAccess = await _context.DeviceUsers + hasAccess = await _dbContext.DeviceUsers .AnyAsync(du => du.DeviceId == deviceId && du.UserId == userId, cancellationToken); } return hasAccess; } } - diff --git a/src/GreenHome.Infrastructure/DeviceService.cs b/src/GreenHome.Infrastructure/Services/Device/DeviceService.cs similarity index 66% rename from src/GreenHome.Infrastructure/DeviceService.cs rename to src/GreenHome.Infrastructure/Services/Device/DeviceService.cs index 5b19760..040cad3 100644 --- a/src/GreenHome.Infrastructure/DeviceService.cs +++ b/src/GreenHome.Infrastructure/Services/Device/DeviceService.cs @@ -1,126 +1,113 @@ -using AutoMapper; -using GreenHome.Application; -using Microsoft.EntityFrameworkCore; - -namespace GreenHome.Infrastructure; - -public sealed class DeviceService : IDeviceService -{ - private readonly GreenHomeDbContext dbContext; - private readonly IMapper mapper; - - public DeviceService(GreenHomeDbContext dbContext, IMapper mapper) - { - this.dbContext = dbContext; - this.mapper = mapper; - } - - public async Task AddDeviceAsync(DeviceDto dto, CancellationToken cancellationToken) - { - var entity = mapper.Map(dto); - dbContext.Devices.Add(entity); - await dbContext.SaveChangesAsync(cancellationToken); - return entity.Id; - } - - public async Task> ListAsync(CancellationToken cancellationToken) - { - var items = await dbContext.Devices - .AsNoTracking() - .Include(d => d.User) - .OrderBy(d => d.DeviceName) - .ToListAsync(cancellationToken); - return mapper.Map>(items); - } - - public async Task GetDeviceId(string deviceName,CancellationToken cancellationToken) - { - var item = await dbContext.Devices - .AsNoTracking() - .Include(d => d.User) - .Where(d=>d.DeviceName==deviceName) - .FirstOrDefaultAsync(cancellationToken); - return mapper.Map(item); - } - - public async Task> GetUserDevicesAsync(int userId, CancellationToken cancellationToken) - { - var items = await dbContext.Devices - .AsNoTracking() - .Include(d => d.User) - .Where(d => d.UserId == userId) - .OrderBy(d => d.DeviceName) - .ToListAsync(cancellationToken); - return mapper.Map>(items); - } - - public async Task> GetDevicesAsync(DeviceFilter filter, CancellationToken cancellationToken) - { - if (!filter.UserId.HasValue) - { - throw new ArgumentException("UserId is required", nameof(filter)); - } - - // Get user and role - var user = await dbContext.Users - .AsNoTracking() - .FirstOrDefaultAsync(u => u.Id == filter.UserId.Value, cancellationToken); - - if (user == null) - { - return new PagedResult - { - Items = Array.Empty(), - TotalCount = 0, - Page = filter.Page, - PageSize = filter.PageSize - }; - } - - // Build query based on role - IQueryable query = dbContext.Devices - .AsNoTracking() - .Include(d => d.User); - - if (user.Role == Domain.UserRole.Normal) - { - // Normal user: only own devices - query = query.Where(d => d.UserId == user.Id); - } - else if (user.Role == Domain.UserRole.Supervisor) - { - // Supervisor: devices assigned to them - query = query.Where(d => d.DeviceUsers.Any(du => du.UserId == user.Id)); - } - // Admin: all devices (no filter) - - // Apply search filter - if (!string.IsNullOrWhiteSpace(filter.Search)) - { - var searchTerm = filter.Search.Trim().ToLower(); - query = query.Where(d => - d.DeviceName.ToLower().Contains(searchTerm) || - d.User.Name.ToLower().Contains(searchTerm) || - d.User.Family.ToLower().Contains(searchTerm) || - d.Location.ToLower().Contains(searchTerm)); - } - - // Get total count - var totalCount = await query.CountAsync(cancellationToken); - - // Apply pagination - var items = await query - .OrderBy(d => d.DeviceName) - .Skip((filter.Page - 1) * filter.PageSize) - .Take(filter.PageSize) - .ToListAsync(cancellationToken); - - return new PagedResult - { - Items = mapper.Map>(items), - TotalCount = totalCount, - Page = filter.Page, - PageSize = filter.PageSize - }; - } -} +using AutoMapper; +using GreenHome.Application; +using GreenHome.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace GreenHome.Infrastructure.Services.Device; + +public sealed class DeviceService : IDeviceService +{ + private readonly GreenHomeDbContext _dbContext; + private readonly IMapper _mapper; + + public DeviceService(GreenHomeDbContext dbContext, IMapper mapper) + { + _dbContext = dbContext; + _mapper = mapper; + } + + public async Task AddDeviceAsync(DeviceDto dto, CancellationToken cancellationToken) + { + var entity = _mapper.Map(dto); + _dbContext.Devices.Add(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + return entity.Id; + } + + public async Task> ListAsync(CancellationToken cancellationToken) + { + var items = await _dbContext.Devices + .AsNoTracking() + .Include(d => d.User) + .OrderBy(d => d.DeviceName) + .ToListAsync(cancellationToken); + return _mapper.Map>(items); + } + + public async Task GetDeviceId(string deviceName, CancellationToken cancellationToken) + { + var item = await _dbContext.Devices + .AsNoTracking() + .Include(d => d.User) + .Where(d => d.DeviceName == deviceName) + .FirstOrDefaultAsync(cancellationToken); + return _mapper.Map(item); + } + + public async Task> GetUserDevicesAsync(int userId, CancellationToken cancellationToken) + { + var items = await _dbContext.Devices + .AsNoTracking() + .Include(d => d.User) + .Where(d => d.UserId == userId) + .OrderBy(d => d.DeviceName) + .ToListAsync(cancellationToken); + return _mapper.Map>(items); + } + + public async Task> GetDevicesAsync(DeviceFilter filter, CancellationToken cancellationToken) + { + if (!filter.UserId.HasValue) + throw new ArgumentException("UserId is required", nameof(filter)); + + var user = await _dbContext.Users + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Id == filter.UserId.Value, cancellationToken); + + if (user == null) + { + return new PagedResult + { + Items = Array.Empty(), + TotalCount = 0, + Page = filter.Page, + PageSize = filter.PageSize + }; + } + + IQueryable query = _dbContext.Devices + .AsNoTracking() + .Include(d => d.User); + + if (user.Role == Domain.UserRole.Normal) + query = query.Where(d => d.UserId == user.Id); + else if (user.Role == Domain.UserRole.Supervisor) + query = query.Where(d => d.DeviceUsers.Any(du => du.UserId == user.Id)); + + if (!string.IsNullOrWhiteSpace(filter.Search)) + { + var searchTerm = filter.Search.Trim().ToLower(); + query = query.Where(d => + d.DeviceName.ToLower().Contains(searchTerm) || + d.User.Name.ToLower().Contains(searchTerm) || + d.User.Family.ToLower().Contains(searchTerm) || + d.Location.ToLower().Contains(searchTerm)); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var items = await query + .OrderBy(d => d.DeviceName) + .Skip((filter.Page - 1) * filter.PageSize) + .Take(filter.PageSize) + .ToListAsync(cancellationToken); + + return new PagedResult + { + Items = _mapper.Map>(items), + TotalCount = totalCount, + Page = filter.Page, + PageSize = filter.PageSize + }; + } +} diff --git a/src/GreenHome.Infrastructure/DeviceSettingsService.cs b/src/GreenHome.Infrastructure/Services/Device/DeviceSettingsService.cs similarity index 58% rename from src/GreenHome.Infrastructure/DeviceSettingsService.cs rename to src/GreenHome.Infrastructure/Services/Device/DeviceSettingsService.cs index c8a45b4..30acec7 100644 --- a/src/GreenHome.Infrastructure/DeviceSettingsService.cs +++ b/src/GreenHome.Infrastructure/Services/Device/DeviceSettingsService.cs @@ -1,54 +1,53 @@ -using AutoMapper; -using GreenHome.Application; -using Microsoft.EntityFrameworkCore; - -namespace GreenHome.Infrastructure; - -public sealed class DeviceSettingsService : IDeviceSettingsService -{ - private readonly GreenHomeDbContext dbContext; - private readonly IMapper mapper; - - public DeviceSettingsService(GreenHomeDbContext dbContext, IMapper mapper) - { - this.dbContext = dbContext; - this.mapper = mapper; - } - - public async Task GetByDeviceIdAsync(int deviceId, CancellationToken cancellationToken) - { - var settings = await dbContext.DeviceSettings - .Include(x => x.Device) - .AsNoTracking() - .FirstOrDefaultAsync(x => x.DeviceId == deviceId, cancellationToken); - - return settings != null ? mapper.Map(settings) : null; - } - - public async Task CreateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken) - { - var entity = mapper.Map(dto); - entity.CreatedAt = DateTime.UtcNow; - entity.UpdatedAt = DateTime.UtcNow; - - dbContext.DeviceSettings.Add(entity); - await dbContext.SaveChangesAsync(cancellationToken); - return entity.Id; - } - - public async Task UpdateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken) - { - var entity = await dbContext.DeviceSettings - .FirstOrDefaultAsync(x => x.DeviceId == dto.DeviceId, cancellationToken); - - if (entity == null) - { - throw new InvalidOperationException($"Device settings not found for device ID {dto.DeviceId}"); - } - - mapper.Map(dto, entity); - entity.UpdatedAt = DateTime.UtcNow; - - await dbContext.SaveChangesAsync(cancellationToken); - } -} +using AutoMapper; +using GreenHome.Application; +using GreenHome.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace GreenHome.Infrastructure.Services.Device; + +public sealed class DeviceSettingsService : IDeviceSettingsService +{ + private readonly GreenHomeDbContext _dbContext; + private readonly IMapper _mapper; + + public DeviceSettingsService(GreenHomeDbContext dbContext, IMapper mapper) + { + _dbContext = dbContext; + _mapper = mapper; + } + + public async Task GetByDeviceIdAsync(int deviceId, CancellationToken cancellationToken) + { + var settings = await _dbContext.DeviceSettings + .Include(x => x.Device) + .AsNoTracking() + .FirstOrDefaultAsync(x => x.DeviceId == deviceId, cancellationToken); + + return settings != null ? _mapper.Map(settings) : null; + } + + public async Task CreateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken) + { + var entity = _mapper.Map(dto); + entity.CreatedAt = DateTime.UtcNow; + entity.UpdatedAt = DateTime.UtcNow; + + _dbContext.DeviceSettings.Add(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + return entity.Id; + } + + public async Task UpdateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken) + { + var entity = await _dbContext.DeviceSettings + .FirstOrDefaultAsync(x => x.DeviceId == dto.DeviceId, cancellationToken); + + if (entity == null) + throw new InvalidOperationException($"Device settings not found for device ID {dto.DeviceId}"); + + _mapper.Map(dto, entity); + entity.UpdatedAt = DateTime.UtcNow; + + await _dbContext.SaveChangesAsync(cancellationToken); + } +} diff --git a/src/GreenHome.Infrastructure/DeviceTokenService.cs b/src/GreenHome.Infrastructure/Services/Device/DeviceTokenService.cs similarity index 68% rename from src/GreenHome.Infrastructure/DeviceTokenService.cs rename to src/GreenHome.Infrastructure/Services/Device/DeviceTokenService.cs index c5dadd2..20967e4 100644 --- a/src/GreenHome.Infrastructure/DeviceTokenService.cs +++ b/src/GreenHome.Infrastructure/Services/Device/DeviceTokenService.cs @@ -1,32 +1,27 @@ using GreenHome.Application; +using GreenHome.Infrastructure.Data; using GreenHome.Sms.Ippanel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Device; -/// -/// سرویس مدیریت توکن و تنظیمات دستگاه -/// public sealed class DeviceTokenService : IDeviceTokenService { - private readonly GreenHomeDbContext dbContext; - private readonly ISmsService smsService; - private readonly ILogger logger; + private readonly GreenHomeDbContext _dbContext; + private readonly ISmsService _smsService; + private readonly ILogger _logger; public DeviceTokenService( GreenHomeDbContext dbContext, ISmsService smsService, ILogger logger) { - this.dbContext = dbContext; - this.smsService = smsService; - this.logger = logger; + _dbContext = dbContext; + _smsService = smsService; + _logger = logger; } - /// - /// دریافت فاصله زمانی آپلود بر اساس شماره تلفن یا شناسه دستگاه - /// public async Task GetUploadIntervalAsync( GetUploadIntervalRequest request, CancellationToken cancellationToken) @@ -35,16 +30,15 @@ public sealed class DeviceTokenService : IDeviceTokenService { Domain.DeviceSettings? settings = null; - // جستجو بر اساس DeviceId یا DevicePhoneNumber if (request.DeviceId.HasValue) { - settings = await dbContext.DeviceSettings + settings = await _dbContext.DeviceSettings .AsNoTracking() .FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId.Value, cancellationToken); } else if (!string.IsNullOrWhiteSpace(request.DevicePhoneNumber)) { - settings = await dbContext.DeviceSettings + settings = await _dbContext.DeviceSettings .AsNoTracking() .FirstOrDefaultAsync(ds => ds.DevicePhoneNumber == request.DevicePhoneNumber, cancellationToken); } @@ -66,7 +60,7 @@ public sealed class DeviceTokenService : IDeviceTokenService } catch (Exception ex) { - logger.LogError(ex, "Error getting upload interval for device"); + _logger.LogError(ex, "Error getting upload interval for device"); return new GetUploadIntervalResponse { Success = false, @@ -75,17 +69,13 @@ public sealed class DeviceTokenService : IDeviceTokenService } } - /// - /// درخواست توکن دستگاه (تولید و ارسال کد) - /// public async Task RequestDeviceTokenAsync( RequestDeviceTokenRequest request, CancellationToken cancellationToken) { try { - // پیدا کردن تنظیمات دستگاه بر اساس شماره تلفن - var settings = await dbContext.DeviceSettings + var settings = await _dbContext.DeviceSettings .FirstOrDefaultAsync(ds => ds.DevicePhoneNumber == request.DevicePhoneNumber, cancellationToken); if (settings == null) @@ -97,28 +87,23 @@ public sealed class DeviceTokenService : IDeviceTokenService }; } - // تولید کد توکن 5 رقمی var random = new Random(); var tokenCode = random.Next(10000, 99999).ToString(); - - // تولید کد تایید بر اساس فرمول: (TokenCode * 7 + 12345) % 100000 var verificationCode = ((int.Parse(tokenCode) * 7 + 12345) % 100000).ToString("D5"); - // ذخیره کدها settings.TokenCode = tokenCode; settings.VerificationCode = verificationCode; - settings.TokenExpiresAt = DateTime.UtcNow.AddMinutes(30); // اعتبار 10 دقیقه + settings.TokenExpiresAt = DateTime.UtcNow.AddMinutes(30); settings.UpdatedAt = DateTime.UtcNow; - await dbContext.SaveChangesAsync(cancellationToken); + await _dbContext.SaveChangesAsync(cancellationToken); - // ارسال کد توکن از طریق پیامک الگویی try { - await smsService.SendPatternSmsAsync(new PatternSmsRequest + await _smsService.SendPatternSmsAsync(new PatternSmsRequest { Recipients = new List { request.DevicePhoneNumber }, - PatternCode = "gfukab9r0nca0pt", // TODO: کد الگوی پیامک را اینجا قرار دهید + PatternCode = "gfukab9r0nca0pt", Variables = new Dictionary { { "tel", "09192530212" }, @@ -128,7 +113,7 @@ public sealed class DeviceTokenService : IDeviceTokenService } catch (Exception smsEx) { - logger.LogError(smsEx, "Error sending token SMS to {PhoneNumber}", request.DevicePhoneNumber); + _logger.LogError(smsEx, "Error sending token SMS to {PhoneNumber}", request.DevicePhoneNumber); return new RequestDeviceTokenResponse { Success = false, @@ -136,7 +121,7 @@ public sealed class DeviceTokenService : IDeviceTokenService }; } - logger.LogInformation("Token requested for device phone {PhoneNumber}", request.DevicePhoneNumber); + _logger.LogInformation("Token requested for device phone {PhoneNumber}", request.DevicePhoneNumber); return new RequestDeviceTokenResponse { @@ -147,7 +132,7 @@ public sealed class DeviceTokenService : IDeviceTokenService } catch (Exception ex) { - logger.LogError(ex, "Error requesting device token"); + _logger.LogError(ex, "Error requesting device token"); return new RequestDeviceTokenResponse { Success = false, @@ -156,17 +141,13 @@ public sealed class DeviceTokenService : IDeviceTokenService } } - /// - /// تایید توکن دستگاه (ارسال تنظیمات) - /// public async Task VerifyDeviceTokenAsync( VerifyDeviceTokenRequest request, CancellationToken cancellationToken) { try { - // پیدا کردن تنظیمات دستگاه - var settings = await dbContext.DeviceSettings + var settings = await _dbContext.DeviceSettings .Include(ds => ds.Device) .FirstOrDefaultAsync(ds => ds.DevicePhoneNumber == request.DevicePhoneNumber, cancellationToken); @@ -179,7 +160,6 @@ public sealed class DeviceTokenService : IDeviceTokenService }; } - // بررسی انقضای توکن if (settings.TokenExpiresAt == null || settings.TokenExpiresAt < DateTime.UtcNow) { return new VerifyDeviceTokenResponse @@ -189,7 +169,6 @@ public sealed class DeviceTokenService : IDeviceTokenService }; } - // بررسی کد تایید if (settings.VerificationCode != request.VerificationCode) { return new VerifyDeviceTokenResponse @@ -198,21 +177,20 @@ public sealed class DeviceTokenService : IDeviceTokenService Message = "کد تایید نادرست است" }; } - Random rnd = new Random(); - // آماده‌سازی پارامترها برای ارسال پیامک الگویی + + var rnd = new Random(); var deviceId = settings.Device.Id + "01"; var uploadInterval = rnd.Next(1, 9) + "" + settings.UploadIntervalMin; var smsInterval = settings.MinimumSmsIntervalMinutes > 0 ? "1" + settings.MinimumSmsIntervalMinutes : "0" + rnd.Next(1, 9); - var sysNumber = rnd.Next(139, 97654); // عدد رندوم SysNumber - var simType = rnd.Next(1, 9).ToString() + ((int?)settings.SimCardType)?.ToString() ?? "1"; // مقدار عددی enum + var sysNumber = rnd.Next(139, 97654); + var simType = rnd.Next(1, 9).ToString() + ((int?)settings.SimCardType)?.ToString() ?? "1"; - // ارسال تنظیمات از طریق پیامک الگویی try { - await smsService.SendPatternSmsAsync(new PatternSmsRequest + await _smsService.SendPatternSmsAsync(new PatternSmsRequest { Recipients = new List { request.DevicePhoneNumber }, - PatternCode = "kx3kfqri7g09r02", // TODO: کد الگوی پیامک را اینجا قرار دهید + PatternCode = "kx3kfqri7g09r02", Variables = new Dictionary { { "deviceId", deviceId }, @@ -225,7 +203,7 @@ public sealed class DeviceTokenService : IDeviceTokenService } catch (Exception smsEx) { - logger.LogError(smsEx, "Error sending settings SMS to {PhoneNumber}", request.DevicePhoneNumber); + _logger.LogError(smsEx, "Error sending settings SMS to {PhoneNumber}", request.DevicePhoneNumber); return new VerifyDeviceTokenResponse { Success = false, @@ -233,14 +211,13 @@ public sealed class DeviceTokenService : IDeviceTokenService }; } - // پاک کردن کدها بعد از استفاده موفق settings.TokenCode = null; settings.VerificationCode = null; settings.TokenExpiresAt = null; settings.UpdatedAt = DateTime.UtcNow; - await dbContext.SaveChangesAsync(cancellationToken); + await _dbContext.SaveChangesAsync(cancellationToken); - logger.LogInformation("Device token verified for {PhoneNumber}", request.DevicePhoneNumber); + _logger.LogInformation("Device token verified for {PhoneNumber}", request.DevicePhoneNumber); return new VerifyDeviceTokenResponse { @@ -251,7 +228,7 @@ public sealed class DeviceTokenService : IDeviceTokenService } catch (Exception ex) { - logger.LogError(ex, "Error verifying device token"); + _logger.LogError(ex, "Error verifying device token"); return new VerifyDeviceTokenResponse { Success = false, @@ -260,4 +237,3 @@ public sealed class DeviceTokenService : IDeviceTokenService } } } - diff --git a/src/GreenHome.Infrastructure/DailyReportService.cs b/src/GreenHome.Infrastructure/Services/Report/DailyReportService.cs similarity index 85% rename from src/GreenHome.Infrastructure/DailyReportService.cs rename to src/GreenHome.Infrastructure/Services/Report/DailyReportService.cs index b6154d0..186fe37 100644 --- a/src/GreenHome.Infrastructure/DailyReportService.cs +++ b/src/GreenHome.Infrastructure/Services/Report/DailyReportService.cs @@ -2,14 +2,16 @@ using System.Diagnostics; using System.Text; using GreenHome.AI.DeepSeek; using GreenHome.Application; +using GreenHome.Infrastructure.Data; +using GreenHome.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Report; -public class DailyReportService : IDailyReportService +public sealed class DailyReportService : IDailyReportService { - private readonly GreenHomeDbContext _context; + private readonly GreenHomeDbContext _dbContext; private readonly IDeepSeekService _deepSeekService; private readonly ILogger _logger; @@ -18,7 +20,7 @@ public class DailyReportService : IDailyReportService IDeepSeekService deepSeekService, ILogger logger) { - _context = context; + _dbContext = context; _deepSeekService = deepSeekService; _logger = logger; } @@ -27,14 +29,10 @@ public class DailyReportService : IDailyReportService DailyReportRequest request, CancellationToken cancellationToken) { - // Validate Persian date format - if (!IsValidPersianDate(request.PersianDate, out var year, out var month, out var day)) - { + if (!PersianDateHelper.TryParsePersianDate(request.PersianDate, out var year, out var month, out var day)) throw new ArgumentException("تاریخ شمسی باید به فرمت yyyy/MM/dd باشد", nameof(request.PersianDate)); - } - // Check if report already exists - var existingReport = await _context.DailyReports + var existingReport = await _dbContext.DailyReports .Include(r => r.Device) .FirstOrDefaultAsync( r => r.DeviceId == request.DeviceId && r.PersianDate == request.PersianDate, @@ -61,21 +59,16 @@ public class DailyReportService : IDailyReportService }; } - // Get device info - var device = await _context.Devices + var device = await _dbContext.Devices .FirstOrDefaultAsync(d => d.Id == request.DeviceId, cancellationToken); if (device == null) - { throw new InvalidOperationException($"دستگاه با شناسه {request.DeviceId} یافت نشد"); - } - // Get device settings (including ProductType if available) - var deviceSettings = await _context.DeviceSettings + var deviceSettings = await _dbContext.DeviceSettings .FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId, cancellationToken); - // Query telemetry data for the specified date - var telemetryRecords = await _context.TelemetryRecords + var telemetryRecords = await _dbContext.TelemetryRecords .Where(t => t.DeviceId == request.DeviceId && t.PersianDate == request.PersianDate) .OrderBy(t => t.TimestampUtc) .Select(t => new @@ -89,12 +82,9 @@ public class DailyReportService : IDailyReportService .ToListAsync(cancellationToken); if (telemetryRecords.Count == 0) - { throw new InvalidOperationException( $"هیچ رکوردی برای دستگاه {request.DeviceId} در تاریخ {request.PersianDate} یافت نشد"); - } - // Sample records: take first record from every 20 records var sampledRecords = telemetryRecords .Select((record, index) => new { record, index }) .Where(x => x.index % 20 == 0) @@ -105,24 +95,21 @@ public class DailyReportService : IDailyReportService "تعداد {TotalCount} رکورد یافت شد. نمونه‌برداری: {SampledCount} رکورد", telemetryRecords.Count, sampledRecords.Count); - // Build the data string for AI var dataBuilder = new StringBuilder(); dataBuilder.AppendLine("زمان | دما (°C) | رطوبت (%) | نور (Lux) | CO (PPM)"); dataBuilder.AppendLine("------|----------|-----------|-----------|----------"); foreach (var record in sampledRecords) { - // Convert UTC to local time for display - var localTime = record.TimestampUtc.AddHours(3.5); // Iran timezone (UTC+3:30) + var localTime = record.TimestampUtc.AddHours(3.5); dataBuilder.AppendLine( $"{localTime:HH:mm:ss} | {record.TemperatureC:F1} | {record.HumidityPercent:F1} | {record.Lux:F1} | {record.GasPPM}"); } - // Prepare the question for AI - var productTypeInfo = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType) - ? $" محصول کشت شده: {deviceSettings.ProductType}." + var productTypeInfo = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType) + ? $" محصول کشت شده: {deviceSettings.ProductType}." : string.Empty; - + var areaInfo = deviceSettings?.AreaSquareMeters != null ? $" مساحت گلخانه: {deviceSettings.AreaSquareMeters:F1} متر مربع." : string.Empty; @@ -139,16 +126,15 @@ public class DailyReportService : IDailyReportService خلاصه و مفید باش (حداکثر 300 کلمه)."; - // Send to DeepSeek var stopwatch = Stopwatch.StartNew(); ChatResponse? aiResponse; - + try { var systemMessage = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType) ? $"تو یک متخصص کشاورزی و گلخانه هستی که در کشت {deviceSettings.ProductType} تخصص داری و داده‌های تلمتری رو تحلیل می‌کنی." : "تو یک متخصص کشاورزی و گلخانه هستی که داده‌های تلمتری رو تحلیل می‌کنی."; - + var chatRequest = new ChatRequest { Model = "deepseek-chat", @@ -170,15 +156,12 @@ public class DailyReportService : IDailyReportService stopwatch.Stop(); - if (aiResponse?.Choices == null || aiResponse.Choices.Count == 0 || + if (aiResponse?.Choices == null || aiResponse.Choices.Count == 0 || string.IsNullOrWhiteSpace(aiResponse.Choices[0].Message?.Content)) - { throw new InvalidOperationException("پاسخ نامعتبر از سرویس هوش مصنوعی"); - } var analysis = aiResponse.Choices[0].Message!.Content; - // Save the report var dailyReport = new Domain.DailyReport { DeviceId = request.DeviceId, @@ -197,8 +180,8 @@ public class DailyReportService : IDailyReportService CreatedAt = DateTime.UtcNow }; - _context.DailyReports.Add(dailyReport); - await _context.SaveChangesAsync(cancellationToken); + _dbContext.DailyReports.Add(dailyReport); + await _dbContext.SaveChangesAsync(cancellationToken); _logger.LogInformation( "گزارش روزانه جدید برای دستگاه {DeviceId} و تاریخ {Date} ایجاد شد. توکن مصرف شده: {Tokens}", @@ -219,49 +202,21 @@ public class DailyReportService : IDailyReportService }; } - private static bool IsValidPersianDate(string persianDate, out int year, out int month, out int day) - { - year = month = day = 0; - - if (string.IsNullOrWhiteSpace(persianDate)) - return false; - - var parts = persianDate.Split('/'); - if (parts.Length != 3) - return false; - - if (!int.TryParse(parts[0], out year) || year < 1300 || year > 1500) - return false; - - if (!int.TryParse(parts[1], out month) || month < 1 || month > 12) - return false; - - if (!int.TryParse(parts[2], out day) || day < 1 || day > 31) - return false; - - return true; - } - public async Task GetWeeklyAnalysisAsync( WeeklyAnalysisRequest request, CancellationToken cancellationToken) { - // Get device info - var device = await _context.Devices + var device = await _dbContext.Devices .FirstOrDefaultAsync(d => d.Id == request.DeviceId, cancellationToken); if (device == null) - { throw new InvalidOperationException($"دستگاه با شناسه {request.DeviceId} یافت نشد"); - } - // Get device settings - var deviceSettings = await _context.DeviceSettings + var deviceSettings = await _dbContext.DeviceSettings .FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId, cancellationToken); - // Query telemetry data for the week - var telemetryRecords = await _context.TelemetryRecords - .Where(t => t.DeviceId == request.DeviceId && + var telemetryRecords = await _dbContext.TelemetryRecords + .Where(t => t.DeviceId == request.DeviceId && string.Compare(t.PersianDate, request.StartDate) >= 0 && string.Compare(t.PersianDate, request.EndDate) <= 0) .OrderBy(t => t.TimestampUtc) @@ -277,12 +232,9 @@ public class DailyReportService : IDailyReportService .ToListAsync(cancellationToken); if (telemetryRecords.Count == 0) - { throw new InvalidOperationException( $"هیچ رکوردی برای دستگاه {request.DeviceId} در بازه {request.StartDate} تا {request.EndDate} یافت نشد"); - } - // Sample 1 per 100 records var sampledRecords = telemetryRecords .Select((record, index) => new { record, index }) .Where(x => x.index % 100 == 0) @@ -293,7 +245,6 @@ public class DailyReportService : IDailyReportService "تعداد {TotalCount} رکورد یافت شد. نمونه‌برداری هفتگی: {SampledCount} رکورد", telemetryRecords.Count, sampledRecords.Count); - // Build the data string var dataBuilder = new StringBuilder(); dataBuilder.AppendLine("تاریخ | زمان | دما (°C) | رطوبت (%) | نور (Lux) | CO (PPM)"); dataBuilder.AppendLine("---------|----------|----------|-----------|-----------|----------"); @@ -305,7 +256,6 @@ public class DailyReportService : IDailyReportService $"{record.PersianDate} | {localTime:HH:mm} | {record.TemperatureC:F1} | {record.HumidityPercent:F1} | {record.Lux:F1} | {record.GasPPM}"); } - // Prepare AI prompt var productTypeInfo = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType) ? $" محصول کشت شده: {deviceSettings.ProductType}." : string.Empty; @@ -330,7 +280,6 @@ public class DailyReportService : IDailyReportService ? $"تو یک متخصص کشاورزی و گلخانه هستی که در کشت {deviceSettings.ProductType} تخصص داری و داده‌های تلمتری رو تحلیل می‌کنی." : "تو یک متخصص کشاورزی و گلخانه هستی که داده‌های تلمتری رو تحلیل می‌کنی."; - // Send to DeepSeek var stopwatch = Stopwatch.StartNew(); var chatRequest = new ChatRequest { @@ -348,9 +297,7 @@ public class DailyReportService : IDailyReportService if (aiResponse?.Choices == null || aiResponse.Choices.Count == 0 || string.IsNullOrWhiteSpace(aiResponse.Choices[0].Message?.Content)) - { throw new InvalidOperationException("پاسخ نامعتبر از سرویس هوش مصنوعی"); - } return new DailyReportResponse { @@ -371,21 +318,16 @@ public class DailyReportService : IDailyReportService MonthlyAnalysisRequest request, CancellationToken cancellationToken) { - // Get device info - var device = await _context.Devices + var device = await _dbContext.Devices .FirstOrDefaultAsync(d => d.Id == request.DeviceId, cancellationToken); if (device == null) - { throw new InvalidOperationException($"دستگاه با شناسه {request.DeviceId} یافت نشد"); - } - // Get device settings - var deviceSettings = await _context.DeviceSettings + var deviceSettings = await _dbContext.DeviceSettings .FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId, cancellationToken); - // Get all daily reports for this month - var dailyReports = await _context.DailyReports + var dailyReports = await _dbContext.DailyReports .Where(dr => dr.DeviceId == request.DeviceId && dr.PersianYear == request.Year && dr.PersianMonth == request.Month) @@ -394,12 +336,9 @@ public class DailyReportService : IDailyReportService .ToListAsync(cancellationToken); if (dailyReports.Count == 0) - { throw new InvalidOperationException( $"هیچ تحلیل روزانه‌ای برای دستگاه {request.DeviceId} در ماه {request.Month} سال {request.Year} یافت نشد"); - } - // Build summary of daily analyses var summaryBuilder = new StringBuilder(); summaryBuilder.AppendLine($"تحلیل‌های روزانه ماه {request.Month} سال {request.Year}:"); summaryBuilder.AppendLine(); @@ -413,7 +352,6 @@ public class DailyReportService : IDailyReportService summaryBuilder.AppendLine(); } - // Prepare AI prompt var productTypeInfo = !string.IsNullOrWhiteSpace(deviceSettings?.ProductType) ? $" محصول کشت شده: {deviceSettings.ProductType}." : string.Empty; @@ -439,7 +377,6 @@ public class DailyReportService : IDailyReportService ? $"تو یک متخصص کشاورزی و گلخانه هستی که در کشت {deviceSettings.ProductType} تخصص داری. تحلیل‌های روزانه رو بررسی کن و یک جمع‌بندی ماهانه جامع ارائه بده." : "تو یک متخصص کشاورزی و گلخانه هستی. تحلیل‌های روزانه رو بررسی کن و یک جمع‌بندی ماهانه جامع ارائه بده."; - // Send to DeepSeek var stopwatch = Stopwatch.StartNew(); var chatRequest = new ChatRequest { @@ -457,9 +394,7 @@ public class DailyReportService : IDailyReportService if (aiResponse?.Choices == null || aiResponse.Choices.Count == 0 || string.IsNullOrWhiteSpace(aiResponse.Choices[0].Message?.Content)) - { throw new InvalidOperationException("پاسخ نامعتبر از سرویس هوش مصنوعی"); - } return new DailyReportResponse { @@ -476,4 +411,3 @@ public class DailyReportService : IDailyReportService }; } } - diff --git a/src/GreenHome.Infrastructure/MonthlyReportService.cs b/src/GreenHome.Infrastructure/Services/Report/MonthlyReportService.cs similarity index 81% rename from src/GreenHome.Infrastructure/MonthlyReportService.cs rename to src/GreenHome.Infrastructure/Services/Report/MonthlyReportService.cs index fad1cee..d84f3b6 100644 --- a/src/GreenHome.Infrastructure/MonthlyReportService.cs +++ b/src/GreenHome.Infrastructure/Services/Report/MonthlyReportService.cs @@ -1,15 +1,16 @@ using GreenHome.Application; +using GreenHome.Infrastructure.Data; using Microsoft.EntityFrameworkCore; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Report; public sealed class MonthlyReportService : IMonthlyReportService { - private readonly GreenHomeDbContext _context; + private readonly GreenHomeDbContext _dbContext; public MonthlyReportService(GreenHomeDbContext context) { - _context = context; + _dbContext = context; } public async Task GetMonthlyReportAsync( @@ -18,16 +19,13 @@ public sealed class MonthlyReportService : IMonthlyReportService int month, CancellationToken cancellationToken) { - var device = await _context.Devices + var device = await _dbContext.Devices .FirstOrDefaultAsync(d => d.Id == deviceId, cancellationToken); if (device == null) - { throw new InvalidOperationException($"دستگاه با شناسه {deviceId} یافت نشد"); - } - // Get alert logs for the month - var alertLogs = await _context.AlertLogs + var alertLogs = await _dbContext.AlertLogs .Where(al => al.DeviceId == deviceId && al.SentAt.Year == year && al.SentAt.Month == month) @@ -40,8 +38,7 @@ public sealed class MonthlyReportService : IMonthlyReportService var failedAlerts = alertLogs.Count(al => al.Status == Domain.AlertStatus.Failed); var powerOutageAlerts = alertLogs.Count(al => al.AlertType == Domain.AlertType.PowerOutage); - // Get telemetry statistics - var telemetryData = await _context.TelemetryRecords + var telemetryData = await _dbContext.TelemetryRecords .Where(t => t.DeviceId == deviceId && t.PersianYear == year && t.PersianMonth == month) @@ -58,35 +55,26 @@ public sealed class MonthlyReportService : IMonthlyReportService var avgGas = telemetryData.Any() ? (int)telemetryData.Average(t => t.GasPPM) : 0; var maxGas = telemetryData.Any() ? telemetryData.Max(t => t.GasPPM) : 0; - // Get user activity - var userReportsCount = await _context.UserDailyReports + var userReportsCount = await _dbContext.UserDailyReports .CountAsync(r => r.DeviceId == deviceId && r.PersianYear == year && r.PersianMonth == month, cancellationToken); - var checklistCompletionsCount = await _context.ChecklistCompletions + var checklistCompletionsCount = await _dbContext.ChecklistCompletions .Where(cc => cc.Checklist.DeviceId == deviceId) .CountAsync(cc => cc.PersianDate.StartsWith($"{year}/{month:D2}"), cancellationToken); - var dailyAnalysesCount = await _context.DailyReports + var dailyAnalysesCount = await _dbContext.DailyReports .CountAsync(dr => dr.DeviceId == deviceId && dr.PersianYear == year && dr.PersianMonth == month, cancellationToken); - // Generate performance summary var performanceSummary = GeneratePerformanceSummary( - totalTelemetryRecords, - totalAlerts, - successfulAlerts, - failedAlerts, - avgTemp, - avgHumidity, - avgLux, - avgGas, - userReportsCount, - checklistCompletionsCount); + totalTelemetryRecords, totalAlerts, successfulAlerts, failedAlerts, + avgTemp, avgHumidity, avgLux, avgGas, + userReportsCount, checklistCompletionsCount); return new MonthlyReportDto { @@ -118,17 +106,10 @@ public sealed class MonthlyReportService : IMonthlyReportService }; } - private string GeneratePerformanceSummary( - int totalRecords, - int totalAlerts, - int successfulAlerts, - int failedAlerts, - decimal avgTemp, - decimal avgHumidity, - decimal avgLux, - int avgGas, - int userReports, - int checklistCompletions) + private static string GeneratePerformanceSummary( + int totalRecords, int totalAlerts, int successfulAlerts, int failedAlerts, + decimal avgTemp, decimal avgHumidity, decimal avgLux, int avgGas, + int userReports, int checklistCompletions) { var summary = new List(); @@ -157,7 +138,6 @@ public sealed class MonthlyReportService : IMonthlyReportService summary.Add($" • تکمیل چک‌لیست: {checklistCompletions}"); } - // Performance rating summary.Add(""); var rating = CalculatePerformanceRating(totalRecords, failedAlerts, totalAlerts); summary.Add($"⭐ ارزیابی کلی: {rating}"); @@ -165,7 +145,7 @@ public sealed class MonthlyReportService : IMonthlyReportService return string.Join("\n", summary); } - private string CalculatePerformanceRating(int totalRecords, int failedAlerts, int totalAlerts) + private static string CalculatePerformanceRating(int totalRecords, int failedAlerts, int totalAlerts) { if (totalRecords == 0) return "بدون داده"; @@ -182,4 +162,3 @@ public sealed class MonthlyReportService : IMonthlyReportService return "نیاز به بررسی 🔧"; } } - diff --git a/src/GreenHome.Infrastructure/UserDailyReportService.cs b/src/GreenHome.Infrastructure/Services/Report/UserDailyReportService.cs similarity index 88% rename from src/GreenHome.Infrastructure/UserDailyReportService.cs rename to src/GreenHome.Infrastructure/Services/Report/UserDailyReportService.cs index 0ba1cb6..22610ff 100644 --- a/src/GreenHome.Infrastructure/UserDailyReportService.cs +++ b/src/GreenHome.Infrastructure/Services/Report/UserDailyReportService.cs @@ -1,8 +1,10 @@ using AutoMapper; using GreenHome.Application; +using GreenHome.Infrastructure.Data; +using GreenHome.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Report; public sealed class UserDailyReportService : IUserDailyReportService { @@ -87,8 +89,8 @@ public sealed class UserDailyReportService : IUserDailyReportService CreateUserDailyReportRequest request, CancellationToken cancellationToken) { - // Validate date format - if (!IsValidPersianDate(request.PersianDate, out var year, out var month, out var day)) + // Validate Persian date format + if (!PersianDateHelper.TryParsePersianDate(request.PersianDate, out var year, out var month, out var day)) { throw new ArgumentException("تاریخ شمسی باید به فرمت yyyy/MM/dd باشد"); } @@ -198,28 +200,4 @@ public sealed class UserDailyReportService : IUserDailyReportService _context.ReportImages.Remove(image); await _context.SaveChangesAsync(cancellationToken); } - - private static bool IsValidPersianDate(string persianDate, out int year, out int month, out int day) - { - year = month = day = 0; - - if (string.IsNullOrWhiteSpace(persianDate)) - return false; - - var parts = persianDate.Split('/'); - if (parts.Length != 3) - return false; - - if (!int.TryParse(parts[0], out year) || year < 1300 || year > 1500) - return false; - - if (!int.TryParse(parts[1], out month) || month < 1 || month > 12) - return false; - - if (!int.TryParse(parts[2], out day) || day < 1 || day > 31) - return false; - - return true; - } } - diff --git a/src/GreenHome.Infrastructure/SunCalculatorService.cs b/src/GreenHome.Infrastructure/Services/Sun/SunCalculatorService.cs similarity index 93% rename from src/GreenHome.Infrastructure/SunCalculatorService.cs rename to src/GreenHome.Infrastructure/Services/Sun/SunCalculatorService.cs index e82d19e..8cc34b5 100644 --- a/src/GreenHome.Infrastructure/SunCalculatorService.cs +++ b/src/GreenHome.Infrastructure/Services/Sun/SunCalculatorService.cs @@ -1,6 +1,6 @@ using GreenHome.Application; -namespace GreenHome.Infrastructure; +namespace GreenHome.Infrastructure.Services.Sun; /// /// سرویس محاسبه طلوع و غروب خورشید @@ -11,10 +11,10 @@ public sealed class SunCalculatorService : ISunCalculatorService { var lat = (double)latitude; var lng = (double)longitude; - + // Calculate sunrise and sunset times var (sunrise, sunset) = CalculateSunriseSunset(dateTime, lat, lng); - + // Check if current time is between sunrise and sunset var currentTime = dateTime.TimeOfDay; return currentTime >= sunrise && currentTime <= sunset; @@ -28,33 +28,33 @@ public sealed class SunCalculatorService : ISunCalculatorService // Sun's mean longitude var sunMeanLongitude = (280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)) % 360; - + // Sun's mean anomaly var sunMeanAnomaly = 357.52911 + julianCentury * (35999.05029 - 0.0001537 * julianCentury); - + // Earth's orbit eccentricity var eccentricity = 0.016708634 - julianCentury * (0.000042037 + 0.0000001267 * julianCentury); - + // Sun's equation of center var sunCenter = Math.Sin(ToRadians(sunMeanAnomaly)) * (1.914602 - julianCentury * (0.004817 + 0.000014 * julianCentury)) + Math.Sin(ToRadians(2 * sunMeanAnomaly)) * (0.019993 - 0.000101 * julianCentury) + Math.Sin(ToRadians(3 * sunMeanAnomaly)) * 0.000289; - + // Sun's true longitude var sunTrueLongitude = sunMeanLongitude + sunCenter; - + // Sun's apparent longitude var sunApparentLongitude = sunTrueLongitude - 0.00569 - 0.00478 * Math.Sin(ToRadians(125.04 - 1934.136 * julianCentury)); - + // Mean oblique ecliptic var meanOblique = 23.0 + (26.0 + ((21.448 - julianCentury * (46.815 + julianCentury * (0.00059 - julianCentury * 0.001813)))) / 60.0) / 60.0; - + // Oblique correction var obliqueCorrection = meanOblique + 0.00256 * Math.Cos(ToRadians(125.04 - 1934.136 * julianCentury)); - + // Sun's declination var declination = ToDegrees(Math.Asin(Math.Sin(ToRadians(obliqueCorrection)) * Math.Sin(ToRadians(sunApparentLongitude)))); - + // Equation of time var y = Math.Tan(ToRadians(obliqueCorrection / 2.0)) * Math.Tan(ToRadians(obliqueCorrection / 2.0)); var equationOfTime = 4.0 * ToDegrees(y * Math.Sin(2.0 * ToRadians(sunMeanLongitude)) @@ -62,64 +62,63 @@ public sealed class SunCalculatorService : ISunCalculatorService + 4.0 * eccentricity * y * Math.Sin(ToRadians(sunMeanAnomaly)) * Math.Cos(2.0 * ToRadians(sunMeanLongitude)) - 0.5 * y * y * Math.Sin(4.0 * ToRadians(sunMeanLongitude)) - 1.25 * eccentricity * eccentricity * Math.Sin(2.0 * ToRadians(sunMeanAnomaly))); - + // Hour angle sunrise (civil twilight: sun 6 degrees below horizon) var zenith = 90.833; // Official: 90 degrees 50 minutes var hourAngle = ToDegrees(Math.Acos( (Math.Cos(ToRadians(zenith)) / (Math.Cos(ToRadians(latitude)) * Math.Cos(ToRadians(declination)))) - Math.Tan(ToRadians(latitude)) * Math.Tan(ToRadians(declination)) )); - + // Calculate sunrise and sunset in minutes var solarNoon = (720.0 - 4.0 * longitude - equationOfTime) / 1440.0; var sunriseTime = solarNoon - hourAngle * 4.0 / 1440.0; var sunsetTime = solarNoon + hourAngle * 4.0 / 1440.0; - + // Convert to local time (assume UTC offset for Iran: +3:30 = 210 minutes) // You should ideally calculate timezone offset based on longitude var utcOffsetMinutes = Math.Round(longitude / 15.0) * 60.0; - + var sunriseMinutes = sunriseTime * 1440.0 + utcOffsetMinutes; var sunsetMinutes = sunsetTime * 1440.0 + utcOffsetMinutes; - + // Handle edge cases if (sunriseMinutes < 0) sunriseMinutes += 1440; if (sunriseMinutes >= 1440) sunriseMinutes -= 1440; if (sunsetMinutes < 0) sunsetMinutes += 1440; if (sunsetMinutes >= 1440) sunsetMinutes -= 1440; - + var sunrise = TimeSpan.FromMinutes(sunriseMinutes); var sunset = TimeSpan.FromMinutes(sunsetMinutes); - + return (sunrise, sunset); } - private double CalculateJulianDay(DateTime date) + private static double CalculateJulianDay(DateTime date) { var year = date.Year; var month = date.Month; var day = date.Day; - + if (month <= 2) { year -= 1; month += 12; } - + var a = year / 100; var b = 2 - a + (a / 4); - + return Math.Floor(365.25 * (year + 4716)) + Math.Floor(30.6001 * (month + 1)) + day + b - 1524.5; } - private double ToRadians(double degrees) + private static double ToRadians(double degrees) { return degrees * Math.PI / 180.0; } - private double ToDegrees(double radians) + private static double ToDegrees(double radians) { return radians * 180.0 / Math.PI; } } - diff --git a/src/GreenHome.Infrastructure/TelemetryService.cs b/src/GreenHome.Infrastructure/Services/Telemetry/TelemetryService.cs similarity index 64% rename from src/GreenHome.Infrastructure/TelemetryService.cs rename to src/GreenHome.Infrastructure/Services/Telemetry/TelemetryService.cs index 5ced6b4..1637d40 100644 --- a/src/GreenHome.Infrastructure/TelemetryService.cs +++ b/src/GreenHome.Infrastructure/Services/Telemetry/TelemetryService.cs @@ -1,171 +1,163 @@ -using System.Globalization; -using AutoMapper; -using GreenHome.Application; -using Microsoft.EntityFrameworkCore; - -namespace GreenHome.Infrastructure; - -public sealed class TelemetryService : ITelemetryService -{ - private readonly GreenHomeDbContext dbContext; - private readonly IMapper mapper; - private static readonly PersianCalendar PersianCalendar = new PersianCalendar(); - - public TelemetryService(GreenHomeDbContext dbContext, IMapper mapper) - { - this.dbContext = dbContext; - this.mapper = mapper; - } - - public async Task AddAsync(TelemetryDto dto, CancellationToken cancellationToken) - { - var entity = mapper.Map(dto); - if (!string.IsNullOrEmpty(dto.DeviceName)) - { - var device = await dbContext.Devices.FirstOrDefaultAsync(d => d.DeviceName == dto.DeviceName, cancellationToken); - if (device != null) - { - entity.DeviceId = device.Id; - dto.DeviceId = device.Id; // Update DTO for alert service - } - } - if (dto.Id!=0) - { - var device = await dbContext.Devices.FirstOrDefaultAsync(d => d.Id == dto.Id, cancellationToken); - if (device != null) - { - entity.DeviceId = device.Id; - } - } - // ذخیره زمان سرور در لحظه ثبت - entity.ServerTimestampUtc = DateTime.Now; - - var dt = dto.TimestampUtc!.Value.ToLocalTime(); - var py = PersianCalendar.GetYear(dt); - var pm = PersianCalendar.GetMonth(dt); - var pd = PersianCalendar.GetDayOfMonth(dt); - entity.PersianYear = py; - entity.PersianMonth = pm; - entity.PersianDate = $"{py:0000}/{pm:00}/{pd:00}"; - - if(entity.Lux < 0) - { - var lastItem = await dbContext.TelemetryRecords - .Where(x => x.DeviceId == entity.DeviceId) - .OrderByDescending(x => x.TimestampUtc) - .FirstOrDefaultAsync(cancellationToken); - - entity.Lux = lastItem?.Lux ?? 0; - } - - dbContext.TelemetryRecords.Add(entity); - await dbContext.SaveChangesAsync(cancellationToken); - return entity.Id; - } - - public async Task> ListAsync(TelemetryFilter filter, CancellationToken cancellationToken) - { - var query = dbContext.TelemetryRecords.AsNoTracking().AsQueryable(); - if (filter.DeviceId.HasValue) - { - query = query.Where(x => x.DeviceId == filter.DeviceId.Value); - } - if (filter.StartDateUtc.HasValue) - { - //var start = filter.StartDateUtc.Value.Date.AddDays(1); - - query = query.Where(x => x.ServerTimestampUtc >= filter.StartDateUtc.Value.ToLocalTime()); - } - - if (filter.EndDateUtc.HasValue) - { - query = query.Where(x => x.ServerTimestampUtc < filter.EndDateUtc.Value.ToLocalTime()); - } - - if(filter.Page <= 0) filter.Page = 1; - if(filter.PageSize <= 0) filter.PageSize = 1000000; // No limit - var total = await query.CountAsync(cancellationToken); - var skip = (filter.Page - 1) * filter.PageSize; - var items = await query - .OrderByDescending(x => x.ServerTimestampUtc) - .Skip(skip) - .Take(filter.PageSize) - .ToListAsync(cancellationToken); - - if(items.Any(x => x.Lux < 0)) - { - for (int i = 1; i < items.Count; i++) - { - if (items[i].Lux < 0) - items[i].Lux = items[i - 1].Lux; - } - } - - return new PagedResult - { - Items = mapper.Map>(items), - TotalCount = total, - Page = filter.Page, - PageSize = filter.PageSize - }; - } - - public async Task GetMinMaxAsync(int deviceId, DateTime? startUtc, DateTime? endUtc, CancellationToken cancellationToken) - { - var query = dbContext.TelemetryRecords.AsNoTracking().Where(x => x.DeviceId == deviceId); - if (startUtc.HasValue) query = query.Where(x => x.TimestampUtc >= startUtc.Value); - if (endUtc.HasValue) query = query.Where(x => x.TimestampUtc <= endUtc.Value); - - var result = await query - .Select(x => new - { - x.TemperatureC, - x.HumidityPercent, - x.SoilPercent, - x.GasPPM, - x.Lux - }) - .ToListAsync(cancellationToken); - - if (result.Count == 0) - { - return new TelemetryMinMax(); - } - - return new TelemetryMinMax - { - MinTemperatureC = result.Min(x => x.TemperatureC), - MaxTemperatureC = result.Max(x => x.TemperatureC), - MinHumidityPercent = result.Min(x => x.HumidityPercent), - MaxHumidityPercent = result.Max(x => x.HumidityPercent), - MinSoilPercent = result.Min(x => x.SoilPercent), - MaxSoilPercent = result.Max(x => x.SoilPercent), - MinGasPPM = result.Min(x => x.GasPPM), - MaxGasPPM = result.Max(x => x.GasPPM), - MinLux = result.Min(x => x.Lux), - MaxLux = result.Max(x => x.Lux) - }; - } - - public async Task> GetMonthDaysAsync(int deviceId, int persianYear, int persianMonth, CancellationToken cancellationToken) - { - var query = dbContext.TelemetryRecords.AsNoTracking() - .Where(x => x.DeviceId == deviceId && x.PersianYear == persianYear && x.PersianMonth == persianMonth) - .GroupBy(x => x.PersianDate) - .Select(g => new DayCount { PersianDate = g.Key, Count = g.Count() }) - .OrderBy(x => x.PersianDate); - - return await query.ToListAsync(cancellationToken); - } - - public async Task> GetActiveMonthsAsync(int deviceId, int persianYear, CancellationToken cancellationToken) - { - var months = await dbContext.TelemetryRecords.AsNoTracking() - .Where(x => x.DeviceId == deviceId && x.PersianYear == persianYear) - .Select(x => x.PersianMonth) - .Distinct() - .OrderBy(x => x) - .ToListAsync(cancellationToken); - return months; - } -} +using AutoMapper; +using GreenHome.Application; +using GreenHome.Infrastructure.Data; +using GreenHome.Infrastructure.Extensions; +using Microsoft.EntityFrameworkCore; + +namespace GreenHome.Infrastructure.Services.Telemetry; + +public sealed class TelemetryService : ITelemetryService +{ + private readonly GreenHomeDbContext _dbContext; + private readonly IMapper _mapper; + + public TelemetryService(GreenHomeDbContext dbContext, IMapper mapper) + { + _dbContext = dbContext; + _mapper = mapper; + } + + public async Task AddAsync(TelemetryDto dto, CancellationToken cancellationToken) + { + var entity = _mapper.Map(dto); + + if (!string.IsNullOrEmpty(dto.DeviceName)) + { + var device = await _dbContext.Devices.FirstOrDefaultAsync(d => d.DeviceName == dto.DeviceName, cancellationToken); + if (device != null) + { + entity.DeviceId = device.Id; + dto.DeviceId = device.Id; + } + } + + if (dto.Id != 0) + { + var device = await _dbContext.Devices.FirstOrDefaultAsync(d => d.Id == dto.Id, cancellationToken); + if (device != null) + entity.DeviceId = device.Id; + } + + entity.ServerTimestampUtc = DateTime.Now; + + if (dto.TimestampUtc.HasValue) + { + var (year, month, day, persianDate) = dto.TimestampUtc.Value.ToPersianDate(); + entity.PersianYear = year; + entity.PersianMonth = month; + entity.PersianDate = persianDate; + } + + if (entity.Lux < 0) + { + var lastItem = await _dbContext.TelemetryRecords + .Where(x => x.DeviceId == entity.DeviceId) + .OrderByDescending(x => x.TimestampUtc) + .FirstOrDefaultAsync(cancellationToken); + + entity.Lux = lastItem?.Lux ?? 0; + } + + _dbContext.TelemetryRecords.Add(entity); + await _dbContext.SaveChangesAsync(cancellationToken); + return entity.Id; + } + + public async Task> ListAsync(TelemetryFilter filter, CancellationToken cancellationToken) + { + var query = _dbContext.TelemetryRecords.AsNoTracking().AsQueryable(); + + if (filter.DeviceId.HasValue) + query = query.Where(x => x.DeviceId == filter.DeviceId.Value); + + if (filter.StartDateUtc.HasValue) + query = query.Where(x => x.ServerTimestampUtc >= filter.StartDateUtc.Value.ToLocalTime()); + + if (filter.EndDateUtc.HasValue) + query = query.Where(x => x.ServerTimestampUtc < filter.EndDateUtc.Value.ToLocalTime()); + + if (filter.Page <= 0) filter.Page = 1; + if (filter.PageSize <= 0) filter.PageSize = 1000000; + + var total = await query.CountAsync(cancellationToken); + var skip = (filter.Page - 1) * filter.PageSize; + + var items = await query + .OrderByDescending(x => x.ServerTimestampUtc) + .Skip(skip) + .Take(filter.PageSize) + .ToListAsync(cancellationToken); + + // Fix negative Lux values + for (int i = 1; i < items.Count; i++) + { + if (items[i].Lux < 0) + items[i].Lux = items[i - 1].Lux; + } + + return new PagedResult + { + Items = _mapper.Map>(items), + TotalCount = total, + Page = filter.Page, + PageSize = filter.PageSize + }; + } + + public async Task GetMinMaxAsync(int deviceId, DateTime? startUtc, DateTime? endUtc, CancellationToken cancellationToken) + { + var query = _dbContext.TelemetryRecords.AsNoTracking().Where(x => x.DeviceId == deviceId); + if (startUtc.HasValue) query = query.Where(x => x.TimestampUtc >= startUtc.Value); + if (endUtc.HasValue) query = query.Where(x => x.TimestampUtc <= endUtc.Value); + + var result = await query + .Select(x => new + { + x.TemperatureC, + x.HumidityPercent, + x.SoilPercent, + x.GasPPM, + x.Lux + }) + .ToListAsync(cancellationToken); + + if (result.Count == 0) + return new TelemetryMinMax(); + + return new TelemetryMinMax + { + MinTemperatureC = result.Min(x => x.TemperatureC), + MaxTemperatureC = result.Max(x => x.TemperatureC), + MinHumidityPercent = result.Min(x => x.HumidityPercent), + MaxHumidityPercent = result.Max(x => x.HumidityPercent), + MinSoilPercent = result.Min(x => x.SoilPercent), + MaxSoilPercent = result.Max(x => x.SoilPercent), + MinGasPPM = result.Min(x => x.GasPPM), + MaxGasPPM = result.Max(x => x.GasPPM), + MinLux = result.Min(x => x.Lux), + MaxLux = result.Max(x => x.Lux) + }; + } + + public async Task> GetMonthDaysAsync(int deviceId, int persianYear, int persianMonth, CancellationToken cancellationToken) + { + var query = _dbContext.TelemetryRecords.AsNoTracking() + .Where(x => x.DeviceId == deviceId && x.PersianYear == persianYear && x.PersianMonth == persianMonth) + .GroupBy(x => x.PersianDate) + .Select(g => new DayCount { PersianDate = g.Key, Count = g.Count() }) + .OrderBy(x => x.PersianDate); + + return await query.ToListAsync(cancellationToken); + } + + public async Task> GetActiveMonthsAsync(int deviceId, int persianYear, CancellationToken cancellationToken) + { + var months = await _dbContext.TelemetryRecords.AsNoTracking() + .Where(x => x.DeviceId == deviceId && x.PersianYear == persianYear) + .Select(x => x.PersianMonth) + .Distinct() + .OrderBy(x => x) + .ToListAsync(cancellationToken); + return months; + } +} diff --git a/src/GreenHome.Infrastructure/VoiceCallService.cs b/src/GreenHome.Infrastructure/VoiceCallService.cs deleted file mode 100644 index 1b803df..0000000 --- a/src/GreenHome.Infrastructure/VoiceCallService.cs +++ /dev/null @@ -1,4 +0,0 @@ -public class VoiceCallService -{ - -} \ No newline at end of file