using JobTrackerApi.Data; using JobTrackerApi.Models; using Microsoft.EntityFrameworkCore; namespace JobTrackerApi.Services; public sealed record EmailSettingsSnapshot( bool Enabled, string Host, int Port, string User, string Password, string From, string FromName, bool EnableSsl, int TimeoutMs, bool UsesOverrides, bool HasPassword); public sealed record EmailSettingsAdminDto( bool Enabled, string Host, int Port, string User, string From, string FromName, bool EnableSsl, int TimeoutMs, bool UsesOverrides, bool HasPassword); public sealed record UpdateEmailSettingsRequest( bool Enabled, string? Host, int Port, string? User, string? Password, bool ClearPassword, string? From, string? FromName, bool EnableSsl, int TimeoutMs); public interface IEmailSettingsResolver { Task GetSnapshotAsync(CancellationToken cancellationToken = default); Task GetAdminDtoAsync(CancellationToken cancellationToken = default); Task UpdateAsync(UpdateEmailSettingsRequest request, CancellationToken cancellationToken = default); } public sealed class EmailSettingsResolver : IEmailSettingsResolver { private readonly IConfiguration _cfg; private readonly JobTrackerContext _db; public EmailSettingsResolver(IConfiguration cfg, JobTrackerContext db) { _cfg = cfg; _db = db; } public async Task GetSnapshotAsync(CancellationToken cancellationToken = default) { var overrides = await _db.SystemEmailSettings.AsNoTracking().FirstOrDefaultAsync(x => x.Id == 1, cancellationToken); return Merge(overrides); } public async Task GetAdminDtoAsync(CancellationToken cancellationToken = default) { var snapshot = await GetSnapshotAsync(cancellationToken); return new EmailSettingsAdminDto( Enabled: snapshot.Enabled, Host: snapshot.Host, Port: snapshot.Port, User: snapshot.User, From: snapshot.From, FromName: snapshot.FromName, EnableSsl: snapshot.EnableSsl, TimeoutMs: snapshot.TimeoutMs, UsesOverrides: snapshot.UsesOverrides, HasPassword: snapshot.HasPassword); } public async Task UpdateAsync(UpdateEmailSettingsRequest request, CancellationToken cancellationToken = default) { var settings = await _db.SystemEmailSettings.FirstOrDefaultAsync(x => x.Id == 1, cancellationToken); if (settings is null) { settings = new SystemEmailSettings { Id = 1 }; _db.SystemEmailSettings.Add(settings); } settings.Enabled = request.Enabled; settings.SmtpHost = TrimOrNull(request.Host); settings.SmtpPort = request.Port <= 0 ? 587 : request.Port; settings.SmtpUser = TrimOrNull(request.User); settings.From = TrimOrNull(request.From); settings.FromName = TrimOrNull(request.FromName); settings.SmtpEnableSsl = request.EnableSsl; settings.SmtpTimeoutMs = request.TimeoutMs <= 0 ? 15000 : request.TimeoutMs; if (request.ClearPassword) { settings.SmtpPassword = null; } else if (!string.IsNullOrWhiteSpace(request.Password)) { settings.SmtpPassword = request.Password.Trim(); } await _db.SaveChangesAsync(cancellationToken); return await GetAdminDtoAsync(cancellationToken); } private EmailSettingsSnapshot Merge(SystemEmailSettings? overrides) { var host = overrides?.SmtpHost ?? (_cfg["Email:SmtpHost"] ?? string.Empty).Trim(); var user = overrides?.SmtpUser ?? (_cfg["Email:SmtpUser"] ?? string.Empty).Trim(); var password = overrides?.SmtpPassword ?? (_cfg["Email:SmtpPassword"] ?? string.Empty).Trim(); var from = overrides?.From ?? (_cfg["Email:From"] ?? user).Trim(); var fromName = overrides?.FromName ?? (_cfg["Email:FromName"] ?? "Jobbjakt").Trim(); var port = overrides?.SmtpPort ?? _cfg.GetValue("Email:SmtpPort", 587); if (port <= 0) port = 587; var enableSsl = overrides?.SmtpEnableSsl ?? _cfg.GetValue("Email:SmtpEnableSsl", true); var timeoutMs = overrides?.SmtpTimeoutMs ?? _cfg.GetValue("Email:SmtpTimeoutMs", 15000); if (timeoutMs <= 0) timeoutMs = 15000; var enabled = overrides?.Enabled ?? _cfg.GetValue("Email:Enabled", false); var usesOverrides = overrides is not null; return new EmailSettingsSnapshot( Enabled: enabled, Host: host, Port: port, User: user, Password: password, From: from, FromName: fromName, EnableSsl: enableSsl, TimeoutMs: timeoutMs, UsesOverrides: usesOverrides, HasPassword: !string.IsNullOrWhiteSpace(password)); } private static string? TrimOrNull(string? value) { return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } }