Kavita+ Overhaul & New Changelog (#3507)
This commit is contained in:
parent
d880c1690c
commit
a5707617f2
249 changed files with 14775 additions and 2300 deletions
|
|
@ -8,7 +8,10 @@ using System.Threading.Tasks;
|
|||
using System.Web;
|
||||
using API.Data;
|
||||
using API.DTOs.Email;
|
||||
using API.Entities;
|
||||
using API.Services.Plus;
|
||||
using Kavita.Common;
|
||||
using Kavita.Common.Extensions;
|
||||
using MailKit.Security;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
|
@ -29,6 +32,8 @@ internal class EmailOptionsDto
|
|||
/// Filenames to attach
|
||||
/// </summary>
|
||||
public IList<string>? Attachments { get; set; }
|
||||
public int? ToUserId { get; set; }
|
||||
public required string Template { get; set; }
|
||||
}
|
||||
|
||||
public interface IEmailService
|
||||
|
|
@ -43,6 +48,9 @@ public interface IEmailService
|
|||
|
||||
Task<string> GenerateEmailLink(HttpRequest request, string token, string routePart, string email,
|
||||
bool withHost = true);
|
||||
|
||||
Task<bool> SendTokenExpiredEmail(int userId, ScrobbleProvider provider);
|
||||
Task<bool> SendTokenExpiringSoonEmail(int userId, ScrobbleProvider provider);
|
||||
}
|
||||
|
||||
public class EmailService : IEmailService
|
||||
|
|
@ -56,6 +64,14 @@ public class EmailService : IEmailService
|
|||
private const string TemplatePath = @"{0}.html";
|
||||
private const string LocalHost = "localhost:4200";
|
||||
|
||||
public const string SendToDeviceTemplate = "SendToDevice";
|
||||
public const string EmailTestTemplate = "EmailTest";
|
||||
public const string EmailChangeTemplate = "EmailChange";
|
||||
public const string TokenExpirationTemplate = "TokenExpiration";
|
||||
public const string TokenExpiringSoonTemplate = "TokenExpiringSoon";
|
||||
public const string EmailConfirmTemplate = "EmailConfirm";
|
||||
public const string EmailPasswordResetTemplate = "EmailPasswordReset";
|
||||
|
||||
public EmailService(ILogger<EmailService> logger, IUnitOfWork unitOfWork, IDirectoryService directoryService,
|
||||
IHostEnvironment environment, ILocalizationService localizationService)
|
||||
{
|
||||
|
|
@ -104,12 +120,13 @@ public class EmailService : IEmailService
|
|||
var emailOptions = new EmailOptionsDto()
|
||||
{
|
||||
Subject = "Kavita - Email Test",
|
||||
Body = UpdatePlaceHolders(await GetEmailBody("EmailTest"), placeholders),
|
||||
Template = EmailTestTemplate,
|
||||
Body = UpdatePlaceHolders(await GetEmailBody(EmailTestTemplate), placeholders),
|
||||
Preheader = "Kavita - Email Test",
|
||||
ToEmails = new List<string>()
|
||||
{
|
||||
adminEmail
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
await SendEmail(emailOptions);
|
||||
|
|
@ -139,7 +156,8 @@ public class EmailService : IEmailService
|
|||
var emailOptions = new EmailOptionsDto()
|
||||
{
|
||||
Subject = UpdatePlaceHolders("Your email has been changed on {{InvitingUser}}'s Server", placeholders),
|
||||
Body = UpdatePlaceHolders(await GetEmailBody("EmailChange"), placeholders),
|
||||
Template = EmailChangeTemplate,
|
||||
Body = UpdatePlaceHolders(await GetEmailBody(EmailChangeTemplate), placeholders),
|
||||
Preheader = UpdatePlaceHolders("Your email has been changed on {{InvitingUser}}'s Server", placeholders),
|
||||
ToEmails = new List<string>()
|
||||
{
|
||||
|
|
@ -155,9 +173,9 @@ public class EmailService : IEmailService
|
|||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsValidEmail(string email)
|
||||
public bool IsValidEmail(string? email)
|
||||
{
|
||||
return new EmailAddressAttribute().IsValid(email);
|
||||
return !string.IsNullOrEmpty(email) && new EmailAddressAttribute().IsValid(email);
|
||||
}
|
||||
|
||||
public async Task<string> GenerateEmailLink(HttpRequest request, string token, string routePart, string email, bool withHost = true)
|
||||
|
|
@ -180,6 +198,66 @@ public class EmailService : IEmailService
|
|||
.Replace("//", "/");
|
||||
}
|
||||
|
||||
public async Task<bool> SendTokenExpiredEmail(int userId, ScrobbleProvider provider)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
if (user == null || !IsValidEmail(user.Email) || !settings.IsEmailSetup()) return false;
|
||||
|
||||
var placeholders = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new ("{{UserName}}", user.UserName!),
|
||||
new ("{{Provider}}", provider.ToDescription()),
|
||||
new ("{{Link}}", $"{settings.HostName}/settings#account" ),
|
||||
};
|
||||
|
||||
var emailOptions = new EmailOptionsDto()
|
||||
{
|
||||
Subject = UpdatePlaceHolders("Kavita - Your {{Provider}} token has expired and scrobbling events have stopped", placeholders),
|
||||
Template = TokenExpirationTemplate,
|
||||
Body = UpdatePlaceHolders(await GetEmailBody(TokenExpirationTemplate), placeholders),
|
||||
Preheader = UpdatePlaceHolders("Kavita - Your {{Provider}} token has expired and scrobbling events have stopped", placeholders),
|
||||
ToEmails = new List<string>()
|
||||
{
|
||||
user.Email
|
||||
}
|
||||
};
|
||||
|
||||
await SendEmail(emailOptions);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> SendTokenExpiringSoonEmail(int userId, ScrobbleProvider provider)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
if (user == null || !IsValidEmail(user.Email) || !settings.IsEmailSetup()) return false;
|
||||
|
||||
var placeholders = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new ("{{UserName}}", user.UserName!),
|
||||
new ("{{Provider}}", provider.ToDescription()),
|
||||
new ("{{Link}}", $"{settings.HostName}/settings#account" ),
|
||||
};
|
||||
|
||||
var emailOptions = new EmailOptionsDto()
|
||||
{
|
||||
Subject = UpdatePlaceHolders("Kavita - Your {{Provider}} token will expire soon!", placeholders),
|
||||
Template = TokenExpiringSoonTemplate,
|
||||
Body = UpdatePlaceHolders(await GetEmailBody(TokenExpiringSoonTemplate), placeholders),
|
||||
Preheader = UpdatePlaceHolders("Kavita - Your {{Provider}} token will expire soon!", placeholders),
|
||||
ToEmails = new List<string>()
|
||||
{
|
||||
user.Email
|
||||
}
|
||||
};
|
||||
|
||||
await SendEmail(emailOptions);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends an invite email to a user to setup their account
|
||||
/// </summary>
|
||||
|
|
@ -195,7 +273,8 @@ public class EmailService : IEmailService
|
|||
var emailOptions = new EmailOptionsDto()
|
||||
{
|
||||
Subject = UpdatePlaceHolders("You've been invited to join {{InvitingUser}}'s Server", placeholders),
|
||||
Body = UpdatePlaceHolders(await GetEmailBody("EmailConfirm"), placeholders),
|
||||
Template = EmailConfirmTemplate,
|
||||
Body = UpdatePlaceHolders(await GetEmailBody(EmailConfirmTemplate), placeholders),
|
||||
Preheader = UpdatePlaceHolders("You've been invited to join {{InvitingUser}}'s Server", placeholders),
|
||||
ToEmails = new List<string>()
|
||||
{
|
||||
|
|
@ -221,8 +300,9 @@ public class EmailService : IEmailService
|
|||
var emailOptions = new EmailOptionsDto()
|
||||
{
|
||||
Subject = UpdatePlaceHolders("A password reset has been requested", placeholders),
|
||||
Body = UpdatePlaceHolders(await GetEmailBody("EmailPasswordReset"), placeholders),
|
||||
Preheader = "A password reset has been requested",
|
||||
Template = EmailPasswordResetTemplate,
|
||||
Body = UpdatePlaceHolders(await GetEmailBody(EmailPasswordResetTemplate), placeholders),
|
||||
Preheader = "Email confirmation is required for continued access. Click the button to confirm your email.",
|
||||
ToEmails = new List<string>()
|
||||
{
|
||||
dto.EmailAddress
|
||||
|
|
@ -242,11 +322,9 @@ public class EmailService : IEmailService
|
|||
{
|
||||
Subject = "Send file from Kavita",
|
||||
Preheader = "File(s) sent from Kavita",
|
||||
ToEmails = new List<string>()
|
||||
{
|
||||
data.DestinationEmail
|
||||
},
|
||||
Body = await GetEmailBody("SendToDevice"),
|
||||
ToEmails = [data.DestinationEmail],
|
||||
Template = SendToDeviceTemplate,
|
||||
Body = await GetEmailBody(SendToDeviceTemplate),
|
||||
Attachments = data.FilePaths.ToList()
|
||||
};
|
||||
|
||||
|
|
@ -302,21 +380,66 @@ public class EmailService : IEmailService
|
|||
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.SystemDefault;
|
||||
|
||||
var emailAddress = userEmailOptions.ToEmails[0];
|
||||
AppUser? user;
|
||||
if (userEmailOptions.Template == SendToDeviceTemplate)
|
||||
{
|
||||
user = await _unitOfWork.UserRepository.GetUserByDeviceEmail(emailAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
user = await _unitOfWork.UserRepository.GetUserByEmailAsync(emailAddress);
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
await smtpClient.SendAsync(email);
|
||||
if (user != null)
|
||||
{
|
||||
await LogEmailHistory(user.Id, userEmailOptions.Template, userEmailOptions.Subject, userEmailOptions.Body, "Sent");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an issue sending the email");
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
await LogEmailHistory(user.Id, userEmailOptions.Template, userEmailOptions.Subject, userEmailOptions.Body, "Failed", ex.Message);
|
||||
}
|
||||
_logger.LogError("Could not find user on file for email, {Template} email was not sent and not recorded into history table", userEmailOptions.Template);
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
await smtpClient.DisconnectAsync(true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs email history for the specified user.
|
||||
/// </summary>
|
||||
private async Task LogEmailHistory(int appUserId, string emailTemplate, string subject, string body, string deliveryStatus, string? errorMessage = null)
|
||||
{
|
||||
var emailHistory = new EmailHistory
|
||||
{
|
||||
AppUserId = appUserId,
|
||||
EmailTemplate = emailTemplate,
|
||||
Sent = deliveryStatus == "Sent",
|
||||
Body = body,
|
||||
Subject = subject,
|
||||
SendDate = DateTime.UtcNow,
|
||||
DeliveryStatus = deliveryStatus,
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
|
||||
_unitOfWork.DataContext.EmailHistory.Add(emailHistory);
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
private async Task<string> GetTemplatePath(string templateName)
|
||||
{
|
||||
if ((await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).SmtpConfig.CustomizedTemplates)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue