version 3

This commit is contained in:
2025-12-17 00:34:41 +03:30
parent 139924db94
commit 74e8480a68
38 changed files with 5399 additions and 70 deletions

View File

@@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Mvc;
using GreenHome.Application;
namespace GreenHome.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AlertLogsController : ControllerBase
{
private readonly IAlertLogService _alertLogService;
public AlertLogsController(IAlertLogService alertLogService)
{
_alertLogService = alertLogService;
}
/// <summary>
/// دریافت لیست لاگ‌های هشدار با فیلتر و صفحه‌بندی
/// </summary>
[HttpGet]
public async Task<ActionResult<PagedResult<AlertLogDto>>> GetAlertLogs(
[FromQuery] int? deviceId,
[FromQuery] int? userId,
[FromQuery] Domain.AlertType? alertType,
[FromQuery] Domain.AlertStatus? status,
[FromQuery] DateTime? startDate,
[FromQuery] DateTime? endDate,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken cancellationToken = default)
{
var filter = new AlertLogFilter
{
DeviceId = deviceId,
UserId = userId,
AlertType = alertType,
Status = status,
StartDate = startDate,
EndDate = endDate,
Page = page,
PageSize = pageSize
};
var result = await _alertLogService.GetAlertLogsAsync(filter, cancellationToken);
return Ok(result);
}
/// <summary>
/// دریافت جزئیات کامل یک لاگ هشدار
/// </summary>
[HttpGet("{id}")]
public async Task<ActionResult<AlertLogDto>> GetAlertLogById(int id, CancellationToken cancellationToken)
{
var result = await _alertLogService.GetAlertLogByIdAsync(id, cancellationToken);
if (result == null)
{
return NotFound(new { error = $"لاگ هشدار با شناسه {id} یافت نشد" });
}
return Ok(result);
}
}

View File

@@ -0,0 +1,98 @@
using Microsoft.AspNetCore.Mvc;
using GreenHome.Application;
namespace GreenHome.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class ChecklistsController : ControllerBase
{
private readonly IChecklistService _checklistService;
public ChecklistsController(IChecklistService checklistService)
{
_checklistService = checklistService;
}
/// <summary>
/// دریافت چک‌لیست فعال یک دستگاه
/// </summary>
[HttpGet("active/{deviceId}")]
public async Task<ActionResult<ChecklistDto>> GetActiveChecklist(int deviceId, CancellationToken cancellationToken)
{
var result = await _checklistService.GetActiveChecklistByDeviceIdAsync(deviceId, cancellationToken);
if (result == null)
{
return NotFound(new { error = "چک‌لیست فعالی برای این دستگاه یافت نشد" });
}
return Ok(result);
}
/// <summary>
/// دریافت تمام چک‌لیست‌های یک دستگاه
/// </summary>
[HttpGet("device/{deviceId}")]
public async Task<ActionResult<List<ChecklistDto>>> GetChecklists(int deviceId, CancellationToken cancellationToken)
{
var result = await _checklistService.GetChecklistsByDeviceIdAsync(deviceId, cancellationToken);
return Ok(result);
}
/// <summary>
/// دریافت جزئیات یک چک‌لیست
/// </summary>
[HttpGet("{id}")]
public async Task<ActionResult<ChecklistDto>> GetChecklistById(int id, CancellationToken cancellationToken)
{
var result = await _checklistService.GetChecklistByIdAsync(id, cancellationToken);
if (result == null)
{
return NotFound(new { error = $"چک‌لیست با شناسه {id} یافت نشد" });
}
return Ok(result);
}
/// <summary>
/// ایجاد چک‌لیست جدید (چک‌لیست قبلی غیرفعال می‌شود)
/// </summary>
[HttpPost]
public async Task<ActionResult<int>> CreateChecklist(
CreateChecklistRequest request,
CancellationToken cancellationToken)
{
var id = await _checklistService.CreateChecklistAsync(request, cancellationToken);
return Ok(new { id, message = "چک‌لیست با موفقیت ایجاد شد و چک‌لیست قبلی غیرفعال شد" });
}
/// <summary>
/// دریافت سابقه تکمیل‌های یک چک‌لیست
/// </summary>
[HttpGet("{checklistId}/completions")]
public async Task<ActionResult<List<ChecklistCompletionDto>>> GetCompletions(
int checklistId,
CancellationToken cancellationToken)
{
var result = await _checklistService.GetCompletionsByChecklistIdAsync(checklistId, cancellationToken);
return Ok(result);
}
/// <summary>
/// ثبت تکمیل چک‌لیست
/// </summary>
[HttpPost("complete")]
public async Task<ActionResult<int>> CompleteChecklist(
CompleteChecklistRequest request,
CancellationToken cancellationToken)
{
try
{
var id = await _checklistService.CompleteChecklistAsync(request, cancellationToken);
return Ok(new { id, message = "چک‌لیست با موفقیت تکمیل شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
}
}

View File

@@ -73,5 +73,71 @@ public class DailyReportController : ControllerBase
return StatusCode(500, new { error = "خطای سرور در پردازش درخواست" });
}
}
/// <summary>
/// دریافت تحلیل هفتگی
/// </summary>
[HttpGet("weekly")]
public async Task<ActionResult<DailyReportResponse>> GetWeeklyAnalysis(
[FromQuery] int deviceId,
[FromQuery] string startDate,
[FromQuery] string endDate,
CancellationToken cancellationToken)
{
try
{
var request = new WeeklyAnalysisRequest
{
DeviceId = deviceId,
StartDate = startDate.Trim(),
EndDate = endDate.Trim()
};
var result = await _dailyReportService.GetWeeklyAnalysisAsync(request, cancellationToken);
return Ok(result);
}
catch (InvalidOperationException ex)
{
return BadRequest(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting weekly analysis");
return StatusCode(500, new { error = "خطا در دریافت تحلیل هفتگی" });
}
}
/// <summary>
/// دریافت تحلیل ماهانه
/// </summary>
[HttpGet("monthly")]
public async Task<ActionResult<DailyReportResponse>> GetMonthlyAnalysis(
[FromQuery] int deviceId,
[FromQuery] int year,
[FromQuery] int month,
CancellationToken cancellationToken)
{
try
{
var request = new MonthlyAnalysisRequest
{
DeviceId = deviceId,
Year = year,
Month = month
};
var result = await _dailyReportService.GetMonthlyAnalysisAsync(request, cancellationToken);
return Ok(result);
}
catch (InvalidOperationException ex)
{
return BadRequest(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting monthly analysis");
return StatusCode(500, new { error = "خطا در دریافت تحلیل ماهانه" });
}
}
}

View File

@@ -0,0 +1,212 @@
using Microsoft.AspNetCore.Mvc;
using GreenHome.Application;
namespace GreenHome.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class DevicePostsController : ControllerBase
{
private readonly IDevicePostService _postService;
private readonly ILogger<DevicePostsController> _logger;
private readonly IWebHostEnvironment _environment;
public DevicePostsController(
IDevicePostService postService,
ILogger<DevicePostsController> logger,
IWebHostEnvironment environment)
{
_postService = postService;
_logger = logger;
_environment = environment;
}
/// <summary>
/// دریافت پست‌های گروه مجازی دستگاه (تایم‌لاین)
/// </summary>
[HttpGet]
public async Task<ActionResult<PagedResult<DevicePostDto>>> GetPosts(
[FromQuery] int deviceId,
[FromQuery] int? authorUserId,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken cancellationToken = default)
{
var filter = new DevicePostFilter
{
DeviceId = deviceId,
AuthorUserId = authorUserId,
Page = page,
PageSize = pageSize
};
var result = await _postService.GetPostsAsync(filter, cancellationToken);
return Ok(result);
}
/// <summary>
/// دریافت جزئیات یک پست
/// </summary>
[HttpGet("{id}")]
public async Task<ActionResult<DevicePostDto>> GetPostById(int id, CancellationToken cancellationToken)
{
var result = await _postService.GetPostByIdAsync(id, cancellationToken);
if (result == null)
{
return NotFound(new { error = $"پست با شناسه {id} یافت نشد" });
}
return Ok(result);
}
/// <summary>
/// ایجاد پست جدید در گروه مجازی
/// </summary>
[HttpPost]
public async Task<ActionResult<int>> CreatePost(
CreateDevicePostRequest request,
CancellationToken cancellationToken)
{
try
{
var id = await _postService.CreatePostAsync(request, cancellationToken);
return Ok(new { id, message = "پست با موفقیت ایجاد شد" });
}
catch (UnauthorizedAccessException ex)
{
return Unauthorized(new { error = ex.Message });
}
}
/// <summary>
/// ویرایش پست
/// </summary>
[HttpPut]
public async Task<ActionResult> UpdatePost(
UpdateDevicePostRequest request,
CancellationToken cancellationToken)
{
try
{
await _postService.UpdatePostAsync(request, cancellationToken);
return Ok(new { message = "پست با موفقیت ویرایش شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
}
/// <summary>
/// حذف پست
/// </summary>
[HttpDelete("{id}")]
public async Task<ActionResult> DeletePost(int id, CancellationToken cancellationToken)
{
try
{
await _postService.DeletePostAsync(id, cancellationToken);
return Ok(new { message = "پست با موفقیت حذف شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
}
/// <summary>
/// آپلود تصویر برای پست
/// </summary>
[HttpPost("{postId}/images")]
[Consumes("multipart/form-data")]
public async Task<ActionResult<int>> UploadImage(
int postId,
[FromForm] IFormFile file,
CancellationToken cancellationToken)
{
try
{
if (file == null || file.Length == 0)
{
return BadRequest(new { error = "فایل انتخاب نشده است" });
}
// Validate file type
var allowedTypes = new[] { "image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp" };
if (!allowedTypes.Contains(file.ContentType.ToLower()))
{
return BadRequest(new { error = "فقط فایل‌های تصویری مجاز هستند" });
}
// Validate file size (max 5MB)
if (file.Length > 5 * 1024 * 1024)
{
return BadRequest(new { error = "حجم فایل نباید بیشتر از 5 مگابایت باشد" });
}
// Create upload directory
var uploadsFolder = Path.Combine(_environment.WebRootPath ?? "wwwroot", "uploads", "posts");
Directory.CreateDirectory(uploadsFolder);
// Generate unique filename
var fileName = $"{Guid.NewGuid()}_{Path.GetFileName(file.FileName)}";
var filePath = Path.Combine(uploadsFolder, fileName);
// Save file
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream, cancellationToken);
}
// Save to database
var relativePath = $"/uploads/posts/{fileName}";
var imageId = await _postService.AddImageToPostAsync(
postId,
file.FileName,
relativePath,
file.ContentType,
file.Length,
cancellationToken);
_logger.LogInformation("Image uploaded for post {PostId}: {ImageId}", postId, imageId);
return Ok(new { imageId, filePath = relativePath, message = "تصویر با موفقیت آپلود شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error uploading image for post {PostId}", postId);
return StatusCode(500, new { error = "خطا در آپلود تصویر" });
}
}
/// <summary>
/// حذف تصویر از پست
/// </summary>
[HttpDelete("images/{imageId}")]
public async Task<ActionResult> DeleteImage(int imageId, CancellationToken cancellationToken)
{
try
{
await _postService.DeleteImageAsync(imageId, cancellationToken);
return Ok(new { message = "تصویر با موفقیت حذف شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
}
/// <summary>
/// بررسی دسترسی کاربر به دستگاه
/// </summary>
[HttpGet("access/{userId}/{deviceId}")]
public async Task<ActionResult<bool>> CheckAccess(int userId, int deviceId, CancellationToken cancellationToken)
{
var hasAccess = await _postService.CanUserAccessDeviceAsync(userId, deviceId, cancellationToken);
return Ok(new { hasAccess });
}
}

View File

@@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Mvc;
using GreenHome.Application;
namespace GreenHome.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class MonthlyReportController : ControllerBase
{
private readonly IMonthlyReportService _monthlyReportService;
private readonly ILogger<MonthlyReportController> _logger;
public MonthlyReportController(
IMonthlyReportService monthlyReportService,
ILogger<MonthlyReportController> logger)
{
_monthlyReportService = monthlyReportService;
_logger = logger;
}
/// <summary>
/// دریافت گزارش آماری ماهانه
/// </summary>
[HttpGet]
public async Task<ActionResult<MonthlyReportDto>> GetMonthlyReport(
[FromQuery] int deviceId,
[FromQuery] int year,
[FromQuery] int month,
CancellationToken cancellationToken)
{
try
{
if (deviceId <= 0)
{
return BadRequest(new { error = "شناسه دستگاه نامعتبر است" });
}
if (month < 1 || month > 12)
{
return BadRequest(new { error = "ماه باید بین 1 تا 12 باشد" });
}
var result = await _monthlyReportService.GetMonthlyReportAsync(deviceId, year, month, cancellationToken);
_logger.LogInformation(
"گزارش ماهانه برای دستگاه {DeviceId} و ماه {Month}/{Year} ایجاد شد",
deviceId, month, year);
return Ok(result);
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error generating monthly report");
return StatusCode(500, new { error = "خطا در ایجاد گزارش ماهانه" });
}
}
}

View File

@@ -0,0 +1,60 @@
using Microsoft.AspNetCore.Mvc;
using GreenHome.Application;
namespace GreenHome.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class PowerOutageController : ControllerBase
{
private readonly IAlertService _alertService;
private readonly ILogger<PowerOutageController> _logger;
public PowerOutageController(
IAlertService alertService,
ILogger<PowerOutageController> logger)
{
_alertService = alertService;
_logger = logger;
}
/// <summary>
/// ارسال هشدار قطع برق برای یک دستگاه
/// </summary>
/// <param name="deviceId">شناسه دستگاه</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>نتیجه عملیات</returns>
[HttpPost]
public async Task<ActionResult> SendPowerOutageAlert(
[FromQuery] int deviceId,
CancellationToken cancellationToken)
{
try
{
if (deviceId <= 0)
{
return BadRequest(new { error = "شناسه دستگاه نامعتبر است" });
}
await _alertService.SendPowerOutageAlertAsync(deviceId, cancellationToken);
_logger.LogInformation("Power outage alert processed for device {DeviceId}", deviceId);
return Ok(new {
success = true,
message = "هشدار قطع برق با موفقیت ارسال شد"
});
}
catch (InvalidOperationException ex)
{
_logger.LogWarning(ex, "Invalid operation for power outage alert: DeviceId={DeviceId}", deviceId);
return BadRequest(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending power outage alert: DeviceId={DeviceId}", deviceId);
return StatusCode(500, new { error = "خطا در ارسال هشدار قطع برق" });
}
}
}

View File

@@ -0,0 +1,210 @@
using Microsoft.AspNetCore.Mvc;
using GreenHome.Application;
namespace GreenHome.Api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class UserDailyReportsController : ControllerBase
{
private readonly IUserDailyReportService _reportService;
private readonly ILogger<UserDailyReportsController> _logger;
private readonly IWebHostEnvironment _environment;
public UserDailyReportsController(
IUserDailyReportService reportService,
ILogger<UserDailyReportsController> logger,
IWebHostEnvironment environment)
{
_reportService = reportService;
_logger = logger;
_environment = environment;
}
/// <summary>
/// دریافت لیست گزارش‌های روزانه کاربران با فیلتر
/// </summary>
[HttpGet]
public async Task<ActionResult<PagedResult<UserDailyReportDto>>> GetReports(
[FromQuery] int? deviceId,
[FromQuery] int? userId,
[FromQuery] string? persianDate,
[FromQuery] int? year,
[FromQuery] int? month,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken cancellationToken = default)
{
var filter = new UserDailyReportFilter
{
DeviceId = deviceId,
UserId = userId,
PersianDate = persianDate,
Year = year,
Month = month,
Page = page,
PageSize = pageSize
};
var result = await _reportService.GetReportsAsync(filter, cancellationToken);
return Ok(result);
}
/// <summary>
/// دریافت جزئیات یک گزارش روزانه
/// </summary>
[HttpGet("{id}")]
public async Task<ActionResult<UserDailyReportDto>> GetReportById(int id, CancellationToken cancellationToken)
{
var result = await _reportService.GetReportByIdAsync(id, cancellationToken);
if (result == null)
{
return NotFound(new { error = $"گزارش با شناسه {id} یافت نشد" });
}
return Ok(result);
}
/// <summary>
/// ایجاد گزارش روزانه جدید
/// </summary>
[HttpPost]
public async Task<ActionResult<int>> CreateReport(
CreateUserDailyReportRequest request,
CancellationToken cancellationToken)
{
try
{
var id = await _reportService.CreateReportAsync(request, cancellationToken);
return Ok(new { id, message = "گزارش با موفقیت ایجاد شد" });
}
catch (ArgumentException ex)
{
return BadRequest(new { error = ex.Message });
}
}
/// <summary>
/// ویرایش گزارش روزانه
/// </summary>
[HttpPut]
public async Task<ActionResult> UpdateReport(
UpdateUserDailyReportRequest request,
CancellationToken cancellationToken)
{
try
{
await _reportService.UpdateReportAsync(request, cancellationToken);
return Ok(new { message = "گزارش با موفقیت ویرایش شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
}
/// <summary>
/// حذف گزارش روزانه
/// </summary>
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteReport(int id, CancellationToken cancellationToken)
{
try
{
await _reportService.DeleteReportAsync(id, cancellationToken);
return Ok(new { message = "گزارش با موفقیت حذف شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
}
/// <summary>
/// آپلود تصویر برای گزارش روزانه
/// </summary>
[HttpPost("{reportId}/images")]
[Consumes("multipart/form-data")]
public async Task<ActionResult<int>> UploadImage(
int reportId,
[FromForm] IFormFile file,
[FromForm] string? description,
CancellationToken cancellationToken)
{
try
{
if (file == null || file.Length == 0)
{
return BadRequest(new { error = "فایل انتخاب نشده است" });
}
// Validate file type
var allowedTypes = new[] { "image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp" };
if (!allowedTypes.Contains(file.ContentType.ToLower()))
{
return BadRequest(new { error = "فقط فایل‌های تصویری مجاز هستند" });
}
// Validate file size (max 5MB)
if (file.Length > 5 * 1024 * 1024)
{
return BadRequest(new { error = "حجم فایل نباید بیشتر از 5 مگابایت باشد" });
}
// Create upload directory
var uploadsFolder = Path.Combine(_environment.WebRootPath ?? "wwwroot", "uploads", "reports");
Directory.CreateDirectory(uploadsFolder);
// Generate unique filename
var fileName = $"{Guid.NewGuid()}_{Path.GetFileName(file.FileName)}";
var filePath = Path.Combine(uploadsFolder, fileName);
// Save file
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream, cancellationToken);
}
// Save to database
var relativePath = $"/uploads/reports/{fileName}";
var imageId = await _reportService.AddImageToReportAsync(
reportId,
file.FileName,
relativePath,
file.ContentType,
file.Length,
description,
cancellationToken);
_logger.LogInformation("Image uploaded for report {ReportId}: {ImageId}", reportId, imageId);
return Ok(new { imageId, filePath = relativePath, message = "تصویر با موفقیت آپلود شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error uploading image for report {ReportId}", reportId);
return StatusCode(500, new { error = "خطا در آپلود تصویر" });
}
}
/// <summary>
/// حذف تصویر از گزارش
/// </summary>
[HttpDelete("images/{imageId}")]
public async Task<ActionResult> DeleteImage(int imageId, CancellationToken cancellationToken)
{
try
{
await _reportService.DeleteImageAsync(imageId, cancellationToken);
return Ok(new { message = "تصویر با موفقیت حذف شد" });
}
catch (InvalidOperationException ex)
{
return NotFound(new { error = ex.Message });
}
}
}

View File

@@ -13,7 +13,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.9" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Scalar.AspNetCore" Version="2.11.6" />
</ItemGroup>
<ItemGroup>

View File

@@ -5,12 +5,13 @@ using GreenHome.Infrastructure;
using GreenHome.Sms.Ippanel;
using GreenHome.VoiceCall.Avanak;
using Microsoft.EntityFrameworkCore;
using Scalar.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddOpenApi();
// Application/Infrastructure DI
builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile));
@@ -60,6 +61,11 @@ builder.Services.AddScoped<GreenHome.Application.IAlertConditionService, GreenHo
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>();
// SMS Service Configuration
builder.Services.AddIppanelSms(builder.Configuration);
@@ -89,11 +95,8 @@ using (var scope = app.Services.CreateScope())
}
// Configure the HTTP request pipeline.
//if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.MapOpenApi();
app.MapScalarApiReference();
// HTTPS Redirection فقط در Production
if (!app.Environment.IsDevelopment())