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