diff --git a/.gitignore b/.gitignore index c74956f..f43d12b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ riderModule.iml Publish/ app.db -FoodsharingSiegen.Server/wwwroot/buildinfo.txt \ No newline at end of file +FoodsharingSiegen.Server/wwwroot/buildinfo.txt +FoodsharingSiegen.Server/config/appsettings.json \ No newline at end of file diff --git a/Docker/.env b/Docker/.env new file mode 100644 index 0000000..68370e6 --- /dev/null +++ b/Docker/.env @@ -0,0 +1,7 @@ +MAIL_HOST=smtp.example.com +MAIL_PORT=587 +MAIL_USERNAME=your_username +MAIL_PASSWORD=your_password +MAIL_FROM_ADDRESS=noreply@example.com +MAIL_FROM_NAME="FS Einarbeitungen Musterhausen" +MAIL_USE_SSL=true diff --git a/Docker/docker-compose.yml b/Docker/docker-compose.yml index f013431..281591e 100644 --- a/Docker/docker-compose.yml +++ b/Docker/docker-compose.yml @@ -5,6 +5,17 @@ services: image: ghcr.io/troogs/fs-onboarding/server:latest ports: - "8100:56000" + env_file: + - .env + environment: + # Mail Configuration Examples. These override options in appsettings.json. + # All values below can also be set via the active .env file or appsettings.json. + - Settings__Mail__Host=${MAIL_HOST:-smtp.example.com} + - Settings__Mail__Port=${MAIL_PORT:-587} + - Settings__Mail__Username=${MAIL_USERNAME:-your_username} + - Settings__Mail__Password=${MAIL_PASSWORD:-your_password} + - Settings__Mail__FromAddress=${MAIL_FROM_ADDRESS:-no-reply@example.com} + - Settings__Mail__UseSsl=${MAIL_USE_SSL:-true} volumes: - /docker/data/fs-onboarding/config:/app/config/ - /docker/data/fs-onboarding/data:/app/data/ \ No newline at end of file diff --git a/FoodsharingSiegen.Contracts/Model/AppSettings.cs b/FoodsharingSiegen.Contracts/Model/AppSettings.cs index 30bb9d4..41a2b1d 100644 --- a/FoodsharingSiegen.Contracts/Model/AppSettings.cs +++ b/FoodsharingSiegen.Contracts/Model/AppSettings.cs @@ -6,6 +6,11 @@ public bool DisableStepIn { get; set; } + /// + /// Gets or sets the mail server settings. + /// + public MailSettings Mail { get; set; } = new(); + public TermSettings Terms { get; set; } = new(); public bool TestMode { get; set; } diff --git a/FoodsharingSiegen.Contracts/Model/MailSettings.cs b/FoodsharingSiegen.Contracts/Model/MailSettings.cs new file mode 100644 index 0000000..85905fd --- /dev/null +++ b/FoodsharingSiegen.Contracts/Model/MailSettings.cs @@ -0,0 +1,38 @@ +namespace FoodsharingSiegen.Contracts.Model +{ + /// + /// Configuration settings for the mail service. + /// + public class MailSettings + { + /// + /// Gets or sets the SMTP server host. + /// + public string Host { get; set; } = string.Empty; + + /// + /// Gets or sets the SMTP port. + /// + public int Port { get; set; } = 587; + + /// + /// Gets or sets the username for SMTP authentication. + /// + public string Username { get; set; } = string.Empty; + + /// + /// Gets or sets the password for SMTP authentication. + /// + public string Password { get; set; } = string.Empty; + + /// + /// Gets or sets the sender address. + /// + public string FromAddress { get; set; } = string.Empty; + + /// + /// Gets or sets whether to use SSL/TLS connection. + /// + public bool UseSsl { get; set; } = true; + } +} diff --git a/FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj b/FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj index a4f6944..f53af19 100644 --- a/FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj +++ b/FoodsharingSiegen.Server/FoodsharingSiegen.Server.csproj @@ -29,6 +29,7 @@ + all diff --git a/FoodsharingSiegen.Server/Pages/Settings.razor b/FoodsharingSiegen.Server/Pages/Settings.razor new file mode 100644 index 0000000..e32213e --- /dev/null +++ b/FoodsharingSiegen.Server/Pages/Settings.razor @@ -0,0 +1,30 @@ +@page "/settings" + +@inherits FsBase + +Einstellungen - @AppSettings.Terms.Title + + + Einstellungen + + + Mail Service + + + SMTP Server: @AppSettings.Mail.Host + Port: @AppSettings.Mail.Port + SSL: @(AppSettings.Mail.UseSsl ? "Ja" : "Nein") + + + + + Testmail-Empfänger + + + + + + Testmail senden + + + \ No newline at end of file diff --git a/FoodsharingSiegen.Server/Pages/Settings.razor.cs b/FoodsharingSiegen.Server/Pages/Settings.razor.cs new file mode 100644 index 0000000..f765912 --- /dev/null +++ b/FoodsharingSiegen.Server/Pages/Settings.razor.cs @@ -0,0 +1,45 @@ +using FoodsharingSiegen.Contracts.Helper; +using FoodsharingSiegen.Server.Service; +using Microsoft.AspNetCore.Components; +using System; +using System.Threading.Tasks; + +namespace FoodsharingSiegen.Server.Pages +{ + public partial class Settings + { + [Inject] + public IMailService MailService { get; set; } = null!; + + private string TestMailReceiver { get; set; } = string.Empty; + + protected override async Task InitializeDataAsync() + { + if (!CurrentUser.IsAdmin()) + { + NavigationManager.NavigateTo("/"); + return; + } + await Task.CompletedTask; + } + + private async Task SendTestMail() + { + if (string.IsNullOrWhiteSpace(TestMailReceiver)) + { + await Notification.Error("Bitte eine Empfänger-Adresse angeben."); + return; + } + + try + { + await MailService.SendEmailAsync(TestMailReceiver, "Testmail", "Dies ist eine Testmail."); + await Notification.Success("Testmail versendet."); + } + catch (Exception ex) + { + await Notification.Error($"Fehler beim Senden: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/FoodsharingSiegen.Server/Program.cs b/FoodsharingSiegen.Server/Program.cs index 4176bc2..7cd582b 100644 --- a/FoodsharingSiegen.Server/Program.cs +++ b/FoodsharingSiegen.Server/Program.cs @@ -29,6 +29,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services .AddBlazorise() diff --git a/FoodsharingSiegen.Server/Service/IMailService.cs b/FoodsharingSiegen.Server/Service/IMailService.cs new file mode 100644 index 0000000..c41192a --- /dev/null +++ b/FoodsharingSiegen.Server/Service/IMailService.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; + +namespace FoodsharingSiegen.Server.Service +{ + /// + /// Service interface for sending emails. + /// + public interface IMailService + { + /// + /// Sends an email asynchronously. + /// + /// The recipient's email address. + /// The subject of the email. + /// The HTML content of the email body. + /// A task that represents the asynchronous email sending operation. + Task SendEmailAsync(string toEmail, string subject, string htmlBody); + } +} diff --git a/FoodsharingSiegen.Server/Service/MailService.cs b/FoodsharingSiegen.Server/Service/MailService.cs new file mode 100644 index 0000000..9bf62e9 --- /dev/null +++ b/FoodsharingSiegen.Server/Service/MailService.cs @@ -0,0 +1,57 @@ +using System.Threading.Tasks; +using FoodsharingSiegen.Contracts.Model; +using MailKit.Net.Smtp; +using MailKit.Security; +using Microsoft.Extensions.Options; +using MimeKit; +using MimeKit.Text; + +namespace FoodsharingSiegen.Server.Service +{ + /// + /// Default implementation of which sends emails via SMTP using MailKit. + /// + public class MailService : IMailService + { + private readonly MailSettings _mailSettings; + private readonly TermSettings _termSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The configured application settings injected by DI, containing the . + public MailService(IOptions appSettings) + { + _mailSettings = appSettings.Value.Mail; + _termSettings = appSettings.Value.Terms; + } + + /// + public async Task SendEmailAsync(string toEmail, string subject, string htmlBody) + { + var email = new MimeMessage(); + email.From.Add(new MailboxAddress(_termSettings.Title, _mailSettings.FromAddress)); + email.To.Add(MailboxAddress.Parse(toEmail)); + email.Subject = subject; + + var textPart = new TextPart(TextFormat.Html) + { + Text = htmlBody + }; + email.Body = textPart; + + using var smtp = new SmtpClient(); + var secureOptions = _mailSettings.UseSsl ? SecureSocketOptions.StartTls : SecureSocketOptions.Auto; + + await smtp.ConnectAsync(_mailSettings.Host, _mailSettings.Port, secureOptions); + + if (!string.IsNullOrWhiteSpace(_mailSettings.Username)) + { + await smtp.AuthenticateAsync(_mailSettings.Username, _mailSettings.Password); + } + + await smtp.SendAsync(email); + await smtp.DisconnectAsync(true); + } + } +} diff --git a/FoodsharingSiegen.Server/Shared/NavMenu.razor b/FoodsharingSiegen.Server/Shared/NavMenu.razor index 532aae1..9e1adc0 100644 --- a/FoodsharingSiegen.Server/Shared/NavMenu.razor +++ b/FoodsharingSiegen.Server/Shared/NavMenu.razor @@ -49,13 +49,21 @@ @if (CurrentUser.IsAdmin()) { - + Benutzer + + + + + Einstellungen + + + } diff --git a/FoodsharingSiegen.Server/config/appsettings.json b/FoodsharingSiegen.Server/config/appsettings.example.json similarity index 51% rename from FoodsharingSiegen.Server/config/appsettings.json rename to FoodsharingSiegen.Server/config/appsettings.example.json index 8b02548..5a6af63 100644 --- a/FoodsharingSiegen.Server/config/appsettings.json +++ b/FoodsharingSiegen.Server/config/appsettings.example.json @@ -1,4 +1,4 @@ -{ +{ "Kestrel": { "Endpoints": { "Http": { @@ -9,10 +9,18 @@ "DetailedErrors": true, "Settings": { "TestMode": true, + "Mail": { + "Host": "mail.example.com", + "Port": 587, + "Username": "your_username", + "Password": "your_password", + "FromAddress": "no-reply@example.com", + "UseSsl": true + }, "Terms": { "Title": "Foodsharing Musterhausen", "TitleShort": "Musterhausen", - "StepInName": "Krabbelgruppe2" + "StepInName": "Neulingstreffen" } } } \ No newline at end of file
+ SMTP Server: @AppSettings.Mail.Host + Port: @AppSettings.Mail.Port + SSL: @(AppSettings.Mail.UseSsl ? "Ja" : "Nein") +