Merge pull request #225 from Moonlight-Panel/NewMailSystem
Added new mail system
This commit is contained in:
9
Moonlight/App/Models/Misc/MailTemplate.cs
Normal file
9
Moonlight/App/Models/Misc/MailTemplate.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Moonlight.App.Helpers.Files;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public class MailTemplate // This is just for the blazor table at /admin/system/mail
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public FileData File { get; set; }
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Mail;
|
namespace Moonlight.App.Services.Mail;
|
||||||
@@ -14,8 +15,14 @@ public class MailService
|
|||||||
private readonly int Port;
|
private readonly int Port;
|
||||||
private readonly bool Ssl;
|
private readonly bool Ssl;
|
||||||
|
|
||||||
public MailService(ConfigService configService)
|
private readonly Repository<User> UserRepository;
|
||||||
|
|
||||||
|
public MailService(
|
||||||
|
ConfigService configService,
|
||||||
|
Repository<User> userRepository)
|
||||||
{
|
{
|
||||||
|
UserRepository = userRepository;
|
||||||
|
|
||||||
var mailConfig = configService
|
var mailConfig = configService
|
||||||
.Get()
|
.Get()
|
||||||
.Moonlight.Mail;
|
.Moonlight.Mail;
|
||||||
@@ -27,28 +34,8 @@ public class MailService
|
|||||||
Ssl = mailConfig.Ssl;
|
Ssl = mailConfig.Ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendMail(
|
public Task SendMailRaw(User user, string html)
|
||||||
User user,
|
|
||||||
string name,
|
|
||||||
Action<Dictionary<string, string>> values
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{name}.html")))
|
|
||||||
{
|
|
||||||
Logger.Warn($"Mail template '{name}' not found. Make sure to place one in the resources folder");
|
|
||||||
throw new DisplayException("Mail template not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{name}.html"));
|
|
||||||
|
|
||||||
var val = new Dictionary<string, string>();
|
|
||||||
values.Invoke(val);
|
|
||||||
|
|
||||||
val.Add("FirstName", user.FirstName);
|
|
||||||
val.Add("LastName", user.LastName);
|
|
||||||
|
|
||||||
var parsed = ParseMail(rawHtml, val);
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -62,17 +49,15 @@ public class MailService
|
|||||||
|
|
||||||
var body = new BodyBuilder
|
var body = new BodyBuilder
|
||||||
{
|
{
|
||||||
HtmlBody = parsed
|
HtmlBody = html
|
||||||
};
|
};
|
||||||
mailMessage.Body = body.ToMessageBody();
|
mailMessage.Body = body.ToMessageBody();
|
||||||
|
|
||||||
using (var smtpClient = new SmtpClient())
|
using var smtpClient = new SmtpClient();
|
||||||
{
|
await smtpClient.ConnectAsync(Server, Port, Ssl);
|
||||||
await smtpClient.ConnectAsync(Server, Port, Ssl);
|
await smtpClient.AuthenticateAsync(Email, Password);
|
||||||
await smtpClient.AuthenticateAsync(Email, Password);
|
await smtpClient.SendAsync(mailMessage);
|
||||||
await smtpClient.SendAsync(mailMessage);
|
await smtpClient.DisconnectAsync(true);
|
||||||
await smtpClient.DisconnectAsync(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -80,6 +65,54 @@ public class MailService
|
|||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendMail(User user, string template, Action<Dictionary<string, string>> values)
|
||||||
|
{
|
||||||
|
if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{template}.html")))
|
||||||
|
{
|
||||||
|
Logger.Warn($"Mail template '{template}' not found. Make sure to place one in the resources folder");
|
||||||
|
throw new DisplayException("Mail template not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{template}.html"));
|
||||||
|
|
||||||
|
var val = new Dictionary<string, string>();
|
||||||
|
values.Invoke(val);
|
||||||
|
|
||||||
|
val.Add("FirstName", user.FirstName);
|
||||||
|
val.Add("LastName", user.LastName);
|
||||||
|
|
||||||
|
var parsed = ParseMail(rawHtml, val);
|
||||||
|
|
||||||
|
await SendMailRaw(user, parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendEmailToAll(string template, Action<Dictionary<string, string>> values)
|
||||||
|
{
|
||||||
|
var users = UserRepository
|
||||||
|
.Get()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
await SendMail(user, template, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendEmailToAllAdmins(string template, Action<Dictionary<string, string>> values)
|
||||||
|
{
|
||||||
|
var users = UserRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Admin)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
await SendMail(user, template, values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ParseMail(string html, Dictionary<string, string> values)
|
private string ParseMail(string html, Dictionary<string, string> values)
|
||||||
|
|||||||
@@ -44,6 +44,11 @@
|
|||||||
<TL>Configuration</TL>
|
<TL>Configuration</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 9 ? "active" : "")" href="/admin/system/mail">
|
||||||
|
<TL>Mail</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
180
Moonlight/Shared/Views/Admin/Sys/Mail.razor
Normal file
180
Moonlight/Shared/Views/Admin/Sys/Mail.razor
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
@page "/admin/system/mail"
|
||||||
|
|
||||||
|
@using Moonlight.Shared.Components.Navigations
|
||||||
|
@using Moonlight.Shared.Components.FileManagerPartials
|
||||||
|
@using Moonlight.App.Helpers.Files
|
||||||
|
@using Moonlight.App.Helpers
|
||||||
|
@using BlazorTable
|
||||||
|
@using Moonlight.App.Database.Entities
|
||||||
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
@using Moonlight.App.Services.Interop
|
||||||
|
@using Moonlight.App.Services.Mail
|
||||||
|
|
||||||
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
@inject ToastService ToastService
|
||||||
|
@inject AlertService AlertService
|
||||||
|
@inject MailService MailService
|
||||||
|
|
||||||
|
<OnlyAdmin>
|
||||||
|
<AdminSystemNavigation Index="9"/>
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<TL>Actions</TL>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Test mail configuration"))"
|
||||||
|
WorkingText="@(SmartTranslateService.Translate("Sending test mail"))"
|
||||||
|
CssClasses="btn-primary"
|
||||||
|
OnClick="SendTestMail">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
|
@if (CurrentMailTemplate == null)
|
||||||
|
{
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span class="card-title">
|
||||||
|
<TL>Mail templates</TL>
|
||||||
|
</span>
|
||||||
|
<div class="card-toolbar">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("New mail template"))"
|
||||||
|
CssClasses="btn-sm btn-success"
|
||||||
|
OnClick="CreateNewMailTemplate">
|
||||||
|
</WButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<Table TableItem="MailTemplate" Items="MailTemplateFiles" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||||
|
<Column TableItem="MailTemplate" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||||
|
<Template>
|
||||||
|
@{
|
||||||
|
var name = context.Name.Replace(Path.GetExtension(context.Name), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
<span>@(name)</span>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Column TableItem="MailTemplate" Title="" Field="@(x => x.Name)" Filterable="false" Sortable="false">
|
||||||
|
<Template>
|
||||||
|
<div class="text-end">
|
||||||
|
<WButton Text="@(SmartTranslateService.Translate("Edit"))"
|
||||||
|
OnClick="() => EditTemplate(context)">
|
||||||
|
</WButton>
|
||||||
|
<DeleteButton OnClick="() => DeleteTemplate(context)"
|
||||||
|
Confirm="true">
|
||||||
|
</DeleteButton>
|
||||||
|
</div>
|
||||||
|
</Template>
|
||||||
|
</Column>
|
||||||
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<FileEditor Language="html"
|
||||||
|
HideControls="false"
|
||||||
|
InitialData="@(CurrentMailTemplateContent)"
|
||||||
|
OnCancel="OnCancelTemplateEdit"
|
||||||
|
OnSubmit="OnSubmitTemplateEdit"/>
|
||||||
|
}
|
||||||
|
</LazyLoader>
|
||||||
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[CascadingParameter]
|
||||||
|
public User User { get; set; }
|
||||||
|
|
||||||
|
private MailTemplate[] MailTemplateFiles;
|
||||||
|
private FileAccess FileAccess;
|
||||||
|
private LazyLoader LazyLoader;
|
||||||
|
|
||||||
|
#region Template Editor
|
||||||
|
|
||||||
|
private MailTemplate? CurrentMailTemplate;
|
||||||
|
private string CurrentMailTemplateContent = "";
|
||||||
|
|
||||||
|
private async Task Load(LazyLoader arg)
|
||||||
|
{
|
||||||
|
FileAccess = new HostFileAccess(PathBuilder.Dir("storage"));
|
||||||
|
|
||||||
|
await FileAccess.Cd("resources");
|
||||||
|
await FileAccess.Cd("mail");
|
||||||
|
|
||||||
|
MailTemplateFiles = (await FileAccess.Ls())
|
||||||
|
.Where(x => x.IsFile)
|
||||||
|
.Select(x => new MailTemplate()
|
||||||
|
{
|
||||||
|
Name = x.Name,
|
||||||
|
File = x
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EditTemplate(MailTemplate mailTemplate)
|
||||||
|
{
|
||||||
|
CurrentMailTemplate = mailTemplate;
|
||||||
|
|
||||||
|
CurrentMailTemplateContent = await FileAccess
|
||||||
|
.Read(CurrentMailTemplate.File);
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteTemplate(MailTemplate mailTemplate)
|
||||||
|
{
|
||||||
|
await FileAccess.Delete(mailTemplate.File);
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnCancelTemplateEdit()
|
||||||
|
{
|
||||||
|
CurrentMailTemplate = null;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSubmitTemplateEdit(string text)
|
||||||
|
{
|
||||||
|
await FileAccess.Write(CurrentMailTemplate!.File, text);
|
||||||
|
|
||||||
|
await ToastService.Success(
|
||||||
|
SmartTranslateService.Translate("Successfully saved file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateNewMailTemplate()
|
||||||
|
{
|
||||||
|
var name = await AlertService.Text(
|
||||||
|
SmartTranslateService.Translate("New mail template"),
|
||||||
|
SmartTranslateService.Translate("Enter the name of the new template"),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await FileAccess.Write(new()
|
||||||
|
{
|
||||||
|
Name = name + ".html"
|
||||||
|
}, "");
|
||||||
|
|
||||||
|
await LazyLoader.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private async Task SendTestMail()
|
||||||
|
{
|
||||||
|
await MailService.SendMailRaw(User, "<html><body>If you see this mail, your moonlight mail configuration is ready to use</body></html>");
|
||||||
|
await AlertService.Info(SmartTranslateService.Translate("A test mail has been sent to the email address of your account"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user