refactor: reorganize Application layer into folder structure

This commit is contained in:
2026-06-01 18:15:17 +03:30
parent dca3fc62ca
commit 775ae0d9b4
50 changed files with 618 additions and 492 deletions

View File

@@ -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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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;
}

View File

@@ -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; }
}

View 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; }
}

View File

@@ -0,0 +1,6 @@
namespace GreenHome.Application;
public sealed class SendCodeRequest
{
public required string Mobile { get; set; }
}

View 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;
}

View 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; }
}

View File

@@ -0,0 +1,7 @@
namespace GreenHome.Application;
public sealed class VerifyCodeRequest
{
public required string Mobile { get; set; }
public required string Code { get; set; }
}

View File

@@ -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; }
}

View File

@@ -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; }
} }

View 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; }
}

View 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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View 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;
}

View 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;
}

View 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; }
}

View File

@@ -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;
} }

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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; }
} }

View File

@@ -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;
}

View File

@@ -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; }
}

View File

@@ -0,0 +1,7 @@
namespace GreenHome.Application;
public sealed class DayCount
{
public string PersianDate { get; set; } = string.Empty;
public int Count { get; set; }
}

View 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; }
}

View 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;
}

View 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; }
}

View File

@@ -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;
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -14,4 +14,3 @@ public interface IDailyReportService
Task<DailyReportResponse> GetMonthlyAnalysisAsync(MonthlyAnalysisRequest request, CancellationToken cancellationToken); Task<DailyReportResponse> GetMonthlyAnalysisAsync(MonthlyAnalysisRequest request, CancellationToken cancellationToken);
} }

View File

@@ -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);
} }

View File

@@ -1,11 +1,10 @@
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); Task<IReadOnlyList<DeviceDto>> ListAsync(CancellationToken cancellationToken);
Task<IReadOnlyList<DeviceDto>> ListAsync(CancellationToken cancellationToken); Task<DeviceDto> GetDeviceId(string deviceName, CancellationToken cancellationToken);
Task<DeviceDto> GetDeviceId(string deviceName, CancellationToken cancellationToken); Task<IReadOnlyList<DeviceDto>> GetUserDevicesAsync(int userId, CancellationToken cancellationToken);
Task<IReadOnlyList<DeviceDto>> GetUserDevicesAsync(int userId, CancellationToken cancellationToken); Task<PagedResult<DeviceDto>> GetDevicesAsync(DeviceFilter filter, CancellationToken cancellationToken);
Task<PagedResult<DeviceDto>> GetDevicesAsync(DeviceFilter filter, CancellationToken cancellationToken); }
}

View File

@@ -1,8 +1,8 @@
namespace GreenHome.Application; namespace GreenHome.Application;
public interface IDeviceSettingsService public interface IDeviceSettingsService
{ {
Task<DeviceSettingsDto?> GetByDeviceIdAsync(int deviceId, CancellationToken cancellationToken); Task<DeviceSettingsDto?> GetByDeviceIdAsync(int deviceId, CancellationToken cancellationToken);
Task<int> CreateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken); Task<int> CreateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken);
Task UpdateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken); Task UpdateAsync(DeviceSettingsDto dto, CancellationToken cancellationToken);
} }

View File

@@ -20,4 +20,3 @@ public interface IDeviceTokenService
/// </summary> /// </summary>
Task<VerifyDeviceTokenResponse> VerifyDeviceTokenAsync(VerifyDeviceTokenRequest request, CancellationToken cancellationToken); Task<VerifyDeviceTokenResponse> VerifyDeviceTokenAsync(VerifyDeviceTokenRequest request, CancellationToken cancellationToken);
} }

View File

@@ -0,0 +1,6 @@
namespace GreenHome.Application;
public interface IMonthlyReportService
{
Task<MonthlyReportDto> GetMonthlyReportAsync(int deviceId, int year, int month, CancellationToken cancellationToken);
}

View File

@@ -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);
}

View File

@@ -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);
} }

View File

@@ -1,10 +1,10 @@
namespace GreenHome.Application; namespace GreenHome.Application;
public interface ITelemetryService public interface ITelemetryService
{ {
Task<int> AddAsync(TelemetryDto dto, CancellationToken cancellationToken); Task<int> AddAsync(TelemetryDto dto, CancellationToken cancellationToken);
Task<PagedResult<TelemetryDto>> ListAsync(TelemetryFilter filter, CancellationToken cancellationToken); Task<PagedResult<TelemetryDto>> ListAsync(TelemetryFilter filter, CancellationToken cancellationToken);
Task<TelemetryMinMax> GetMinMaxAsync(int deviceId, DateTime? startUtc, DateTime? endUtc, CancellationToken cancellationToken); Task<TelemetryMinMax> GetMinMaxAsync(int deviceId, DateTime? startUtc, DateTime? endUtc, CancellationToken cancellationToken);
Task<IReadOnlyList<DayCount>> GetMonthDaysAsync(int deviceId, int persianYear, int persianMonth, CancellationToken cancellationToken); Task<IReadOnlyList<DayCount>> GetMonthDaysAsync(int deviceId, int persianYear, int persianMonth, CancellationToken cancellationToken);
Task<IReadOnlyList<int>> GetActiveMonthsAsync(int deviceId, int persianYear, CancellationToken cancellationToken); Task<IReadOnlyList<int>> GetActiveMonthsAsync(int deviceId, int persianYear, CancellationToken cancellationToken);
} }

View File

@@ -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);
} }

View File

@@ -1,14 +1,13 @@
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() {
{ RuleFor(x => x.DeviceName).NotEmpty().MaximumLength(10);
RuleFor(x => x.DeviceName).NotEmpty().MaximumLength(10); RuleFor(x => x.Location).MaximumLength(250);
RuleFor(x => x.Location).MaximumLength(250); RuleFor(x => x.NeshanLocation).MaximumLength(80);
RuleFor(x => x.NeshanLocation).MaximumLength(80); }
} }
}