feat: implement communications module with SMTP settings, email logging, and frontend UI

This commit is contained in:
2025-12-12 11:19:25 +01:00
parent dedd4f4e69
commit 9174e75be0
19 changed files with 727 additions and 9 deletions

View File

@@ -0,0 +1,112 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Zentral.API.Apps.Communications.Dtos;
using Zentral.Domain.Entities;
using Zentral.Domain.Interfaces;
using Zentral.Infrastructure.Data;
using System.Security.Claims;
namespace Zentral.API.Apps.Communications.Controllers;
[ApiController]
[Route("api/communications")]
public class CommunicationsController : ControllerBase
{
private readonly ZentralDbContext _context;
private readonly IEmailSender _emailSender;
public CommunicationsController(ZentralDbContext context, IEmailSender emailSender)
{
_context = context;
_emailSender = emailSender;
}
[HttpGet("config")]
public async Task<ActionResult<SmtpConfigDto>> GetConfig()
{
var configs = await _context.Configurazioni
.Where(c => c.Chiave.StartsWith("SMTP_"))
.ToDictionaryAsync(c => c.Chiave, c => c.Valore);
var dto = new SmtpConfigDto
{
Host = GetValue(configs, "SMTP_HOST"),
Port = int.Parse(GetValue(configs, "SMTP_PORT", "587")),
User = GetValue(configs, "SMTP_USER"),
Password = GetValue(configs, "SMTP_PASS"),
EnableSsl = bool.Parse(GetValue(configs, "SMTP_SSL", "false")),
FromEmail = GetValue(configs, "SMTP_FROM_EMAIL"),
FromName = GetValue(configs, "SMTP_FROM_NAME")
};
return Ok(dto);
}
[HttpPost("config")]
public async Task<ActionResult> SaveConfig(SmtpConfigDto dto)
{
await SetConfig("SMTP_HOST", dto.Host);
await SetConfig("SMTP_PORT", dto.Port.ToString());
await SetConfig("SMTP_USER", dto.User);
await SetConfig("SMTP_PASS", dto.Password);
await SetConfig("SMTP_SSL", dto.EnableSsl.ToString().ToLower());
await SetConfig("SMTP_FROM_EMAIL", dto.FromEmail);
await SetConfig("SMTP_FROM_NAME", dto.FromName);
await _context.SaveChangesAsync();
return Ok();
}
[HttpPost("send-test")]
public async Task<ActionResult> SendTestEmail(TestEmailDto dto)
{
try
{
await _emailSender.SendEmailAsync(dto.To, dto.Subject, dto.Body);
return Ok(new { message = "Email send process initiated. Check logs for status." });
}
catch (Exception ex)
{
return BadRequest(new { message = ex.Message });
}
}
[HttpGet("logs")]
public async Task<ActionResult<List<EmailLogDto>>> GetLogs([FromQuery] int limit = 50)
{
var logs = await _context.EmailLogs
.OrderByDescending(l => l.SentDate)
.Take(limit)
.Select(l => new EmailLogDto
{
Id = l.Id,
SentDate = l.SentDate,
Sender = l.Sender,
Recipient = l.Recipient,
Subject = l.Subject,
Status = l.Status,
ErrorMessage = l.ErrorMessage
})
.ToListAsync();
return Ok(logs);
}
private string GetValue(Dictionary<string, string?> dict, string key, string def = "")
{
return dict.ContainsKey(key) && dict[key] != null ? dict[key]! : def;
}
private async Task SetConfig(string key, string? value)
{
var config = await _context.Configurazioni.FirstOrDefaultAsync(c => c.Chiave == key);
if (config == null)
{
config = new Configurazione { Chiave = key, CreatedAt = DateTime.UtcNow, CreatedBy = User.FindFirstValue(ClaimTypes.Name) ?? "System" };
_context.Configurazioni.Add(config);
}
config.Valore = value;
config.UpdatedAt = DateTime.UtcNow;
config.UpdatedBy = User.FindFirstValue(ClaimTypes.Name) ?? "System";
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace Zentral.API.Apps.Communications.Dtos;
public class EmailLogDto
{
public int Id { get; set; }
public DateTime SentDate { get; set; }
public string Sender { get; set; } = string.Empty;
public string Recipient { get; set; } = string.Empty;
public string Subject { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string? ErrorMessage { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace Zentral.API.Apps.Communications.Dtos;
public class SmtpConfigDto
{
public string Host { get; set; } = string.Empty;
public int Port { get; set; } = 587;
public string User { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool EnableSsl { get; set; } = false;
public string FromEmail { get; set; } = string.Empty;
public string FromName { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,8 @@
namespace Zentral.API.Apps.Communications.Dtos;
public class TestEmailDto
{
public string To { get; set; } = string.Empty;
public string Subject { get; set; } = "Test Email from Zentral";
public string Body { get; set; } = "This is a test email sent from Zentral Communications Module.";
}

View File

@@ -6,7 +6,10 @@ using Zentral.API.Apps.Warehouse.Services;
using Zentral.API.Apps.Purchases.Services;
using Zentral.API.Apps.Sales.Services;
using Zentral.API.Apps.Production.Services;
using Zentral.API.Apps.Production.Services;
using Zentral.Infrastructure.Data;
using Zentral.Infrastructure.Services;
using Zentral.Domain.Interfaces;
using Microsoft.EntityFrameworkCore;
using System.Text.Json.Serialization;
@@ -28,6 +31,9 @@ builder.Services.AddScoped<AutoCodeService>();
builder.Services.AddScoped<CustomFieldService>();
builder.Services.AddSingleton<DataNotificationService>();
// Communications Module Services
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
// Warehouse Module Services
builder.Services.AddScoped<IWarehouseService, WarehouseService>();

View File

@@ -521,6 +521,20 @@ public class AppService
RoutePath = "/report-designer",
IsAvailable = true,
CreatedAt = DateTime.UtcNow
},
new App
{
Code = "communications",
Name = "Comunicazioni",
Description = "Gestione invio mail, chat interna e condivisione risorse",
Icon = "Email",
BasePrice = 1000m,
MonthlyMultiplier = 1.2m,
SortOrder = 90,
IsCore = false,
RoutePath = "/communications",
IsAvailable = true,
CreatedAt = DateTime.UtcNow
}
};