Compare commits
5 Commits
dca3fc62ca
...
40fe6e897a
| Author | SHA1 | Date | |
|---|---|---|---|
| 40fe6e897a | |||
| 4df537816f | |||
| 97a946ebea | |||
| 9fe575ae07 | |||
| 775ae0d9b4 |
@@ -8,13 +8,16 @@ namespace GreenHome.Api.Controllers;
|
|||||||
public class PowerOutageController : ControllerBase
|
public class PowerOutageController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IAlertService _alertService;
|
private readonly IAlertService _alertService;
|
||||||
|
private readonly IPowerOutageLogService _powerOutageLogService;
|
||||||
private readonly ILogger<PowerOutageController> _logger;
|
private readonly ILogger<PowerOutageController> _logger;
|
||||||
|
|
||||||
public PowerOutageController(
|
public PowerOutageController(
|
||||||
IAlertService alertService,
|
IAlertService alertService,
|
||||||
|
IPowerOutageLogService powerOutageLogService,
|
||||||
ILogger<PowerOutageController> logger)
|
ILogger<PowerOutageController> logger)
|
||||||
{
|
{
|
||||||
_alertService = alertService;
|
_alertService = alertService;
|
||||||
|
_powerOutageLogService = powerOutageLogService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,5 +59,175 @@ public class PowerOutageController : ControllerBase
|
|||||||
return StatusCode(500, new { error = "خطا در ارسال هشدار قطع برق" });
|
return StatusCode(500, new { error = "خطا در ارسال هشدار قطع برق" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت لیست رویدادهای قطع و وصل برق یک دستگاه
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">شناسه دستگاه</param>
|
||||||
|
/// <param name="startUtc">شروع بازه (UTC)</param>
|
||||||
|
/// <param name="endUtc">پایان بازه (UTC)</param>
|
||||||
|
/// <param name="eventType">نوع رویداد: 1 = وصل, 2 = قطع (اختیاری)</param>
|
||||||
|
/// <param name="page">شماره صفحه</param>
|
||||||
|
/// <param name="pageSize">تعداد در هر صفحه</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>لیست رویدادها</returns>
|
||||||
|
[HttpGet("logs")]
|
||||||
|
public async Task<ActionResult<PagedResult<PowerOutageLogDto>>> GetLogs(
|
||||||
|
[FromQuery] int deviceId,
|
||||||
|
[FromQuery] DateTime? startUtc,
|
||||||
|
[FromQuery] DateTime? endUtc,
|
||||||
|
[FromQuery] int? eventType,
|
||||||
|
[FromQuery] int page = 1,
|
||||||
|
[FromQuery] int pageSize = 20,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (deviceId <= 0)
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = "شناسه دستگاه نامعتبر است" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter = new PowerOutageFilter
|
||||||
|
{
|
||||||
|
EventType = eventType,
|
||||||
|
StartDateUtc = startUtc,
|
||||||
|
EndDateUtc = endUtc,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _powerOutageLogService.ListAsync(deviceId, filter, cancellationToken);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting power outage logs: DeviceId={DeviceId}", deviceId);
|
||||||
|
return StatusCode(500, new { error = "خطا در دریافت لیست رویدادهای برق" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت خلاصه قطعیهای برق یک دستگاه در یک بازه زمانی
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">شناسه دستگاه</param>
|
||||||
|
/// <param name="startUtc">شروع بازه (UTC)</param>
|
||||||
|
/// <param name="endUtc">پایان بازه (UTC)</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>خلاصه قطعیها شامل تعداد قطعیها و مجموع مدت زمان قطعی</returns>
|
||||||
|
[HttpGet("summary")]
|
||||||
|
public async Task<ActionResult<PowerOutageSummaryDto>> GetSummary(
|
||||||
|
[FromQuery] int deviceId,
|
||||||
|
[FromQuery] DateTime? startUtc,
|
||||||
|
[FromQuery] DateTime? endUtc,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (deviceId <= 0)
|
||||||
|
{
|
||||||
|
return BadRequest(new { error = "شناسه دستگاه نامعتبر است" });
|
||||||
|
}
|
||||||
|
|
||||||
|
var filter = new PowerOutageFilter
|
||||||
|
{
|
||||||
|
StartDateUtc = startUtc,
|
||||||
|
EndDateUtc = endUtc,
|
||||||
|
PageSize = int.MaxValue // Get all records for summary
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _powerOutageLogService.ListAsync(deviceId, filter, cancellationToken);
|
||||||
|
|
||||||
|
var outages = result.Items.Where(x => x.CurrentPower == 0).ToList();
|
||||||
|
var connections = result.Items.Where(x => x.CurrentPower == 1).ToList();
|
||||||
|
|
||||||
|
// Calculate total downtime by pairing disconnects with subsequent connects
|
||||||
|
long totalDowntimeSeconds = 0;
|
||||||
|
var pairedOutages = new List<PowerOutagePairDto>();
|
||||||
|
|
||||||
|
foreach (var outage in outages)
|
||||||
|
{
|
||||||
|
// Find the next connect event after this outage
|
||||||
|
var nextConnect = connections
|
||||||
|
.Where(c => c.TimestampUtc > outage.TimestampUtc)
|
||||||
|
.OrderBy(c => c.TimestampUtc)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (nextConnect != null)
|
||||||
|
{
|
||||||
|
var duration = (nextConnect.TimestampUtc - outage.TimestampUtc).TotalSeconds;
|
||||||
|
totalDowntimeSeconds += (long)duration;
|
||||||
|
|
||||||
|
pairedOutages.Add(new PowerOutagePairDto
|
||||||
|
{
|
||||||
|
DisconnectedAt = outage.TimestampUtc,
|
||||||
|
ConnectedAt = nextConnect.TimestampUtc,
|
||||||
|
DurationSeconds = (long)duration
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Still disconnected (no subsequent connect)
|
||||||
|
pairedOutages.Add(new PowerOutagePairDto
|
||||||
|
{
|
||||||
|
DisconnectedAt = outage.TimestampUtc,
|
||||||
|
ConnectedAt = null,
|
||||||
|
DurationSeconds = null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var summary = new PowerOutageSummaryDto
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
TotalOutageCount = outages.Count,
|
||||||
|
TotalDowntimeSeconds = totalDowntimeSeconds,
|
||||||
|
StartDateUtc = startUtc,
|
||||||
|
EndDateUtc = endUtc,
|
||||||
|
Outages = pairedOutages
|
||||||
|
};
|
||||||
|
|
||||||
|
return Ok(summary);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error getting power outage summary: DeviceId={DeviceId}", deviceId);
|
||||||
|
return StatusCode(500, new { error = "خطا در دریافت خلاصه قطعیهای برق" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مهاجرت دادههای قدیمی از جدول Telemetry به PowerOutageLogs
|
||||||
|
/// این اندپوینت یکبار مصرف است - بعد از اجرا میتوانید آن را غیرفعال کنید
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">اختیاری - اگر داده شود فقط برای همان دستگاه مهاجرت میکند</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>تعداد رکوردهای مهاجرت داده شده</returns>
|
||||||
|
[HttpPost("migrate")]
|
||||||
|
public async Task<ActionResult> MigrateFromTelemetry(
|
||||||
|
[FromQuery] int? deviceId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting migration from Telemetry to PowerOutageLogs...");
|
||||||
|
|
||||||
|
var count = await _powerOutageLogService.MigrateFromTelemetryAsync(deviceId, cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("Migration completed: {Count} records inserted", count);
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
message = $"مهاجرت با موفقیت انجام شد. {count} رکورد به جدول PowerOutageLogs اضافه شد.",
|
||||||
|
recordsInserted = count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during migration from Telemetry to PowerOutageLogs");
|
||||||
|
return StatusCode(500, new { error = $"خطا در مهاجرت دادهها: {ex.Message}" });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using GreenHome.Application;
|
using GreenHome.Application;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace GreenHome.Api.Controllers;
|
namespace GreenHome.Api.Controllers;
|
||||||
|
|
||||||
@@ -29,38 +30,66 @@ public class TelemetryController : ControllerBase
|
|||||||
[HttpGet("AddData")]
|
[HttpGet("AddData")]
|
||||||
public async Task<ActionResult<string>> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken)
|
public async Task<ActionResult<string>> Create([FromQuery] TelemetryDto telementry, [FromQuery] string? lastUpdateDate, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|
||||||
telementry.TimestampUtc = DateTime.UtcNow;
|
telementry.TimestampUtc = DateTime.UtcNow;
|
||||||
var id = await telemetryService.AddAsync(telementry, cancellationToken);
|
var id = await telemetryService.AddAsync(telementry, cancellationToken);
|
||||||
|
|
||||||
string? message;
|
var parts = new List<string> { $"ID:{id}" };
|
||||||
|
|
||||||
var deviceId = telementry.DeviceId;
|
var deviceId = telementry.DeviceId;
|
||||||
if(deviceId > 0)
|
|
||||||
|
if (deviceId > 0)
|
||||||
{
|
{
|
||||||
message = await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken);
|
// 1. Check alerts and power status
|
||||||
if(!string.IsNullOrEmpty(message))
|
var alertResult = await alertService.CheckAndSendAlertsAsync(deviceId, telementry, cancellationToken);
|
||||||
|
if (alertResult != null)
|
||||||
{
|
{
|
||||||
return Ok(message);
|
if (alertResult.Messages.Count > 0)
|
||||||
|
{
|
||||||
|
parts.Add($"A:{alertResult.Messages.Count}");
|
||||||
|
parts.Add($"R:{alertResult.Recipients}");
|
||||||
|
parts.Add($"M:{string.Join("@", alertResult.Messages)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alertResult.PowerStatus != null)
|
||||||
|
{
|
||||||
|
parts.Add($"P:{alertResult.PowerStatus}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check relay conditions update
|
||||||
|
if (!string.IsNullOrEmpty(lastUpdateDate))
|
||||||
|
{
|
||||||
|
var parsedDate = ParseLastUpdateDate(lastUpdateDate);
|
||||||
|
var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken);
|
||||||
|
if (relayConditions.Count > 0)
|
||||||
|
{
|
||||||
|
parts.Add("RU:1");
|
||||||
|
parts.Add($"RL:{relayConditions.Count}");
|
||||||
|
parts.Add($"RC:{EncodeRelayConditions(relayConditions)}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if relay conditions have been updated since lastUpdateDate
|
return Ok(string.Join("|", parts));
|
||||||
if (deviceId > 0 && !string.IsNullOrEmpty(lastUpdateDate))
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encode relay conditions into a compact string format for STM32 parsing.
|
||||||
|
/// Format: E{enabled}T{timeType}R{relay}S{sensor}O{op}V{value1}[,{value2}]
|
||||||
|
/// Multiple conditions separated by |, multiple rules within a condition separated by &
|
||||||
|
/// </summary>
|
||||||
|
private static string EncodeRelayConditions(IReadOnlyList<RelayConditionDto> conditions)
|
||||||
|
{
|
||||||
|
return string.Join("|", conditions.Select(c =>
|
||||||
{
|
{
|
||||||
var parsedDate = ParseLastUpdateDate(lastUpdateDate);
|
var rules = string.Join("&", c.Rules.Select(r =>
|
||||||
// var relayConditions = await alertConditionService.GetUpdatedRelayConditionsAsync(deviceId, parsedDate, cancellationToken);
|
{
|
||||||
// if (relayConditions.Count > 0)
|
var rule = $"S{r.Sensor}O{r.Op}V{r.V1}";
|
||||||
// {
|
if (r.V2.HasValue)
|
||||||
// return Ok(new { conditions = relayConditions });
|
rule += $",{r.V2.Value}";
|
||||||
// }
|
return rule;
|
||||||
|
}));
|
||||||
var hasUpdates = await alertConditionService.HasUpdatedConditionsAsync(deviceId, parsedDate, cancellationToken);
|
return $"E{c.Enabled}T{c.TimeType}R{c.Relay}{rules}";
|
||||||
if(hasUpdates)
|
}));
|
||||||
return Ok("Relay");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(id + "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -2,92 +2,104 @@ using FluentValidation;
|
|||||||
using GreenHome.AI.DeepSeek;
|
using GreenHome.AI.DeepSeek;
|
||||||
using GreenHome.Application;
|
using GreenHome.Application;
|
||||||
using GreenHome.Infrastructure;
|
using GreenHome.Infrastructure;
|
||||||
|
using GreenHome.Infrastructure.Data;
|
||||||
using GreenHome.Sms.Ippanel;
|
using GreenHome.Sms.Ippanel;
|
||||||
using GreenHome.VoiceCall.Avanak;
|
using GreenHome.VoiceCall.Avanak;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Scalar.AspNetCore;
|
using Scalar.AspNetCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// ============================================================
|
||||||
builder.Services.AddControllers();
|
// 1. Service Registration
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
// ============================================================
|
||||||
builder.Services.AddOpenApi();
|
|
||||||
|
|
||||||
// Application/Infrastructure DI
|
ConfigureCoreServices(builder);
|
||||||
builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile));
|
ConfigureCors(builder);
|
||||||
builder.Services.AddValidatorsFromAssemblyContaining<GreenHome.Application.DeviceDtoValidator>();
|
ConfigureExternalServices(builder);
|
||||||
|
|
||||||
// CORS Configuration
|
|
||||||
const string CorsPolicy = "DefaultCors";
|
|
||||||
builder.Services.AddCors(options =>
|
|
||||||
{
|
|
||||||
options.AddPolicy(CorsPolicy, policy =>
|
|
||||||
{
|
|
||||||
if (builder.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
// در محیط Development همه origin ها مجاز هستند
|
|
||||||
policy
|
|
||||||
.AllowAnyOrigin()
|
|
||||||
.AllowAnyHeader()
|
|
||||||
.AllowAnyMethod();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// در محیط Production فقط دامنههای مشخص
|
|
||||||
policy
|
|
||||||
.WithOrigins(
|
|
||||||
"http://green.nabaksoft.ir",
|
|
||||||
"https://green.nabaksoft.ir",
|
|
||||||
"http://gh1.nabaksoft.ir",
|
|
||||||
"https://gh1.nabaksoft.ir"
|
|
||||||
)
|
|
||||||
.AllowAnyHeader()
|
|
||||||
.AllowAnyMethod()
|
|
||||||
.AllowCredentials();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddDbContext<GreenHome.Infrastructure.GreenHomeDbContext>(options =>
|
|
||||||
{
|
|
||||||
options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
|
|
||||||
});
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IDeviceService, GreenHome.Infrastructure.DeviceService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.ITelemetryService, GreenHome.Infrastructure.TelemetryService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IDeviceSettingsService, GreenHome.Infrastructure.DeviceSettingsService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IAuthService, GreenHome.Infrastructure.AuthService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IAlertService, GreenHome.Infrastructure.AlertService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IAlertConditionService, GreenHome.Infrastructure.AlertConditionService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.ISunCalculatorService, GreenHome.Infrastructure.SunCalculatorService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IAIQueryService, GreenHome.Infrastructure.AIQueryService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IDailyReportService, GreenHome.Infrastructure.DailyReportService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IAlertLogService, GreenHome.Infrastructure.AlertLogService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IUserDailyReportService, GreenHome.Infrastructure.UserDailyReportService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IChecklistService, GreenHome.Infrastructure.ChecklistService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IMonthlyReportService, GreenHome.Infrastructure.MonthlyReportService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IDevicePostService, GreenHome.Infrastructure.DevicePostService>();
|
|
||||||
builder.Services.AddScoped<GreenHome.Application.IDeviceTokenService, GreenHome.Infrastructure.DeviceTokenService>();
|
|
||||||
|
|
||||||
// SMS Service Configuration
|
|
||||||
builder.Services.AddIppanelSms(builder.Configuration);
|
|
||||||
|
|
||||||
// Voice Call Service Configuration
|
|
||||||
builder.Services.AddAvanakVoiceCall(builder.Configuration);
|
|
||||||
|
|
||||||
// AI Service Configuration
|
|
||||||
builder.Services.AddDeepSeek(builder.Configuration);
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Apply pending migrations automatically
|
// ============================================================
|
||||||
using (var scope = app.Services.CreateScope())
|
// 2. Pipeline Configuration
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
ApplyMigrations(app);
|
||||||
|
ConfigureMiddleware(app);
|
||||||
|
ConfigureStaticFiles(app);
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Service Registration Methods
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void ConfigureCoreServices(WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddOpenApi();
|
||||||
|
|
||||||
|
builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile));
|
||||||
|
builder.Services.AddValidatorsFromAssemblyContaining<GreenHome.Application.DeviceDtoValidator>();
|
||||||
|
|
||||||
|
builder.Services.AddInfrastructure(builder.Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureCors(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
const string CorsPolicy = "DefaultCors";
|
||||||
|
|
||||||
|
builder.Services.AddCors(options =>
|
||||||
|
{
|
||||||
|
options.AddPolicy(CorsPolicy, policy =>
|
||||||
|
{
|
||||||
|
if (builder.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
policy
|
||||||
|
.AllowAnyOrigin()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
policy
|
||||||
|
.WithOrigins(
|
||||||
|
"http://green.nabaksoft.ir",
|
||||||
|
"https://green.nabaksoft.ir",
|
||||||
|
"http://gh1.nabaksoft.ir",
|
||||||
|
"https://gh1.nabaksoft.ir"
|
||||||
|
)
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowCredentials();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureExternalServices(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddIppanelSms(builder.Configuration);
|
||||||
|
builder.Services.AddAvanakVoiceCall(builder.Configuration);
|
||||||
|
builder.Services.AddDeepSeek(builder.Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Middleware & Pipeline Methods
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void ApplyMigrations(WebApplication app)
|
||||||
|
{
|
||||||
|
using var scope = app.Services.CreateScope();
|
||||||
var services = scope.ServiceProvider;
|
var services = scope.ServiceProvider;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var context = services.GetRequiredService<GreenHome.Infrastructure.GreenHomeDbContext>();
|
var context = services.GetRequiredService<GreenHomeDbContext>();
|
||||||
context.Database.Migrate();
|
context.Database.Migrate();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -97,28 +109,30 @@ using (var scope = app.Services.CreateScope())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
void ConfigureMiddleware(WebApplication app)
|
||||||
app.MapOpenApi();
|
|
||||||
app.MapScalarApiReference();
|
|
||||||
if (!app.Environment.IsDevelopment())
|
|
||||||
{
|
{
|
||||||
app.UseHttpsRedirection();
|
app.MapOpenApi();
|
||||||
|
app.MapScalarApiReference();
|
||||||
|
|
||||||
|
if (!app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseCors("DefaultCors");
|
||||||
|
app.UseAuthorization();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseCors(CorsPolicy);
|
void ConfigureStaticFiles(WebApplication app)
|
||||||
app.UseAuthorization();
|
|
||||||
|
|
||||||
var provider = new FileExtensionContentTypeProvider();
|
|
||||||
provider.Mappings[".amr"] = "audio/amr";
|
|
||||||
|
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
|
||||||
{
|
{
|
||||||
FileProvider = new PhysicalFileProvider(
|
var provider = new FileExtensionContentTypeProvider();
|
||||||
Path.Combine(builder.Environment.ContentRootPath, "My_StaticFiles")),
|
provider.Mappings[".amr"] = "audio/amr";
|
||||||
RequestPath = "/My_StaticFiles",
|
|
||||||
ContentTypeProvider = provider
|
|
||||||
});
|
|
||||||
|
|
||||||
app.MapControllers();
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
app.Run();
|
FileProvider = new PhysicalFileProvider(
|
||||||
|
Path.Combine(app.Environment.ContentRootPath, "My_StaticFiles")),
|
||||||
|
RequestPath = "/My_StaticFiles",
|
||||||
|
ContentTypeProvider = provider
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"Default": "Server=87.107.108.119;TrustServerCertificate=True;Database=GreenHomeDb;User Id=sa;Password=qwER12#$110"
|
"Default": "Server=.;Integrated Security=False;Trusted_Connection=True;Encrypt=false;MultipleActiveResultSets=true;Database=GreenHomeDb;"
|
||||||
},
|
},
|
||||||
"AvanakVoiceCall": {
|
"AvanakVoiceCall": {
|
||||||
"Token": "A948B776B90CFD919B0EC60929714136CCB49DDB"
|
"Token": "A948B776B90CFD919B0EC60929714136CCB49DDB"
|
||||||
|
|||||||
@@ -1,409 +0,0 @@
|
|||||||
using Domain = GreenHome.Domain;
|
|
||||||
|
|
||||||
namespace GreenHome.Application;
|
|
||||||
|
|
||||||
// DTOs
|
|
||||||
public sealed class DeviceDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string DeviceName { get; set; } = string.Empty;
|
|
||||||
public int UserId { get; set; }
|
|
||||||
public string UserName { get; set; } = string.Empty;
|
|
||||||
public string UserFamily { get; set; } = string.Empty;
|
|
||||||
public string UserMobile { get; set; } = string.Empty;
|
|
||||||
public string Location { get; set; } = string.Empty;
|
|
||||||
public string NeshanLocation { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class TelemetryDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string? DeviceName { get; set; }
|
|
||||||
public DateTime? TimestampUtc { get; set; }
|
|
||||||
public decimal TemperatureC { get; set; }
|
|
||||||
public decimal HumidityPercent { get; set; }
|
|
||||||
public decimal SoilPercent { get; set; }
|
|
||||||
public int GasPPM { get; set; }
|
|
||||||
public decimal? Voltage { get; set; }
|
|
||||||
public decimal? BatteryVoltage { get; set; }
|
|
||||||
public decimal? BatteryPercent { get; set; }
|
|
||||||
public byte? Power { get; set; }
|
|
||||||
public byte? OldPower { get; set; }
|
|
||||||
public decimal Lux { get; set; }
|
|
||||||
public int? PersianYear { get; set; }
|
|
||||||
public int? PersianMonth { get; set; }
|
|
||||||
public string PersianDate { get; set; } = string.Empty;
|
|
||||||
public DateTime? ServerTimestampUtc { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class TelemetryFilter
|
|
||||||
{
|
|
||||||
public int? DeviceId { get; set; }
|
|
||||||
public DateTime? StartDateUtc { get; set; }
|
|
||||||
public DateTime? EndDateUtc { get; set; }
|
|
||||||
public int Page { get; set; } = 1;
|
|
||||||
public int PageSize { get; set; } = 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class PagedResult<T>
|
|
||||||
{
|
|
||||||
public required IReadOnlyList<T> Items { get; init; }
|
|
||||||
public required int TotalCount { get; init; }
|
|
||||||
public required int Page { get; init; }
|
|
||||||
public required int PageSize { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class TelemetryMinMax
|
|
||||||
{
|
|
||||||
public decimal MinTemperatureC { get; set; }
|
|
||||||
public decimal MaxTemperatureC { get; set; }
|
|
||||||
public decimal MinHumidityPercent { get; set; }
|
|
||||||
public decimal MaxHumidityPercent { get; set; }
|
|
||||||
public decimal MinSoilPercent { get; set; }
|
|
||||||
public decimal MaxSoilPercent { get; set; }
|
|
||||||
public int MinGasPPM { get; set; }
|
|
||||||
public int MaxGasPPM { get; set; }
|
|
||||||
public decimal MinLux { get; set; }
|
|
||||||
public decimal MaxLux { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DayCount
|
|
||||||
{
|
|
||||||
public string PersianDate { get; set; } = string.Empty;
|
|
||||||
public int Count { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class SendCodeRequest
|
|
||||||
{
|
|
||||||
public required string Mobile { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class SendCodeResponse
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string? Message { get; set; }
|
|
||||||
public int ResendAfterSeconds { get; set; } = 120;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class VerifyCodeRequest
|
|
||||||
{
|
|
||||||
public required string Mobile { get; set; }
|
|
||||||
public required string Code { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class VerifyCodeResponse
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string? Message { get; set; }
|
|
||||||
public string? Token { get; set; }
|
|
||||||
public UserDto? User { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UserDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Mobile { get; set; } = string.Empty;
|
|
||||||
public string Name { get; set; } = string.Empty;
|
|
||||||
public string Family { get; set; } = string.Empty;
|
|
||||||
public Domain.UserRole Role { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DeviceFilter
|
|
||||||
{
|
|
||||||
public int? UserId { get; set; }
|
|
||||||
public string? Search { get; set; } // جستجو در نام دستگاه، نام صاحب، نام خانوادگی صاحب، Location
|
|
||||||
public int Page { get; set; } = 1;
|
|
||||||
public int PageSize { get; set; } = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DeviceSettingsDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public string Province { get; set; } = string.Empty;
|
|
||||||
public string City { get; set; } = string.Empty;
|
|
||||||
public decimal? Latitude { get; set; }
|
|
||||||
public decimal? Longitude { get; set; }
|
|
||||||
|
|
||||||
public string ProductType { get; set; } = string.Empty;
|
|
||||||
public int MinimumSmsIntervalMinutes { get; set; } = 15;
|
|
||||||
public int MinimumCallIntervalMinutes { get; set; } = 60;
|
|
||||||
public decimal? AreaSquareMeters { get; set; }
|
|
||||||
|
|
||||||
public int UploadIntervalMin { get; set; } = 5;
|
|
||||||
public string DevicePhoneNumber { get; set; } = string.Empty;
|
|
||||||
public Domain.SimCardType? SimCardType { get; set; }
|
|
||||||
public int RelayCount { get; set; } = 4;
|
|
||||||
public string SmsRecievers { get; set; } = string.Empty;
|
|
||||||
public string? TokenCode { get; set; }
|
|
||||||
public string? VerificationCode { get; set; }
|
|
||||||
public DateTime? TokenExpiresAt { get; set; }
|
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
public DateTime UpdatedAt { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AlertConditionDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; } = string.Empty;
|
|
||||||
public Domain.AlertNotificationType NotificationType { get; set; }
|
|
||||||
public Domain.AlertTimeType TimeType { get; set; }
|
|
||||||
public int CallCooldownMinutes { get; set; } = 60;
|
|
||||||
public int SmsCooldownMinutes { get; set; } = 15;
|
|
||||||
public bool IsEnabled { get; set; } = true;
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
public DateTime UpdatedAt { get; set; }
|
|
||||||
public List<AlertRuleDto> Rules { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AlertRuleDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int AlertConditionId { get; set; }
|
|
||||||
public Domain.SensorType SensorType { get; set; }
|
|
||||||
public Domain.ComparisonType ComparisonType { get; set; }
|
|
||||||
public decimal Value1 { get; set; }
|
|
||||||
public decimal? Value2 { get; set; }
|
|
||||||
public int Order { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// DTO for relay alert conditions (notificationType > 0)
|
|
||||||
/// </summary>
|
|
||||||
public sealed class RelayConditionDto
|
|
||||||
{
|
|
||||||
public int Enabled { get; set; }
|
|
||||||
public int TimeType { get; set; }
|
|
||||||
public string Relay { get; set; } = string.Empty;
|
|
||||||
public List<RelayRuleDto> Rules { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// DTO for relay condition rules
|
|
||||||
/// </summary>
|
|
||||||
public sealed class RelayRuleDto
|
|
||||||
{
|
|
||||||
public string Sensor { get; set; } = string.Empty;
|
|
||||||
public string Op { get; set; } = string.Empty;
|
|
||||||
public decimal V1 { get; set; }
|
|
||||||
public decimal? V2 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CreateAlertConditionRequest
|
|
||||||
{
|
|
||||||
public required int DeviceId { get; set; }
|
|
||||||
public required Domain.AlertNotificationType NotificationType { get; set; }
|
|
||||||
public required Domain.AlertTimeType TimeType { get; set; }
|
|
||||||
public int CallCooldownMinutes { get; set; } = 60;
|
|
||||||
public int SmsCooldownMinutes { get; set; } = 15;
|
|
||||||
public bool IsEnabled { get; set; } = true;
|
|
||||||
public required List<CreateAlertRuleRequest> Rules { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CreateAlertRuleRequest
|
|
||||||
{
|
|
||||||
public required Domain.SensorType SensorType { get; set; }
|
|
||||||
public required Domain.ComparisonType ComparisonType { get; set; }
|
|
||||||
public required decimal Value1 { get; set; }
|
|
||||||
public decimal? Value2 { get; set; }
|
|
||||||
public int Order { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UpdateAlertConditionRequest
|
|
||||||
{
|
|
||||||
public required int Id { get; set; }
|
|
||||||
public required Domain.AlertNotificationType NotificationType { get; set; }
|
|
||||||
public required Domain.AlertTimeType TimeType { get; set; }
|
|
||||||
public int CallCooldownMinutes { get; set; } = 60;
|
|
||||||
public int SmsCooldownMinutes { get; set; } = 15;
|
|
||||||
public bool IsEnabled { get; set; } = true;
|
|
||||||
public required List<CreateAlertRuleRequest> Rules { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DailyReportRequest
|
|
||||||
{
|
|
||||||
public required int DeviceId { get; set; }
|
|
||||||
public required string PersianDate { get; set; } // yyyy/MM/dd
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class DailyReportResponse
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; } = string.Empty;
|
|
||||||
public string PersianDate { get; set; } = string.Empty;
|
|
||||||
public string Analysis { get; set; } = string.Empty;
|
|
||||||
public int RecordCount { get; set; }
|
|
||||||
public int SampledRecordCount { get; set; }
|
|
||||||
public int TotalTokens { get; set; }
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
public bool FromCache { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class WeeklyAnalysisRequest
|
|
||||||
{
|
|
||||||
public required int DeviceId { get; set; }
|
|
||||||
public required string StartDate { get; set; } // yyyy/MM/dd
|
|
||||||
public required string EndDate { get; set; } // yyyy/MM/dd
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class MonthlyAnalysisRequest
|
|
||||||
{
|
|
||||||
public required int DeviceId { get; set; }
|
|
||||||
public required int Year { get; set; }
|
|
||||||
public required int Month { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AlertLogDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; } = string.Empty;
|
|
||||||
public int UserId { get; set; }
|
|
||||||
public string UserName { get; set; } = string.Empty;
|
|
||||||
public string UserMobile { get; set; } = string.Empty;
|
|
||||||
public int? AlertConditionId { get; set; }
|
|
||||||
public Domain.AlertType AlertType { get; set; }
|
|
||||||
public Domain.AlertNotificationType NotificationType { get; set; }
|
|
||||||
public string Message { get; set; } = string.Empty;
|
|
||||||
public Domain.AlertStatus Status { get; set; }
|
|
||||||
public string? ErrorMessage { get; set; }
|
|
||||||
public string PhoneNumber { get; set; } = string.Empty;
|
|
||||||
public DateTime SentAt { get; set; }
|
|
||||||
public long ProcessingTimeMs { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class AlertLogFilter
|
|
||||||
{
|
|
||||||
public int? DeviceId { get; set; }
|
|
||||||
public int? UserId { get; set; }
|
|
||||||
public Domain.AlertType? AlertType { get; set; }
|
|
||||||
public Domain.AlertStatus? Status { get; set; }
|
|
||||||
public DateTime? StartDate { get; set; }
|
|
||||||
public DateTime? EndDate { get; set; }
|
|
||||||
public int Page { get; set; } = 1;
|
|
||||||
public int PageSize { get; set; } = 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UserDailyReportDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public int DeviceId { get; set; }
|
|
||||||
public string DeviceName { get; set; } = string.Empty;
|
|
||||||
public int UserId { get; set; }
|
|
||||||
public string UserName { get; set; } = string.Empty;
|
|
||||||
public string UserFamily { get; set; } = string.Empty;
|
|
||||||
public string PersianDate { get; set; } = string.Empty;
|
|
||||||
public string Title { get; set; } = string.Empty;
|
|
||||||
public string Observations { get; set; } = string.Empty;
|
|
||||||
public string Operations { get; set; } = string.Empty;
|
|
||||||
public string? Notes { get; set; }
|
|
||||||
public List<ReportImageDto> Images { get; set; } = new();
|
|
||||||
public DateTime CreatedAt { get; set; }
|
|
||||||
public DateTime UpdatedAt { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ReportImageDto
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string FileName { get; set; } = string.Empty;
|
|
||||||
public string FilePath { get; set; } = string.Empty;
|
|
||||||
public string ContentType { get; set; } = string.Empty;
|
|
||||||
public long FileSize { get; set; }
|
|
||||||
public string? Description { get; set; }
|
|
||||||
public DateTime UploadedAt { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class CreateUserDailyReportRequest
|
|
||||||
{
|
|
||||||
public required int DeviceId { get; set; }
|
|
||||||
public required int UserId { get; set; }
|
|
||||||
public required string PersianDate { get; set; }
|
|
||||||
public required string Title { get; set; }
|
|
||||||
public required string Observations { get; set; }
|
|
||||||
public required string Operations { get; set; }
|
|
||||||
public string? Notes { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UpdateUserDailyReportRequest
|
|
||||||
{
|
|
||||||
public required int Id { get; set; }
|
|
||||||
public required string Title { get; set; }
|
|
||||||
public required string Observations { get; set; }
|
|
||||||
public required string Operations { get; set; }
|
|
||||||
public string? Notes { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class UserDailyReportFilter
|
|
||||||
{
|
|
||||||
public int? DeviceId { get; set; }
|
|
||||||
public int? UserId { get; set; }
|
|
||||||
public string? PersianDate { get; set; }
|
|
||||||
public int? Year { get; set; }
|
|
||||||
public int? Month { get; set; }
|
|
||||||
public int Page { get; set; } = 1;
|
|
||||||
public int PageSize { get; set; } = 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DTOs برای مدیریت توکن دستگاه
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// درخواست دریافت فاصله زمانی آپلود
|
|
||||||
/// </summary>
|
|
||||||
public sealed class GetUploadIntervalRequest
|
|
||||||
{
|
|
||||||
public int? DeviceId { get; set; }
|
|
||||||
public string? DevicePhoneNumber { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// پاسخ دریافت فاصله زمانی آپلود
|
|
||||||
/// </summary>
|
|
||||||
public sealed class GetUploadIntervalResponse
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string? Message { get; set; }
|
|
||||||
public int? UploadIntervalMin { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// درخواست دریافت توکن دستگاه
|
|
||||||
/// </summary>
|
|
||||||
public sealed class RequestDeviceTokenRequest
|
|
||||||
{
|
|
||||||
public required string DevicePhoneNumber { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// پاسخ دریافت توکن دستگاه
|
|
||||||
/// </summary>
|
|
||||||
public sealed class RequestDeviceTokenResponse
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string? Message { get; set; }
|
|
||||||
public string? TokenCode { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// درخواست تایید توکن دستگاه
|
|
||||||
/// </summary>
|
|
||||||
public sealed class VerifyDeviceTokenRequest
|
|
||||||
{
|
|
||||||
public required string DevicePhoneNumber { get; set; }
|
|
||||||
public required string VerificationCode { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// پاسخ تایید توکن دستگاه
|
|
||||||
/// </summary>
|
|
||||||
public sealed class VerifyDeviceTokenResponse
|
|
||||||
{
|
|
||||||
public bool Success { get; set; }
|
|
||||||
public string? Message { get; set; }
|
|
||||||
public string? EncodedSettings { get; set; }
|
|
||||||
}
|
|
||||||
42
src/GreenHome.Application/Dtos/AI/AIQueryStats.cs
Normal file
42
src/GreenHome.Application/Dtos/AI/AIQueryStats.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Statistics about AI query usage
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AIQueryStats
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of queries made
|
||||||
|
/// </summary>
|
||||||
|
public int TotalQueries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total tokens used across all queries
|
||||||
|
/// </summary>
|
||||||
|
public int TotalTokensUsed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total prompt tokens used
|
||||||
|
/// </summary>
|
||||||
|
public int TotalPromptTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total completion tokens used
|
||||||
|
/// </summary>
|
||||||
|
public int TotalCompletionTokens { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Average response time in milliseconds
|
||||||
|
/// </summary>
|
||||||
|
public double AverageResponseTimeMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of queries made today
|
||||||
|
/// </summary>
|
||||||
|
public int TodayQueries { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total tokens used today
|
||||||
|
/// </summary>
|
||||||
|
public int TodayTokens { get; set; }
|
||||||
|
}
|
||||||
29
src/GreenHome.Application/Dtos/Alert/AlertConditionDto.cs
Normal file
29
src/GreenHome.Application/Dtos/Alert/AlertConditionDto.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Domain = GreenHome.Domain;
|
||||||
|
|
||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class AlertConditionDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
public Domain.AlertNotificationType NotificationType { get; set; }
|
||||||
|
public Domain.AlertTimeType TimeType { get; set; }
|
||||||
|
public int CallCooldownMinutes { get; set; } = 60;
|
||||||
|
public int SmsCooldownMinutes { get; set; } = 15;
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
public List<AlertRuleDto> Rules { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class AlertRuleDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int AlertConditionId { get; set; }
|
||||||
|
public Domain.SensorType SensorType { get; set; }
|
||||||
|
public Domain.ComparisonType ComparisonType { get; set; }
|
||||||
|
public decimal Value1 { get; set; }
|
||||||
|
public decimal? Value2 { get; set; }
|
||||||
|
public int Order { get; set; }
|
||||||
|
}
|
||||||
22
src/GreenHome.Application/Dtos/Alert/AlertLogDto.cs
Normal file
22
src/GreenHome.Application/Dtos/Alert/AlertLogDto.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Domain = GreenHome.Domain;
|
||||||
|
|
||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class AlertLogDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
public string UserMobile { get; set; } = string.Empty;
|
||||||
|
public int? AlertConditionId { get; set; }
|
||||||
|
public Domain.AlertType AlertType { get; set; }
|
||||||
|
public Domain.AlertNotificationType NotificationType { get; set; }
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
|
public Domain.AlertStatus Status { get; set; }
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
public string PhoneNumber { get; set; } = string.Empty;
|
||||||
|
public DateTime SentAt { get; set; }
|
||||||
|
public long ProcessingTimeMs { get; set; }
|
||||||
|
}
|
||||||
15
src/GreenHome.Application/Dtos/Alert/AlertLogFilter.cs
Normal file
15
src/GreenHome.Application/Dtos/Alert/AlertLogFilter.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Domain = GreenHome.Domain;
|
||||||
|
|
||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class AlertLogFilter
|
||||||
|
{
|
||||||
|
public int? DeviceId { get; set; }
|
||||||
|
public int? UserId { get; set; }
|
||||||
|
public Domain.AlertType? AlertType { get; set; }
|
||||||
|
public Domain.AlertStatus? Status { get; set; }
|
||||||
|
public DateTime? StartDate { get; set; }
|
||||||
|
public DateTime? EndDate { get; set; }
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 20;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Domain = GreenHome.Domain;
|
||||||
|
|
||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class CreateAlertConditionRequest
|
||||||
|
{
|
||||||
|
public required int DeviceId { get; set; }
|
||||||
|
public required Domain.AlertNotificationType NotificationType { get; set; }
|
||||||
|
public required Domain.AlertTimeType TimeType { get; set; }
|
||||||
|
public int CallCooldownMinutes { get; set; } = 60;
|
||||||
|
public int SmsCooldownMinutes { get; set; } = 15;
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
public required List<CreateAlertRuleRequest> Rules { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CreateAlertRuleRequest
|
||||||
|
{
|
||||||
|
public required Domain.SensorType SensorType { get; set; }
|
||||||
|
public required Domain.ComparisonType ComparisonType { get; set; }
|
||||||
|
public required decimal Value1 { get; set; }
|
||||||
|
public decimal? Value2 { get; set; }
|
||||||
|
public int Order { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UpdateAlertConditionRequest
|
||||||
|
{
|
||||||
|
public required int Id { get; set; }
|
||||||
|
public required Domain.AlertNotificationType NotificationType { get; set; }
|
||||||
|
public required Domain.AlertTimeType TimeType { get; set; }
|
||||||
|
public int CallCooldownMinutes { get; set; } = 60;
|
||||||
|
public int SmsCooldownMinutes { get; set; } = 15;
|
||||||
|
public bool IsEnabled { get; set; } = true;
|
||||||
|
public required List<CreateAlertRuleRequest> Rules { get; set; }
|
||||||
|
}
|
||||||
23
src/GreenHome.Application/Dtos/Alert/RelayConditionDto.cs
Normal file
23
src/GreenHome.Application/Dtos/Alert/RelayConditionDto.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO for relay alert conditions (notificationType > 0)
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RelayConditionDto
|
||||||
|
{
|
||||||
|
public int Enabled { get; set; }
|
||||||
|
public int TimeType { get; set; }
|
||||||
|
public string Relay { get; set; } = string.Empty;
|
||||||
|
public List<RelayRuleDto> Rules { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO for relay condition rules
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RelayRuleDto
|
||||||
|
{
|
||||||
|
public string Sensor { get; set; } = string.Empty;
|
||||||
|
public string Op { get; set; } = string.Empty;
|
||||||
|
public decimal V1 { get; set; }
|
||||||
|
public decimal? V2 { get; set; }
|
||||||
|
}
|
||||||
6
src/GreenHome.Application/Dtos/Auth/SendCodeRequest.cs
Normal file
6
src/GreenHome.Application/Dtos/Auth/SendCodeRequest.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class SendCodeRequest
|
||||||
|
{
|
||||||
|
public required string Mobile { get; set; }
|
||||||
|
}
|
||||||
8
src/GreenHome.Application/Dtos/Auth/SendCodeResponse.cs
Normal file
8
src/GreenHome.Application/Dtos/Auth/SendCodeResponse.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class SendCodeResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public int ResendAfterSeconds { get; set; } = 120;
|
||||||
|
}
|
||||||
12
src/GreenHome.Application/Dtos/Auth/UserDto.cs
Normal file
12
src/GreenHome.Application/Dtos/Auth/UserDto.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Domain = GreenHome.Domain;
|
||||||
|
|
||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class UserDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Mobile { get; set; } = string.Empty;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Family { get; set; } = string.Empty;
|
||||||
|
public Domain.UserRole Role { get; set; }
|
||||||
|
}
|
||||||
7
src/GreenHome.Application/Dtos/Auth/VerifyCodeRequest.cs
Normal file
7
src/GreenHome.Application/Dtos/Auth/VerifyCodeRequest.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class VerifyCodeRequest
|
||||||
|
{
|
||||||
|
public required string Mobile { get; set; }
|
||||||
|
public required string Code { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class VerifyCodeResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public string? Token { get; set; }
|
||||||
|
public UserDto? User { get; set; }
|
||||||
|
}
|
||||||
@@ -77,4 +77,3 @@ public sealed class CompleteChecklistItemRequest
|
|||||||
public bool IsChecked { get; set; }
|
public bool IsChecked { get; set; }
|
||||||
public string? Note { get; set; }
|
public string? Note { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
16
src/GreenHome.Application/Dtos/Common/AlertCheckResult.cs
Normal file
16
src/GreenHome.Application/Dtos/Common/AlertCheckResult.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of alert checking, used by TelemetryController.Create to build the line-protocol response
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AlertCheckResult
|
||||||
|
{
|
||||||
|
/// <summary>List of alert messages (condition alerts + power status)</summary>
|
||||||
|
public List<string> Messages { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>Comma-separated recipient phone numbers</summary>
|
||||||
|
public string? Recipients { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Power status: "connected", "disconnected", or null if unchanged</summary>
|
||||||
|
public string? PowerStatus { get; set; }
|
||||||
|
}
|
||||||
9
src/GreenHome.Application/Dtos/Common/PagedResult.cs
Normal file
9
src/GreenHome.Application/Dtos/Common/PagedResult.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class PagedResult<T>
|
||||||
|
{
|
||||||
|
public required IReadOnlyList<T> Items { get; init; }
|
||||||
|
public required int TotalCount { get; init; }
|
||||||
|
public required int Page { get; init; }
|
||||||
|
public required int PageSize { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class DailyReportRequest
|
||||||
|
{
|
||||||
|
public required int DeviceId { get; set; }
|
||||||
|
public required string PersianDate { get; set; } // yyyy/MM/dd
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class WeeklyAnalysisRequest
|
||||||
|
{
|
||||||
|
public required int DeviceId { get; set; }
|
||||||
|
public required string StartDate { get; set; } // yyyy/MM/dd
|
||||||
|
public required string EndDate { get; set; } // yyyy/MM/dd
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class MonthlyAnalysisRequest
|
||||||
|
{
|
||||||
|
public required int DeviceId { get; set; }
|
||||||
|
public required int Year { get; set; }
|
||||||
|
public required int Month { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class DailyReportResponse
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
public string PersianDate { get; set; } = string.Empty;
|
||||||
|
public string Analysis { get; set; } = string.Empty;
|
||||||
|
public int RecordCount { get; set; }
|
||||||
|
public int SampledRecordCount { get; set; }
|
||||||
|
public int TotalTokens { get; set; }
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public bool FromCache { get; set; }
|
||||||
|
}
|
||||||
13
src/GreenHome.Application/Dtos/Device/DeviceDto.cs
Normal file
13
src/GreenHome.Application/Dtos/Device/DeviceDto.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class DeviceDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
public string UserFamily { get; set; } = string.Empty;
|
||||||
|
public string UserMobile { get; set; } = string.Empty;
|
||||||
|
public string Location { get; set; } = string.Empty;
|
||||||
|
public string NeshanLocation { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
9
src/GreenHome.Application/Dtos/Device/DeviceFilter.cs
Normal file
9
src/GreenHome.Application/Dtos/Device/DeviceFilter.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class DeviceFilter
|
||||||
|
{
|
||||||
|
public int? UserId { get; set; }
|
||||||
|
public string? Search { get; set; }
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 10;
|
||||||
|
}
|
||||||
30
src/GreenHome.Application/Dtos/Device/DeviceSettingsDto.cs
Normal file
30
src/GreenHome.Application/Dtos/Device/DeviceSettingsDto.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class DeviceSettingsDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string Province { get; set; } = string.Empty;
|
||||||
|
public string City { get; set; } = string.Empty;
|
||||||
|
public decimal? Latitude { get; set; }
|
||||||
|
public decimal? Longitude { get; set; }
|
||||||
|
|
||||||
|
public string ProductType { get; set; } = string.Empty;
|
||||||
|
public int MinimumSmsIntervalMinutes { get; set; } = 15;
|
||||||
|
public int MinimumCallIntervalMinutes { get; set; } = 60;
|
||||||
|
public decimal? AreaSquareMeters { get; set; }
|
||||||
|
|
||||||
|
public int UploadIntervalMin { get; set; } = 5;
|
||||||
|
public string DevicePhoneNumber { get; set; } = string.Empty;
|
||||||
|
public Domain.SimCardType? SimCardType { get; set; }
|
||||||
|
public int RelayCount { get; set; } = 4;
|
||||||
|
public string SmsRecievers { get; set; } = string.Empty;
|
||||||
|
public string? TokenCode { get; set; }
|
||||||
|
public string? VerificationCode { get; set; }
|
||||||
|
public DateTime? TokenExpiresAt { get; set; }
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
@@ -43,4 +43,3 @@ public sealed class DevicePostFilter
|
|||||||
public int Page { get; set; } = 1;
|
public int Page { get; set; } = 1;
|
||||||
public int PageSize { get; set; } = 20;
|
public int PageSize { get; set; } = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست دریافت فاصله زمانی آپلود
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GetUploadIntervalRequest
|
||||||
|
{
|
||||||
|
public int? DeviceId { get; set; }
|
||||||
|
public string? DevicePhoneNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پاسخ دریافت فاصله زمانی آپلود
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GetUploadIntervalResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public int? UploadIntervalMin { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست دریافت توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RequestDeviceTokenRequest
|
||||||
|
{
|
||||||
|
public required string DevicePhoneNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پاسخ دریافت توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RequestDeviceTokenResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public string? TokenCode { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست تایید توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VerifyDeviceTokenRequest
|
||||||
|
{
|
||||||
|
public required string DevicePhoneNumber { get; set; }
|
||||||
|
public required string VerificationCode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پاسخ تایید توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public string? EncodedSettings { get; set; }
|
||||||
|
}
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
namespace GreenHome.Application;
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
public interface IMonthlyReportService
|
|
||||||
{
|
|
||||||
Task<MonthlyReportDto> GetMonthlyReportAsync(int deviceId, int year, int month, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class MonthlyReportDto
|
public sealed class MonthlyReportDto
|
||||||
{
|
{
|
||||||
public int DeviceId { get; set; }
|
public int DeviceId { get; set; }
|
||||||
@@ -42,4 +37,3 @@ public sealed class MonthlyReportDto
|
|||||||
|
|
||||||
public DateTime GeneratedAt { get; set; }
|
public DateTime GeneratedAt { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class PowerOutageLogDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
public byte PreviousPower { get; set; }
|
||||||
|
public byte CurrentPower { get; set; }
|
||||||
|
public string EventType { get; set; } = string.Empty; // "Connected" or "Disconnected"
|
||||||
|
public string EventTypePersian { get; set; } = string.Empty; // "وصل" or "قطع"
|
||||||
|
public DateTime TimestampUtc { get; set; }
|
||||||
|
public DateTime ServerTimestampUtc { get; set; }
|
||||||
|
public string PersianDate { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PowerOutageFilter
|
||||||
|
{
|
||||||
|
public int? EventType { get; set; } // 1 = Connected, 2 = Disconnected
|
||||||
|
public DateTime? StartDateUtc { get; set; }
|
||||||
|
public DateTime? EndDateUtc { get; set; }
|
||||||
|
public int? PersianYear { get; set; }
|
||||||
|
public int? PersianMonth { get; set; }
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 20;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// خلاصه قطعیهای برق یک دستگاه در یک بازه زمانی
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PowerOutageSummaryDto
|
||||||
|
{
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public int TotalOutageCount { get; set; }
|
||||||
|
public long TotalDowntimeSeconds { get; set; }
|
||||||
|
public DateTime? StartDateUtc { get; set; }
|
||||||
|
public DateTime? EndDateUtc { get; set; }
|
||||||
|
public List<PowerOutagePairDto> Outages { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// یک جفت قطع و وصل برق (مدت زمان قطعی)
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PowerOutagePairDto
|
||||||
|
{
|
||||||
|
public DateTime DisconnectedAt { get; set; }
|
||||||
|
public DateTime? ConnectedAt { get; set; }
|
||||||
|
public long? DurationSeconds { get; set; }
|
||||||
|
}
|
||||||
7
src/GreenHome.Application/Dtos/Telemetry/DayCount.cs
Normal file
7
src/GreenHome.Application/Dtos/Telemetry/DayCount.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class DayCount
|
||||||
|
{
|
||||||
|
public string PersianDate { get; set; } = string.Empty;
|
||||||
|
public int Count { get; set; }
|
||||||
|
}
|
||||||
23
src/GreenHome.Application/Dtos/Telemetry/TelemetryDto.cs
Normal file
23
src/GreenHome.Application/Dtos/Telemetry/TelemetryDto.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class TelemetryDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string? DeviceName { get; set; }
|
||||||
|
public DateTime? TimestampUtc { get; set; }
|
||||||
|
public decimal TemperatureC { get; set; }
|
||||||
|
public decimal HumidityPercent { get; set; }
|
||||||
|
public decimal SoilPercent { get; set; }
|
||||||
|
public int GasPPM { get; set; }
|
||||||
|
public decimal? Voltage { get; set; }
|
||||||
|
public decimal? BatteryVoltage { get; set; }
|
||||||
|
public decimal? BatteryPercent { get; set; }
|
||||||
|
public byte? Power { get; set; }
|
||||||
|
public byte? OldPower { get; set; }
|
||||||
|
public decimal Lux { get; set; }
|
||||||
|
public int? PersianYear { get; set; }
|
||||||
|
public int? PersianMonth { get; set; }
|
||||||
|
public string PersianDate { get; set; } = string.Empty;
|
||||||
|
public DateTime? ServerTimestampUtc { get; set; }
|
||||||
|
}
|
||||||
10
src/GreenHome.Application/Dtos/Telemetry/TelemetryFilter.cs
Normal file
10
src/GreenHome.Application/Dtos/Telemetry/TelemetryFilter.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class TelemetryFilter
|
||||||
|
{
|
||||||
|
public int? DeviceId { get; set; }
|
||||||
|
public DateTime? StartDateUtc { get; set; }
|
||||||
|
public DateTime? EndDateUtc { get; set; }
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 20;
|
||||||
|
}
|
||||||
15
src/GreenHome.Application/Dtos/Telemetry/TelemetryMinMax.cs
Normal file
15
src/GreenHome.Application/Dtos/Telemetry/TelemetryMinMax.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class TelemetryMinMax
|
||||||
|
{
|
||||||
|
public decimal MinTemperatureC { get; set; }
|
||||||
|
public decimal MaxTemperatureC { get; set; }
|
||||||
|
public decimal MinHumidityPercent { get; set; }
|
||||||
|
public decimal MaxHumidityPercent { get; set; }
|
||||||
|
public decimal MinSoilPercent { get; set; }
|
||||||
|
public decimal MaxSoilPercent { get; set; }
|
||||||
|
public int MinGasPPM { get; set; }
|
||||||
|
public int MaxGasPPM { get; set; }
|
||||||
|
public decimal MinLux { get; set; }
|
||||||
|
public decimal MaxLux { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class CreateUserDailyReportRequest
|
||||||
|
{
|
||||||
|
public required int DeviceId { get; set; }
|
||||||
|
public required int UserId { get; set; }
|
||||||
|
public required string PersianDate { get; set; }
|
||||||
|
public required string Title { get; set; }
|
||||||
|
public required string Observations { get; set; }
|
||||||
|
public required string Operations { get; set; }
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UpdateUserDailyReportRequest
|
||||||
|
{
|
||||||
|
public required int Id { get; set; }
|
||||||
|
public required string Title { get; set; }
|
||||||
|
public required string Observations { get; set; }
|
||||||
|
public required string Operations { get; set; }
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UserDailyReportFilter
|
||||||
|
{
|
||||||
|
public int? DeviceId { get; set; }
|
||||||
|
public int? UserId { get; set; }
|
||||||
|
public string? PersianDate { get; set; }
|
||||||
|
public int? Year { get; set; }
|
||||||
|
public int? Month { get; set; }
|
||||||
|
public int Page { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 20;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public sealed class UserDailyReportDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
public int UserId { get; set; }
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
public string UserFamily { get; set; } = string.Empty;
|
||||||
|
public string PersianDate { get; set; } = string.Empty;
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Observations { get; set; } = string.Empty;
|
||||||
|
public string Operations { get; set; } = string.Empty;
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
public List<ReportImageDto> Images { get; set; } = new();
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
public DateTime UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ReportImageDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
public string FilePath { get; set; } = string.Empty;
|
||||||
|
public string ContentType { get; set; } = string.Empty;
|
||||||
|
public long FileSize { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public DateTime UploadedAt { get; set; }
|
||||||
|
}
|
||||||
@@ -33,27 +33,7 @@ public interface IAIQueryService
|
|||||||
Task<int> GetUserTotalTokensAsync(int userId, CancellationToken cancellationToken = default);
|
Task<int> GetUserTotalTokensAsync(int userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets recent queries (all)
|
/// Gets AI query statistics (totals, averages, today's usage)
|
||||||
/// </summary>
|
|
||||||
Task<List<AIQuery>> GetRecentQueriesAsync(int take = 20, CancellationToken cancellationToken = default);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets query statistics
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<AIQueryStats> GetStatsAsync(CancellationToken cancellationToken = default);
|
Task<AIQueryStats> GetStatsAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Statistics about AI queries
|
|
||||||
/// </summary>
|
|
||||||
public class AIQueryStats
|
|
||||||
{
|
|
||||||
public int TotalQueries { get; set; }
|
|
||||||
public int TotalTokensUsed { get; set; }
|
|
||||||
public int TotalPromptTokens { get; set; }
|
|
||||||
public int TotalCompletionTokens { get; set; }
|
|
||||||
public double AverageResponseTimeMs { get; set; }
|
|
||||||
public int TodayQueries { get; set; }
|
|
||||||
public int TodayTokens { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -22,4 +22,3 @@ public interface IAlertConditionService
|
|||||||
|
|
||||||
Task<bool> HasUpdatedConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken);
|
Task<bool> HasUpdatedConditionsAsync(int deviceId, DateTime? lastUpdateDate, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,4 +6,3 @@ public interface IAlertLogService
|
|||||||
Task<AlertLogDto?> GetAlertLogByIdAsync(int id, CancellationToken cancellationToken);
|
Task<AlertLogDto?> GetAlertLogByIdAsync(int id, CancellationToken cancellationToken);
|
||||||
Task<int> CreateAlertLogAsync(AlertLogDto dto, CancellationToken cancellationToken);
|
Task<int> CreateAlertLogAsync(AlertLogDto dto, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2,7 +2,6 @@ namespace GreenHome.Application;
|
|||||||
|
|
||||||
public interface IAlertService
|
public interface IAlertService
|
||||||
{
|
{
|
||||||
Task<string?> CheckAndSendAlertsAsync(int deviceId, TelemetryDto telemetry, CancellationToken cancellationToken);
|
Task<AlertCheckResult?> CheckAndSendAlertsAsync(int deviceId, TelemetryDto telemetry, CancellationToken cancellationToken);
|
||||||
Task SendPowerOutageAlertAsync(int deviceId, CancellationToken cancellationToken);
|
Task SendPowerOutageAlertAsync(int deviceId, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6,4 +6,3 @@ public interface IAuthService
|
|||||||
Task<VerifyCodeResponse> VerifyCodeAsync(VerifyCodeRequest request, CancellationToken cancellationToken);
|
Task<VerifyCodeResponse> VerifyCodeAsync(VerifyCodeRequest request, CancellationToken cancellationToken);
|
||||||
Task<bool> CanResendCodeAsync(string mobile, CancellationToken cancellationToken);
|
Task<bool> CanResendCodeAsync(string mobile, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9,4 +9,3 @@ public interface IChecklistService
|
|||||||
Task<List<ChecklistCompletionDto>> GetCompletionsByChecklistIdAsync(int checklistId, CancellationToken cancellationToken);
|
Task<List<ChecklistCompletionDto>> GetCompletionsByChecklistIdAsync(int checklistId, CancellationToken cancellationToken);
|
||||||
Task<int> CompleteChecklistAsync(CompleteChecklistRequest request, CancellationToken cancellationToken);
|
Task<int> CompleteChecklistAsync(CompleteChecklistRequest request, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,4 +14,3 @@ public interface IDailyReportService
|
|||||||
|
|
||||||
Task<DailyReportResponse> GetMonthlyAnalysisAsync(MonthlyAnalysisRequest request, CancellationToken cancellationToken);
|
Task<DailyReportResponse> GetMonthlyAnalysisAsync(MonthlyAnalysisRequest request, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,4 +11,3 @@ public interface IDevicePostService
|
|||||||
Task DeleteImageAsync(int imageId, CancellationToken cancellationToken);
|
Task DeleteImageAsync(int imageId, CancellationToken cancellationToken);
|
||||||
Task<bool> CanUserAccessDeviceAsync(int userId, int deviceId, CancellationToken cancellationToken);
|
Task<bool> CanUserAccessDeviceAsync(int userId, int deviceId, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
namespace GreenHome.Application;
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
// Services
|
|
||||||
public interface IDeviceService
|
public interface IDeviceService
|
||||||
{
|
{
|
||||||
Task<int> AddDeviceAsync(DeviceDto dto, CancellationToken cancellationToken);
|
Task<int> AddDeviceAsync(DeviceDto dto, CancellationToken cancellationToken);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GreenHome.Application;
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
public interface IDeviceSettingsService
|
public interface IDeviceSettingsService
|
||||||
{
|
{
|
||||||
@@ -20,4 +20,3 @@ public interface IDeviceTokenService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Task<VerifyDeviceTokenResponse> VerifyDeviceTokenAsync(VerifyDeviceTokenRequest request, CancellationToken cancellationToken);
|
Task<VerifyDeviceTokenResponse> VerifyDeviceTokenAsync(VerifyDeviceTokenRequest request, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
public interface IMonthlyReportService
|
||||||
|
{
|
||||||
|
Task<MonthlyReportDto> GetMonthlyReportAsync(int deviceId, int year, int month, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// سرویس لاگ قطع و وصل برق دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public interface IPowerOutageLogService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ثبت یک رویداد قطع یا وصل برق
|
||||||
|
/// </summary>
|
||||||
|
Task LogPowerChangeAsync(int deviceId, byte previousPower, byte currentPower, DateTime timestampUtc, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت لیست رویدادهای برق یک دستگاه
|
||||||
|
/// </summary>
|
||||||
|
Task<PagedResult<PowerOutageLogDto>> ListAsync(int deviceId, PowerOutageFilter filter, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// مهاجرت دادههای قدیمی از جدول Telemetry به PowerOutageLogs
|
||||||
|
/// تشخیص تغییرات Power در رکوردهای متوالی هر دستگاه
|
||||||
|
/// </summary>
|
||||||
|
Task<int> MigrateFromTelemetryAsync(int? deviceId, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -11,4 +11,3 @@ public interface ISunCalculatorService
|
|||||||
/// <returns>true اگر روز باشد، false اگر شب باشد</returns>
|
/// <returns>true اگر روز باشد، false اگر شب باشد</returns>
|
||||||
bool IsDaytime(DateTime dateTime, decimal latitude, decimal longitude);
|
bool IsDaytime(DateTime dateTime, decimal latitude, decimal longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace GreenHome.Application;
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
public interface ITelemetryService
|
public interface ITelemetryService
|
||||||
{
|
{
|
||||||
@@ -10,4 +10,3 @@ public interface IUserDailyReportService
|
|||||||
Task<int> AddImageToReportAsync(int reportId, string fileName, string filePath, string contentType, long fileSize, string? description, CancellationToken cancellationToken);
|
Task<int> AddImageToReportAsync(int reportId, string fileName, string filePath, string contentType, long fileSize, string? description, CancellationToken cancellationToken);
|
||||||
Task DeleteImageAsync(int imageId, CancellationToken cancellationToken);
|
Task DeleteImageAsync(int imageId, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
|
|
||||||
namespace GreenHome.Application;
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
// Validation
|
|
||||||
public sealed class DeviceDtoValidator : AbstractValidator<DeviceDto>
|
public sealed class DeviceDtoValidator : AbstractValidator<DeviceDto>
|
||||||
{
|
{
|
||||||
public DeviceDtoValidator()
|
public DeviceDtoValidator()
|
||||||
60
src/GreenHome.Domain/PowerOutageLog.cs
Normal file
60
src/GreenHome.Domain/PowerOutageLog.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
namespace GreenHome.Domain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// لاگ قطع و وصل برق دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PowerOutageLog
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شناسه دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public Device Device { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// وضعیت قبلی برق (0 = قطع, 1 = وصل)
|
||||||
|
/// </summary>
|
||||||
|
public byte PreviousPower { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// وضعیت جدید برق (0 = قطع, 1 = وصل)
|
||||||
|
/// </summary>
|
||||||
|
public byte CurrentPower { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع رویداد: قطع یا وصل
|
||||||
|
/// </summary>
|
||||||
|
public PowerEventType EventType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// زمان رویداد (از دستگاه)
|
||||||
|
/// </summary>
|
||||||
|
public DateTime TimestampUtc { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// زمان ثبت در سرور
|
||||||
|
/// </summary>
|
||||||
|
public DateTime ServerTimestampUtc { get; set; }
|
||||||
|
|
||||||
|
public int PersianYear { get; set; }
|
||||||
|
public int PersianMonth { get; set; }
|
||||||
|
public string PersianDate { get; set; } = string.Empty; // yyyy/MM/dd
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع رویداد برق
|
||||||
|
/// </summary>
|
||||||
|
public enum PowerEventType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// برق وصل شده
|
||||||
|
/// </summary>
|
||||||
|
Connected = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// برق قطع شده
|
||||||
|
/// </summary>
|
||||||
|
Disconnected = 2
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace GreenHome.Infrastructure;
|
namespace GreenHome.Infrastructure.Data;
|
||||||
|
|
||||||
public sealed class GreenHomeDbContext : DbContext
|
public sealed class GreenHomeDbContext : DbContext
|
||||||
{
|
{
|
||||||
@@ -26,6 +26,7 @@ public sealed class GreenHomeDbContext : DbContext
|
|||||||
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)
|
||||||
{
|
{
|
||||||
@@ -372,5 +373,22 @@ public sealed class GreenHomeDbContext : DbContext
|
|||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
b.HasIndex(x => x.DevicePostId);
|
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,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("20251005195032_init")]
|
[Migration("20251005195032_init")]
|
||||||
@@ -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 init : Migration
|
public partial class init : 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("20251008205106_addminmaxdanger")]
|
[Migration("20251008205106_addminmaxdanger")]
|
||||||
@@ -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 addminmaxdanger : Migration
|
public partial class addminmaxdanger : 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("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
|
||||||
1310
src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260601132156_AddPowerOutageLogs.Designer.cs
generated
Normal file
1310
src/GreenHome.Infrastructure/Data/Migrations/Migrations/20260601132156_AddPowerOutageLogs.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace GreenHome.Infrastructure.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddPowerOutageLogs : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PowerOutageLogs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
|
DeviceId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
PreviousPower = table.Column<byte>(type: "tinyint", nullable: false),
|
||||||
|
CurrentPower = table.Column<byte>(type: "tinyint", nullable: false),
|
||||||
|
EventType = table.Column<int>(type: "int", nullable: false),
|
||||||
|
TimestampUtc = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
ServerTimestampUtc = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||||
|
PersianYear = 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)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PowerOutageLogs", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PowerOutageLogs_Devices_DeviceId",
|
||||||
|
column: x => x.DeviceId,
|
||||||
|
principalTable: "Devices",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PowerOutageLogs_DeviceId_PersianYear_PersianMonth",
|
||||||
|
table: "PowerOutageLogs",
|
||||||
|
columns: new[] { "DeviceId", "PersianYear", "PersianMonth" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PowerOutageLogs_DeviceId_TimestampUtc",
|
||||||
|
table: "PowerOutageLogs",
|
||||||
|
columns: new[] { "DeviceId", "TimestampUtc" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PowerOutageLogs_EventType",
|
||||||
|
table: "PowerOutageLogs",
|
||||||
|
column: "EventType");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PowerOutageLogs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -8,7 +8,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))]
|
||||||
partial class GreenHomeDbContextModelSnapshot : ModelSnapshot
|
partial class GreenHomeDbContextModelSnapshot : ModelSnapshot
|
||||||
@@ -681,6 +681,54 @@ namespace GreenHome.Infrastructure.Migrations
|
|||||||
b.ToTable("DeviceUsers", (string)null);
|
b.ToTable("DeviceUsers", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("GreenHome.Domain.PowerOutageLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<byte>("CurrentPower")
|
||||||
|
.HasColumnType("tinyint");
|
||||||
|
|
||||||
|
b.Property<int>("DeviceId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("EventType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("PersianDate")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(10)
|
||||||
|
.HasColumnType("nvarchar(10)");
|
||||||
|
|
||||||
|
b.Property<int>("PersianMonth")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("PersianYear")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<byte>("PreviousPower")
|
||||||
|
.HasColumnType("tinyint");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ServerTimestampUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TimestampUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EventType");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId", "TimestampUtc");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
|
||||||
|
|
||||||
|
b.ToTable("PowerOutageLogs", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GreenHome.Domain.ReportImage", b =>
|
modelBuilder.Entity("GreenHome.Domain.ReportImage", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -1174,6 +1222,17 @@ namespace GreenHome.Infrastructure.Migrations
|
|||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("GreenHome.Domain.PowerOutageLog", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("GreenHome.Domain.Device", "Device")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("DeviceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Device");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("GreenHome.Domain.ReportImage", b =>
|
modelBuilder.Entity("GreenHome.Domain.ReportImage", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("GreenHome.Domain.UserDailyReport", "UserDailyReport")
|
b.HasOne("GreenHome.Domain.UserDailyReport", "UserDailyReport")
|
||||||
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,38 +1,39 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,7 +43,7 @@ public sealed class AIQueryService : IAIQueryService
|
|||||||
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)
|
||||||
@@ -54,7 +55,7 @@ public sealed class AIQueryService : IAIQueryService
|
|||||||
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)
|
||||||
@@ -65,7 +66,7 @@ public sealed class AIQueryService : IAIQueryService
|
|||||||
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);
|
||||||
}
|
}
|
||||||
@@ -74,7 +75,7 @@ public sealed class AIQueryService : IAIQueryService
|
|||||||
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);
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,7 @@ public sealed class AIQueryService : IAIQueryService
|
|||||||
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)
|
||||||
@@ -95,7 +96,7 @@ public sealed class AIQueryService : IAIQueryService
|
|||||||
{
|
{
|
||||||
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,
|
||||||
@@ -123,4 +124,3 @@ public sealed class AIQueryService : IAIQueryService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,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.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,47 +20,32 @@ public sealed class AlertLogService : IAlertLogService
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user