add voice service call service and more

This commit is contained in:
2025-11-25 16:49:18 +03:30
parent 60d20a2734
commit 9ba81d944f
49 changed files with 4428 additions and 19 deletions

View File

@@ -0,0 +1,319 @@
using GreenHome.Application;
using GreenHome.Sms.Ippanel;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using static GreenHome.Sms.Ippanel.IppanelSmsService;
namespace GreenHome.Infrastructure;
public sealed class AlertService : IAlertService
{
private readonly GreenHomeDbContext dbContext;
private readonly IDeviceSettingsService deviceSettingsService;
private readonly ISmsService smsService;
private readonly ILogger<AlertService> logger;
private const int AlertCooldownMinutes = 10;
private sealed record AlertInfo(
string Type,
string Message,
string ParameterName,
decimal Value,
string Status
);
public AlertService(
GreenHomeDbContext dbContext,
IDeviceSettingsService deviceSettingsService,
ISmsService smsService,
ILogger<AlertService> logger)
{
this.dbContext = dbContext;
this.deviceSettingsService = deviceSettingsService;
this.smsService = smsService;
this.logger = logger;
}
public async Task CheckAndSendAlertsAsync(int deviceId, TelemetryDto telemetry, CancellationToken cancellationToken)
{
var settings = await deviceSettingsService.GetByDeviceIdAsync(deviceId, cancellationToken);
if (settings == null)
{
return;
}
var device = await dbContext.Devices
.Include(d => d.User)
.FirstOrDefaultAsync(d => d.Id == deviceId, cancellationToken);
if (device == null || device.User == null)
{
return;
}
var alerts = CollectAlerts(telemetry, settings, device.DeviceName);
foreach (var alert in alerts)
{
await SendAlertIfNeededAsync(deviceId, device.User.Id, device.DeviceName, alert, cancellationToken);
}
}
private List<AlertInfo> CollectAlerts(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName)
{
var alerts = new List<AlertInfo>();
CheckTemperatureAlert(telemetry, settings, deviceName, alerts);
CheckHumidityAlert(telemetry, settings, deviceName, alerts);
CheckSoilAlert(telemetry, deviceName, alerts);
CheckGasAlert(telemetry, settings, deviceName, alerts);
CheckLuxAlert(telemetry, settings, deviceName, alerts);
return alerts;
}
private void CheckTemperatureAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
{
if (telemetry.TemperatureC > settings.MaxTemperature)
{
alerts.Add(new AlertInfo(
Type: "Temperature",
Message: $"هشدار: دمای گلخانه {deviceName} به {telemetry.TemperatureC} درجه رسیده که از حداکثر مجاز ({settings.MaxTemperature}) بیشتر است.",
ParameterName: "دما",
Value: telemetry.TemperatureC,
Status: "بالاتر"
));
}
else if (telemetry.TemperatureC < settings.MinTemperature)
{
alerts.Add(new AlertInfo(
Type: "Temperature",
Message: $"هشدار: دمای گلخانه {deviceName} به {telemetry.TemperatureC} درجه رسیده که از حداقل مجاز ({settings.MinTemperature}) کمتر است.",
ParameterName: "دما",
Value: telemetry.TemperatureC,
Status: "پایین‌تر"
));
}
}
private void CheckHumidityAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
{
if (telemetry.HumidityPercent > settings.MaxHumidityPercent)
{
alerts.Add(new AlertInfo(
Type: "Humidity",
Message: $"هشدار: رطوبت گلخانه {deviceName} به {telemetry.HumidityPercent}% رسیده که از حداکثر مجاز ({settings.MaxHumidityPercent}%) بیشتر است.",
ParameterName: "رطوبت",
Value: telemetry.HumidityPercent,
Status: "بالاتر"
));
}
else if (telemetry.HumidityPercent < settings.MinHumidityPercent)
{
alerts.Add(new AlertInfo(
Type: "Humidity",
Message: $"هشدار: رطوبت گلخانه {deviceName} به {telemetry.HumidityPercent}% رسیده که از حداقل مجاز ({settings.MinHumidityPercent}%) کمتر است.",
ParameterName: "رطوبت",
Value: telemetry.HumidityPercent,
Status: "پایین‌تر"
));
}
}
private void CheckSoilAlert(TelemetryDto telemetry, string deviceName, List<AlertInfo> alerts)
{
if (telemetry.SoilPercent > 100)
{
alerts.Add(new AlertInfo(
Type: "Soil",
Message: $"هشدار: رطوبت خاک گلخانه {deviceName} مقدار نامعتبر ({telemetry.SoilPercent}%) دارد.",
ParameterName: "رطوبت خاک",
Value: telemetry.SoilPercent,
Status: "بالاتر"
));
}
else if (telemetry.SoilPercent < 0)
{
alerts.Add(new AlertInfo(
Type: "Soil",
Message: $"هشدار: رطوبت خاک گلخانه {deviceName} مقدار نامعتبر ({telemetry.SoilPercent}%) دارد.",
ParameterName: "رطوبت خاک",
Value: telemetry.SoilPercent,
Status: "پایین‌تر"
));
}
}
private void CheckGasAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
{
if (telemetry.GasPPM > settings.MaxGasPPM)
{
alerts.Add(new AlertInfo(
Type: "Gas",
Message: $"هشدار: گاز گلخانه {deviceName} به {telemetry.GasPPM} PPM رسیده که از حداکثر مجاز ({settings.MaxGasPPM}) بیشتر است.",
ParameterName: "گاز Co",
Value: telemetry.GasPPM,
Status: "بالاتر"
));
}
else if (telemetry.GasPPM < settings.MinGasPPM)
{
alerts.Add(new AlertInfo(
Type: "Gas",
Message: $"هشدار: گاز گلخانه {deviceName} به {telemetry.GasPPM} PPM رسیده که از حداقل مجاز ({settings.MinGasPPM}) کمتر است.",
ParameterName: "گاز Co",
Value: telemetry.GasPPM,
Status: "پایین‌تر"
));
}
}
private void CheckLuxAlert(TelemetryDto telemetry, DeviceSettingsDto settings, string deviceName, List<AlertInfo> alerts)
{
if (telemetry.Lux > settings.MaxLux)
{
alerts.Add(new AlertInfo(
Type: "Lux",
Message: $"هشدار: نور گلخانه {deviceName} به {telemetry.Lux} لوکس رسیده که از حداکثر مجاز ({settings.MaxLux}) بیشتر است.",
ParameterName: "نور",
Value: telemetry.Lux,
Status: "بالاتر"
));
}
else if (telemetry.Lux < settings.MinLux)
{
alerts.Add(new AlertInfo(
Type: "Lux",
Message: $"هشدار: نور گلخانه {deviceName} به {telemetry.Lux} لوکس رسیده که از حداقل مجاز ({settings.MinLux}) کمتر است.",
ParameterName: "نور",
Value: telemetry.Lux,
Status: "پایین‌تر"
));
}
}
private async Task SendAlertIfNeededAsync(
int deviceId,
int userId,
string deviceName,
AlertInfo alert,
CancellationToken cancellationToken)
{
// Check if alert was sent in the last 10 minutes
var cooldownTime = DateTime.UtcNow.AddMinutes(-AlertCooldownMinutes);
var recentAlert = await dbContext.AlertNotifications
.Where(a => a.DeviceId == deviceId &&
a.UserId == userId &&
a.AlertType == alert.Type &&
a.SentAt >= cooldownTime)
.FirstOrDefaultAsync(cancellationToken);
if (recentAlert != null)
{
logger.LogInformation("Alert skipped due to cooldown: DeviceId={DeviceId}, AlertType={AlertType}", deviceId, alert.Type);
return;
}
// Get user to send SMS
var user = await dbContext.Users
.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);
if (user == null || string.IsNullOrWhiteSpace(user.Mobile))
{
logger.LogWarning("User not found or mobile is empty: UserId={UserId}", userId);
return;
}
// Send SMS and collect response/errors
string? messageOutboxIdsJson = null;
string? errorMessage = null;
bool isSent = false;
try
{
var smsResponse = await smsService.SendPatternSmsAsync(new PatternSmsRequest
{
Recipients = [user.Mobile],
PatternCode = "64di3w9kb0fxvif",
Variables = new Dictionary<string, string> {
{ "name", deviceName },
{ "parameter", alert.ParameterName },
{ "value", alert.Value.ToString("F1") },
{ "status", alert.Status },
}
}, cancellationToken);
if (smsResponse != null)
{
// Check if SMS was sent successfully
if (smsResponse.Meta.Status && smsResponse.Data != null && smsResponse.Data.MessageOutboxIds != null && smsResponse.Data.MessageOutboxIds.Count > 0)
{
// Success - save message outbox IDs
messageOutboxIdsJson = JsonSerializer.Serialize(smsResponse.Data.MessageOutboxIds);
isSent = true;
logger.LogInformation("Alert SMS sent: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}, OutboxIds={OutboxIds}",
deviceId, userId, alert.Type, messageOutboxIdsJson);
}
else
{
// Failed - save error from meta
var errors = new List<string>();
if (!string.IsNullOrWhiteSpace(smsResponse.Meta.Message))
{
errors.Add(smsResponse.Meta.Message);
}
if (smsResponse.Meta.Errors != null && smsResponse.Meta.Errors.Count > 0)
{
foreach (var error in smsResponse.Meta.Errors)
{
errors.Add($"{error.Key}: {string.Join(", ", error.Value)}");
}
}
if (errors.Count == 0)
{
errors.Add("SMS sending failed with unknown error");
}
errorMessage = string.Join(" | ", errors);
isSent = false;
logger.LogWarning("Alert SMS failed: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}, Error={Error}",
deviceId, userId, alert.Type, errorMessage);
}
}
else
{
errorMessage = "SMS service returned null response";
isSent = false;
logger.LogWarning("Alert SMS returned null: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}",
deviceId, userId, alert.Type);
}
}
catch (Exception ex)
{
errorMessage = $"Exception: {ex.Message}";
if (ex.InnerException != null)
{
errorMessage += $" | InnerException: {ex.InnerException.Message}";
}
isSent = false;
logger.LogError(ex, "Failed to send alert SMS: DeviceId={DeviceId}, UserId={UserId}, AlertType={AlertType}", deviceId, userId, alert.Type);
}
// Save notification to database
var notification = new Domain.AlertNotification
{
DeviceId = deviceId,
UserId = userId,
AlertType = alert.Type,
Message = alert.Message,
MessageOutboxIds = messageOutboxIdsJson,
ErrorMessage = errorMessage,
SentAt = DateTime.UtcNow,
IsSent = isSent
};
dbContext.AlertNotifications.Add(notification);
await dbContext.SaveChangesAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,242 @@
using GreenHome.Application;
using GreenHome.Sms.Ippanel;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Security.Cryptography;
using System.Text;
namespace GreenHome.Infrastructure;
public sealed class AuthService : IAuthService
{
private readonly GreenHomeDbContext dbContext;
private readonly ISmsService smsService;
private readonly ILogger<AuthService> logger;
private const int CodeExpirationMinutes = 5;
private const int ResendCooldownSeconds = 120;
public AuthService(
GreenHomeDbContext dbContext,
ISmsService smsService,
ILogger<AuthService> logger)
{
this.dbContext = dbContext;
this.smsService = smsService;
this.logger = logger;
}
public async Task<SendCodeResponse> SendVerificationCodeAsync(
SendCodeRequest request,
CancellationToken cancellationToken)
{
// Normalize mobile number (remove spaces, dashes, etc.)
var mobile = NormalizeMobile(request.Mobile);
if (string.IsNullOrWhiteSpace(mobile) || mobile.Length != 11 || !mobile.StartsWith("09"))
{
return new SendCodeResponse
{
Success = false,
Message = "شماره موبایل معتبر نیست"
};
}
// Check if we can resend (cooldown period)
var canResend = await CanResendCodeAsync(mobile, cancellationToken);
if (!canResend)
{
var lastCode = await dbContext.VerificationCodes
.Where(v => v.Mobile == mobile && !v.IsUsed)
.OrderByDescending(v => v.CreatedAt)
.FirstOrDefaultAsync(cancellationToken);
if (lastCode != null)
{
var secondsRemaining = (int)(ResendCooldownSeconds - (DateTime.UtcNow - lastCode.CreatedAt).TotalSeconds);
return new SendCodeResponse
{
Success = false,
Message = $"لطفاً {secondsRemaining} ثانیه دیگر دوباره تلاش کنید",
ResendAfterSeconds = secondsRemaining > 0 ? secondsRemaining : 0
};
}
}
// Generate 4-digit code
var code = GenerateCode();
// Invalidate previous unused codes for this mobile
var previousCodes = await dbContext.VerificationCodes
.Where(v => v.Mobile == mobile && !v.IsUsed)
.ToListAsync(cancellationToken);
foreach (var prevCode in previousCodes)
{
prevCode.IsUsed = true;
prevCode.UsedAt = DateTime.UtcNow;
}
// Create new verification code
var verificationCode = new Domain.VerificationCode
{
Mobile = mobile,
Code = code,
CreatedAt = DateTime.UtcNow,
ExpiresAt = DateTime.UtcNow.AddMinutes(CodeExpirationMinutes),
IsUsed = false
};
dbContext.VerificationCodes.Add(verificationCode);
await dbContext.SaveChangesAsync(cancellationToken);
// Send SMS
try
{
await smsService.SendPatternSmsAsync(new PatternSmsRequest
{
Recipients = [mobile],
PatternCode = "ruvpjx7lajne1dx",
Variables = new Dictionary<string, string> { { "code", code } }
}, cancellationToken);
logger.LogInformation("Verification code sent to {Mobile}", mobile);
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to send SMS to {Mobile}", mobile);
// Continue even if SMS fails (for development/testing)
}
return new SendCodeResponse
{
Success = true,
Message = "کد فعال‌سازی ارسال شد",
ResendAfterSeconds = ResendCooldownSeconds
};
}
public async Task<VerifyCodeResponse> VerifyCodeAsync(
VerifyCodeRequest request,
CancellationToken cancellationToken)
{
var mobile = NormalizeMobile(request.Mobile);
var code = request.Code.Trim();
if (string.IsNullOrWhiteSpace(mobile) || string.IsNullOrWhiteSpace(code) || code.Length != 4)
{
return new VerifyCodeResponse
{
Success = false,
Message = "اطلاعات وارد شده معتبر نیست"
};
}
// Find valid verification code
var verificationCode = await dbContext.VerificationCodes
.Where(v => v.Mobile == mobile && v.Code == code && !v.IsUsed && v.ExpiresAt > DateTime.UtcNow)
.OrderByDescending(v => v.CreatedAt)
.FirstOrDefaultAsync(cancellationToken);
if (verificationCode == null)
{
return new VerifyCodeResponse
{
Success = false,
Message = "کد وارد شده معتبر نیست یا منقضی شده است"
};
}
// Mark code as used
verificationCode.IsUsed = true;
verificationCode.UsedAt = DateTime.UtcNow;
// Find or create user
var user = await dbContext.Users
.FirstOrDefaultAsync(u => u.Mobile == mobile, cancellationToken);
if (user == null)
{
user = new Domain.User
{
Mobile = mobile,
CreatedAt = DateTime.UtcNow,
LastLoginAt = DateTime.UtcNow
};
dbContext.Users.Add(user);
}
else
{
user.LastLoginAt = DateTime.UtcNow;
}
await dbContext.SaveChangesAsync(cancellationToken);
// Generate simple token (in production, use JWT)
var token = GenerateToken(user.Id, mobile);
return new VerifyCodeResponse
{
Success = true,
Message = "ورود موفقیت‌آمیز بود",
Token = token,
User = new UserDto
{
Id = user.Id,
Mobile = user.Mobile,
Name = user.Name,
Family = user.Family,
Role = user.Role
}
};
}
public async Task<bool> CanResendCodeAsync(string mobile, CancellationToken cancellationToken)
{
var normalizedMobile = NormalizeMobile(mobile);
var lastCode = await dbContext.VerificationCodes
.Where(v => v.Mobile == normalizedMobile && !v.IsUsed)
.OrderByDescending(v => v.CreatedAt)
.FirstOrDefaultAsync(cancellationToken);
if (lastCode == null)
return true;
var elapsed = (DateTime.UtcNow - lastCode.CreatedAt).TotalSeconds;
return elapsed >= ResendCooldownSeconds;
}
private static string GenerateCode()
{
var random = new Random();
return random.Next(1000, 9999).ToString();
}
private static string GenerateToken(int userId, string mobile)
{
// Simple token generation (in production, use JWT)
var data = $"{userId}:{mobile}:{DateTime.UtcNow:yyyyMMddHHmmss}";
var bytes = Encoding.UTF8.GetBytes(data);
var hash = SHA256.HashData(bytes);
return Convert.ToBase64String(hash);
}
private static string NormalizeMobile(string mobile)
{
if (string.IsNullOrWhiteSpace(mobile))
return string.Empty;
// Remove all non-digit characters
var normalized = new string(mobile.Where(char.IsDigit).ToArray());
// Convert to standard format (09xxxxxxxxx)
if (normalized.StartsWith("9") && normalized.Length == 10)
normalized = "0" + normalized;
else if (normalized.StartsWith("0098") && normalized.Length == 13)
normalized = "0" + normalized.Substring(3);
else if (normalized.StartsWith("98") && normalized.Length == 12)
normalized = "0" + normalized.Substring(2);
return normalized;
}
}

View File

@@ -25,13 +25,102 @@ public sealed class DeviceService : IDeviceService
public async Task<IReadOnlyList<DeviceDto>> ListAsync(CancellationToken cancellationToken)
{
var items = await dbContext.Devices.AsNoTracking().OrderBy(d => d.DeviceName).ToListAsync(cancellationToken);
var items = await dbContext.Devices
.AsNoTracking()
.Include(d => d.User)
.OrderBy(d => d.DeviceName)
.ToListAsync(cancellationToken);
return mapper.Map<IReadOnlyList<DeviceDto>>(items);
}
public async Task<DeviceDto> GetDeviceId(string deviceName,CancellationToken cancellationToken)
{
var item = await dbContext.Devices.AsNoTracking().Where(d=>d.DeviceName==deviceName).FirstOrDefaultAsync(cancellationToken);
var item = await dbContext.Devices
.AsNoTracking()
.Include(d => d.User)
.Where(d=>d.DeviceName==deviceName)
.FirstOrDefaultAsync(cancellationToken);
return mapper.Map<DeviceDto>(item);
}
public async Task<IReadOnlyList<DeviceDto>> GetUserDevicesAsync(int userId, CancellationToken cancellationToken)
{
var items = await dbContext.Devices
.AsNoTracking()
.Include(d => d.User)
.Where(d => d.UserId == userId)
.OrderBy(d => d.DeviceName)
.ToListAsync(cancellationToken);
return mapper.Map<IReadOnlyList<DeviceDto>>(items);
}
public async Task<PagedResult<DeviceDto>> GetDevicesAsync(DeviceFilter filter, CancellationToken cancellationToken)
{
if (!filter.UserId.HasValue)
{
throw new ArgumentException("UserId is required", nameof(filter));
}
// Get user and role
var user = await dbContext.Users
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Id == filter.UserId.Value, cancellationToken);
if (user == null)
{
return new PagedResult<DeviceDto>
{
Items = Array.Empty<DeviceDto>(),
TotalCount = 0,
Page = filter.Page,
PageSize = filter.PageSize
};
}
// Build query based on role
IQueryable<Domain.Device> query = dbContext.Devices
.AsNoTracking()
.Include(d => d.User);
if (user.Role == Domain.UserRole.Normal)
{
// Normal user: only own devices
query = query.Where(d => d.UserId == user.Id);
}
else if (user.Role == Domain.UserRole.Supervisor)
{
// Supervisor: devices assigned to them
query = query.Where(d => d.DeviceUsers.Any(du => du.UserId == user.Id));
}
// Admin: all devices (no filter)
// Apply search filter
if (!string.IsNullOrWhiteSpace(filter.Search))
{
var searchTerm = filter.Search.Trim().ToLower();
query = query.Where(d =>
d.DeviceName.ToLower().Contains(searchTerm) ||
d.User.Name.ToLower().Contains(searchTerm) ||
d.User.Family.ToLower().Contains(searchTerm) ||
d.Location.ToLower().Contains(searchTerm));
}
// Get total count
var totalCount = await query.CountAsync(cancellationToken);
// Apply pagination
var items = await query
.OrderBy(d => d.DeviceName)
.Skip((filter.Page - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync(cancellationToken);
return new PagedResult<DeviceDto>
{
Items = mapper.Map<IReadOnlyList<DeviceDto>>(items),
TotalCount = totalCount,
Page = filter.Page,
PageSize = filter.PageSize
};
}
}

View File

@@ -22,6 +22,7 @@
<ItemGroup>
<ProjectReference Include="..\GreenHome.Application\GreenHome.Application.csproj" />
<ProjectReference Include="..\GreenHome.Domain\GreenHome.Domain.csproj" />
<ProjectReference Include="..\GreenHome.Sms.Ippanel\GreenHome.Sms.Ippanel.csproj" />
</ItemGroup>
</Project>

View File

@@ -9,6 +9,10 @@ public sealed class GreenHomeDbContext : DbContext
public DbSet<Domain.Device> Devices => Set<Domain.Device>();
public DbSet<Domain.TelemetryRecord> TelemetryRecords => Set<Domain.TelemetryRecord>();
public DbSet<Domain.DeviceSettings> DeviceSettings => Set<Domain.DeviceSettings>();
public DbSet<Domain.User> Users => Set<Domain.User>();
public DbSet<Domain.VerificationCode> VerificationCodes => Set<Domain.VerificationCode>();
public DbSet<Domain.DeviceUser> DeviceUsers => Set<Domain.DeviceUser>();
public DbSet<Domain.AlertNotification> AlertNotifications => Set<Domain.AlertNotification>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -19,10 +23,13 @@ public sealed class GreenHomeDbContext : DbContext
b.ToTable("Devices");
b.HasKey(x => x.Id);
b.Property(x => x.DeviceName).IsRequired().HasMaxLength(10);
b.Property(x => x.Owner).IsRequired();
b.Property(x => x.Mobile).IsRequired(false);
b.Property(x => x.UserId).IsRequired();
b.Property(x => x.Location).HasMaxLength(250);
b.Property(x => x.NeshanLocation).HasMaxLength(80);
b.HasOne(x => x.User)
.WithMany()
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity<Domain.TelemetryRecord>(b =>
@@ -56,5 +63,58 @@ public sealed class GreenHomeDbContext : DbContext
.OnDelete(DeleteBehavior.Cascade);
b.HasIndex(x => x.DeviceId).IsUnique();
});
modelBuilder.Entity<Domain.User>(b =>
{
b.ToTable("Users");
b.HasKey(x => x.Id);
b.Property(x => x.Mobile).IsRequired().HasMaxLength(11);
b.Property(x => x.Name).IsRequired().HasMaxLength(100);
b.Property(x => x.Family).IsRequired().HasMaxLength(100);
b.Property(x => x.Role).IsRequired().HasConversion<int>();
b.HasIndex(x => x.Mobile).IsUnique();
});
modelBuilder.Entity<Domain.DeviceUser>(b =>
{
b.ToTable("DeviceUsers");
b.HasKey(x => new { x.DeviceId, x.UserId });
b.HasOne(x => x.Device)
.WithMany(d => d.DeviceUsers)
.HasForeignKey(x => x.DeviceId)
.OnDelete(DeleteBehavior.Cascade);
b.HasOne(x => x.User)
.WithMany(u => u.DeviceUsers)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<Domain.VerificationCode>(b =>
{
b.ToTable("VerificationCodes");
b.HasKey(x => x.Id);
b.Property(x => x.Mobile).IsRequired().HasMaxLength(11);
b.Property(x => x.Code).IsRequired().HasMaxLength(4);
b.HasIndex(x => new { x.Mobile, x.Code, x.IsUsed });
});
modelBuilder.Entity<Domain.AlertNotification>(b =>
{
b.ToTable("AlertNotifications");
b.HasKey(x => x.Id);
b.Property(x => x.AlertType).IsRequired().HasMaxLength(50);
b.Property(x => x.Message).IsRequired().HasMaxLength(500);
b.Property(x => x.MessageOutboxIds).HasMaxLength(500);
b.Property(x => x.ErrorMessage).HasMaxLength(1000);
b.HasOne(x => x.Device)
.WithMany()
.HasForeignKey(x => x.DeviceId)
.OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.User)
.WithMany()
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.DeviceId, x.UserId, x.AlertType, x.SentAt });
});
}
}

View File

@@ -0,0 +1,263 @@
// <auto-generated />
using System;
using GreenHome.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
[DbContext(typeof(GreenHomeDbContext))]
[Migration("20251118204845_UpdateDeviceUserRelationship")]
partial class UpdateDeviceUserRelationship
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("DeviceName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<string>("Location")
.IsRequired()
.HasMaxLength(250)
.HasColumnType("nvarchar(250)");
b.Property<string>("NeshanLocation")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("nvarchar(80)");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Devices", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<decimal>("DangerMaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("DangerMinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("MaxGasPPM")
.HasColumnType("int");
b.Property<decimal>("MaxHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("MinGasPPM")
.HasColumnType("int");
b.Property<decimal>("MinHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId")
.IsUnique();
b.ToTable("DeviceSettings", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("GasPPM")
.HasColumnType("int");
b.Property<decimal>("HumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("Lux")
.HasColumnType("decimal(18,2)");
b.Property<string>("PersianDate")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<int>("PersianMonth")
.HasColumnType("int");
b.Property<int>("PersianYear")
.HasColumnType("int");
b.Property<decimal>("SoilPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TemperatureC")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("TimestampUtc")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId", "TimestampUtc");
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
b.ToTable("Telemetry", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Family")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.HasIndex("Mobile")
.IsUnique();
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(4)
.HasColumnType("nvarchar(4)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime2");
b.Property<bool>("IsUsed")
.HasColumnType("bit");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<DateTime?>("UsedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("Mobile", "Code", "IsUsed");
b.ToTable("VerificationCodes", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany()
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,124 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class UpdateDeviceUserRelationship : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Mobile",
table: "Devices");
migrationBuilder.DropColumn(
name: "Owner",
table: "Devices");
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "Devices",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Mobile = table.Column<string>(type: "nvarchar(11)", maxLength: 11, nullable: false),
Name = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Family = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
LastLoginAt = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "VerificationCodes",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Mobile = table.Column<string>(type: "nvarchar(11)", maxLength: 11, nullable: false),
Code = table.Column<string>(type: "nvarchar(4)", maxLength: 4, nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "datetime2", nullable: false),
IsUsed = table.Column<bool>(type: "bit", nullable: false),
UsedAt = table.Column<DateTime>(type: "datetime2", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_VerificationCodes", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Devices_UserId",
table: "Devices",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Users_Mobile",
table: "Users",
column: "Mobile",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_VerificationCodes_Mobile_Code_IsUsed",
table: "VerificationCodes",
columns: new[] { "Mobile", "Code", "IsUsed" });
migrationBuilder.AddForeignKey(
name: "FK_Devices_Users_UserId",
table: "Devices",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Devices_Users_UserId",
table: "Devices");
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropTable(
name: "VerificationCodes");
migrationBuilder.DropIndex(
name: "IX_Devices_UserId",
table: "Devices");
migrationBuilder.DropColumn(
name: "UserId",
table: "Devices");
migrationBuilder.AddColumn<string>(
name: "Mobile",
table: "Devices",
type: "nvarchar(max)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "Owner",
table: "Devices",
type: "nvarchar(max)",
nullable: false,
defaultValue: "");
}
}
}

View File

@@ -0,0 +1,310 @@
// <auto-generated />
using System;
using GreenHome.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
[DbContext(typeof(GreenHomeDbContext))]
[Migration("20251119133827_AddUserRolesAndDeviceUsers")]
partial class AddUserRolesAndDeviceUsers
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("DeviceName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<string>("Location")
.IsRequired()
.HasMaxLength(250)
.HasColumnType("nvarchar(250)");
b.Property<string>("NeshanLocation")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("nvarchar(80)");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Devices", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<decimal>("DangerMaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("DangerMinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("MaxGasPPM")
.HasColumnType("int");
b.Property<decimal>("MaxHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("MinGasPPM")
.HasColumnType("int");
b.Property<decimal>("MinHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId")
.IsUnique();
b.ToTable("DeviceSettings", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("DeviceId", "UserId");
b.HasIndex("UserId");
b.ToTable("DeviceUsers", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("GasPPM")
.HasColumnType("int");
b.Property<decimal>("HumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("Lux")
.HasColumnType("decimal(18,2)");
b.Property<string>("PersianDate")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<int>("PersianMonth")
.HasColumnType("int");
b.Property<int>("PersianYear")
.HasColumnType("int");
b.Property<decimal>("SoilPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TemperatureC")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("TimestampUtc")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId", "TimestampUtc");
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
b.ToTable("Telemetry", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Family")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("Role")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Mobile")
.IsUnique();
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(4)
.HasColumnType("nvarchar(4)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime2");
b.Property<bool>("IsUsed")
.HasColumnType("bit");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<DateTime?>("UsedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("Mobile", "Code", "IsUsed");
b.ToTable("VerificationCodes", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany()
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany("DeviceUsers")
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("GreenHome.Domain.User", "User")
.WithMany("DeviceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Navigation("DeviceUsers");
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Navigation("DeviceUsers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,61 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddUserRolesAndDeviceUsers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Role",
table: "Users",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "DeviceUsers",
columns: table => new
{
DeviceId = table.Column<int>(type: "int", nullable: false),
UserId = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DeviceUsers", x => new { x.DeviceId, x.UserId });
table.ForeignKey(
name: "FK_DeviceUsers_Devices_DeviceId",
column: x => x.DeviceId,
principalTable: "Devices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_DeviceUsers_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_DeviceUsers_UserId",
table: "DeviceUsers",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DeviceUsers");
migrationBuilder.DropColumn(
name: "Role",
table: "Users");
}
}
}

View File

@@ -0,0 +1,368 @@
// <auto-generated />
using System;
using GreenHome.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
[DbContext(typeof(GreenHomeDbContext))]
[Migration("20251119135914_AddAlertNotifications")]
partial class AddAlertNotifications
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AlertType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<bool>("IsSent")
.HasColumnType("bit");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTime>("SentAt")
.HasColumnType("datetime2");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("DeviceId", "UserId", "AlertType", "SentAt");
b.ToTable("AlertNotifications", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("DeviceName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<string>("Location")
.IsRequired()
.HasMaxLength(250)
.HasColumnType("nvarchar(250)");
b.Property<string>("NeshanLocation")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("nvarchar(80)");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Devices", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<decimal>("DangerMaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("DangerMinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("MaxGasPPM")
.HasColumnType("int");
b.Property<decimal>("MaxHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("MinGasPPM")
.HasColumnType("int");
b.Property<decimal>("MinHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId")
.IsUnique();
b.ToTable("DeviceSettings", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("DeviceId", "UserId");
b.HasIndex("UserId");
b.ToTable("DeviceUsers", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("GasPPM")
.HasColumnType("int");
b.Property<decimal>("HumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("Lux")
.HasColumnType("decimal(18,2)");
b.Property<string>("PersianDate")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<int>("PersianMonth")
.HasColumnType("int");
b.Property<int>("PersianYear")
.HasColumnType("int");
b.Property<decimal>("SoilPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TemperatureC")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("TimestampUtc")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId", "TimestampUtc");
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
b.ToTable("Telemetry", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Family")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("Role")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Mobile")
.IsUnique();
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(4)
.HasColumnType("nvarchar(4)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime2");
b.Property<bool>("IsUsed")
.HasColumnType("bit");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<DateTime?>("UsedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("Mobile", "Code", "IsUsed");
b.ToTable("VerificationCodes", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany()
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Device");
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany()
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany("DeviceUsers")
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("GreenHome.Domain.User", "User")
.WithMany("DeviceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Navigation("DeviceUsers");
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Navigation("DeviceUsers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,62 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddAlertNotifications : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AlertNotifications",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
DeviceId = table.Column<int>(type: "int", nullable: false),
UserId = table.Column<int>(type: "int", nullable: false),
AlertType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: false),
Message = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: false),
SentAt = table.Column<DateTime>(type: "datetime2", nullable: false),
IsSent = table.Column<bool>(type: "bit", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AlertNotifications", x => x.Id);
table.ForeignKey(
name: "FK_AlertNotifications_Devices_DeviceId",
column: x => x.DeviceId,
principalTable: "Devices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_AlertNotifications_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_AlertNotifications_DeviceId_UserId_AlertType_SentAt",
table: "AlertNotifications",
columns: new[] { "DeviceId", "UserId", "AlertType", "SentAt" });
migrationBuilder.CreateIndex(
name: "IX_AlertNotifications_UserId",
table: "AlertNotifications",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AlertNotifications");
}
}
}

View File

@@ -0,0 +1,376 @@
// <auto-generated />
using System;
using GreenHome.Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
[DbContext(typeof(GreenHomeDbContext))]
[Migration("20251119142729_FixAlertNotifications")]
partial class FixAlertNotifications
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AlertType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<string>("ErrorMessage")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<bool>("IsSent")
.HasColumnType("bit");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("MessageOutboxIds")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTime>("SentAt")
.HasColumnType("datetime2");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("DeviceId", "UserId", "AlertType", "SentAt");
b.ToTable("AlertNotifications", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("DeviceName")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<string>("Location")
.IsRequired()
.HasMaxLength(250)
.HasColumnType("nvarchar(250)");
b.Property<string>("NeshanLocation")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("nvarchar(80)");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Devices", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<decimal>("DangerMaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("DangerMinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("MaxGasPPM")
.HasColumnType("int");
b.Property<decimal>("MaxHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MaxTemperature")
.HasColumnType("decimal(18,2)");
b.Property<int>("MinGasPPM")
.HasColumnType("int");
b.Property<decimal>("MinHumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinLux")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("MinTemperature")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId")
.IsUnique();
b.ToTable("DeviceSettings", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("DeviceId", "UserId");
b.HasIndex("UserId");
b.ToTable("DeviceUsers", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("GasPPM")
.HasColumnType("int");
b.Property<decimal>("HumidityPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("Lux")
.HasColumnType("decimal(18,2)");
b.Property<string>("PersianDate")
.IsRequired()
.HasMaxLength(10)
.HasColumnType("nvarchar(10)");
b.Property<int>("PersianMonth")
.HasColumnType("int");
b.Property<int>("PersianYear")
.HasColumnType("int");
b.Property<decimal>("SoilPercent")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TemperatureC")
.HasColumnType("decimal(18,2)");
b.Property<DateTime>("TimestampUtc")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("DeviceId", "TimestampUtc");
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
b.ToTable("Telemetry", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Family")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("Role")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Mobile")
.IsUnique();
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(4)
.HasColumnType("nvarchar(4)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime2");
b.Property<bool>("IsUsed")
.HasColumnType("bit");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<DateTime?>("UsedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("Mobile", "Code", "IsUsed");
b.ToTable("VerificationCodes", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany()
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Device");
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany()
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany("DeviceUsers")
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("GreenHome.Domain.User", "User")
.WithMany("DeviceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Navigation("DeviceUsers");
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Navigation("DeviceUsers");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GreenHome.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class FixAlertNotifications : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ErrorMessage",
table: "AlertNotifications",
type: "nvarchar(1000)",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "MessageOutboxIds",
table: "AlertNotifications",
type: "nvarchar(500)",
maxLength: 500,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ErrorMessage",
table: "AlertNotifications");
migrationBuilder.DropColumn(
name: "MessageOutboxIds",
table: "AlertNotifications");
}
}
}

View File

@@ -22,6 +22,53 @@ namespace GreenHome.Infrastructure.Migrations
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("AlertType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<string>("ErrorMessage")
.HasMaxLength(1000)
.HasColumnType("nvarchar(1000)");
b.Property<bool>("IsSent")
.HasColumnType("bit");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<string>("MessageOutboxIds")
.HasMaxLength(500)
.HasColumnType("nvarchar(500)");
b.Property<DateTime>("SentAt")
.HasColumnType("datetime2");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.HasIndex("DeviceId", "UserId", "AlertType", "SentAt");
b.ToTable("AlertNotifications", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Property<int>("Id")
@@ -40,20 +87,18 @@ namespace GreenHome.Infrastructure.Migrations
.HasMaxLength(250)
.HasColumnType("nvarchar(250)");
b.Property<string>("Mobile")
.HasColumnType("nvarchar(max)");
b.Property<string>("NeshanLocation")
.IsRequired()
.HasMaxLength(80)
.HasColumnType("nvarchar(80)");
b.Property<string>("Owner")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Devices", (string)null);
});
@@ -112,6 +157,21 @@ namespace GreenHome.Infrastructure.Migrations
b.ToTable("DeviceSettings", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.Property<int>("DeviceId")
.HasColumnType("int");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("DeviceId", "UserId");
b.HasIndex("UserId");
b.ToTable("DeviceUsers", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
{
b.Property<int>("Id")
@@ -161,6 +221,113 @@ namespace GreenHome.Infrastructure.Migrations
b.ToTable("Telemetry", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<string>("Family")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("datetime2");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("Role")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("Mobile")
.IsUnique();
b.ToTable("Users", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(4)
.HasColumnType("nvarchar(4)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("datetime2");
b.Property<bool>("IsUsed")
.HasColumnType("bit");
b.Property<string>("Mobile")
.IsRequired()
.HasMaxLength(11)
.HasColumnType("nvarchar(11)");
b.Property<DateTime?>("UsedAt")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("Mobile", "Code", "IsUsed");
b.ToTable("VerificationCodes", (string)null);
});
modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany()
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Device");
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.HasOne("GreenHome.Domain.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
@@ -171,6 +338,35 @@ namespace GreenHome.Infrastructure.Migrations
b.Navigation("Device");
});
modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
{
b.HasOne("GreenHome.Domain.Device", "Device")
.WithMany("DeviceUsers")
.HasForeignKey("DeviceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("GreenHome.Domain.User", "User")
.WithMany("DeviceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Device");
b.Navigation("User");
});
modelBuilder.Entity("GreenHome.Domain.Device", b =>
{
b.Navigation("DeviceUsers");
});
modelBuilder.Entity("GreenHome.Domain.User", b =>
{
b.Navigation("DeviceUsers");
});
#pragma warning restore 612, 618
}
}

View File

@@ -22,7 +22,12 @@ public sealed class TelemetryService : ITelemetryService
var entity = mapper.Map<Domain.TelemetryRecord>(dto);
if (!string.IsNullOrEmpty(dto.DeviceName))
{
entity.DeviceId = dbContext.Devices.First(d => d.DeviceName == dto.DeviceName).Id;
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
}
}
var dt = dto.TimestampUtc;
var py = PersianCalendar.GetYear(dt);

View File

@@ -0,0 +1,4 @@
public class VoiceCallService
{
}