Added the ability to unsubscribe from the newsletter #2137

pull/4235/head v4.0.1422
tidusjar 3 years ago
parent 043eb08aa5
commit ac3f1941bc

@ -2,6 +2,6 @@
{
public interface INewsletterTemplate
{
string LoadTemplate(string subject, string intro, string tableHtml, string logo);
string LoadTemplate(string subject, string intro, string tableHtml, string logo, string unsubscribeLink);
}
}

@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates
if (string.IsNullOrEmpty(_templateLocation))
{
#if DEBUG
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp3.0", "Templates", "NewsletterTemplate.html");
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net5.0", "Templates", "NewsletterTemplate.html");
#else
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "NewsletterTemplate.html");
#endif
@ -29,9 +29,10 @@ namespace Ombi.Notifications.Templates
private const string Logo = "{@LOGO}";
private const string TableLocation = "{@RECENTLYADDED}";
private const string IntroText = "{@INTRO}";
private const string Unsubscribe = "{@UNSUBSCRIBE}";
public string LoadTemplate(string subject, string intro, string tableHtml, string logo)
public string LoadTemplate(string subject, string intro, string tableHtml, string logo, string unsubscribeLink)
{
var sb = new StringBuilder(File.ReadAllText(TemplateLocation));
sb.Replace(SubjectKey, subject);
@ -39,6 +40,7 @@ namespace Ombi.Notifications.Templates
sb.Replace(IntroText, intro);
sb.Replace(DateKey, DateTime.Now.ToString("f"));
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo);
sb.Replace(Unsubscribe, string.IsNullOrEmpty(unsubscribeLink) ? string.Empty : unsubscribeLink);
return sb.ToString();
}

@ -451,6 +451,11 @@
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
<tbody>
<tr>
<td class="content-block powered-by" valign="top" align="center" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;">
<a href="{@UNSUBSCRIBE}" style="font-weight: 400; font-size: 12px; text-align: center; color: #ff761b;">Unsubscribe</a>
</td>
</tr>
<tr>
<td class="content-block powered-by" valign="top" align="center" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;">
Powered by <a href="https://github.com/Ombi-app/Ombi" style="font-weight: 400; font-size: 12px; text-align: center; text-decoration: none; color: #ff761b;">Ombi</a>

@ -0,0 +1,35 @@
using NUnit.Framework;
using Ombi.Schedule.Jobs.Ombi;
using System.Collections.Generic;
namespace Ombi.Schedule.Tests
{
[TestFixture]
public class NewsletterUnsubscribeTests
{
[TestCaseSource(nameof(Data))]
public string GenerateUnsubscribeLinkTest(string appUrl, string id)
{
return NewsletterJob.GenerateUnsubscribeLink(appUrl, id);
}
private static IEnumerable<TestCaseData> Data
{
get
{
yield return new TestCaseData("https://google.com/", "1").Returns("https://google.com:443/unsubscribe/1").SetName("Fully Qualified");
yield return new TestCaseData("https://google.com", "1").Returns("https://google.com:443/unsubscribe/1").SetName("Missing Slash");
yield return new TestCaseData("google.com", "1").Returns("http://google.com:80/unsubscribe/1").SetName("Missing scheme");
yield return new TestCaseData("ombi.google.com", "1").Returns("http://ombi.google.com:80/unsubscribe/1").SetName("Sub domain missing scheme");
yield return new TestCaseData("https://ombi.google.com", "1").Returns("https://ombi.google.com:443/unsubscribe/1").SetName("Sub domain");
yield return new TestCaseData("https://ombi.google.com/", "1").Returns("https://ombi.google.com:443/unsubscribe/1").SetName("Sub domain with slash");
yield return new TestCaseData("https://google.com/ombi/", "1").Returns("https://google.com:443/ombi/unsubscribe/1").SetName("RP");
yield return new TestCaseData("https://google.com/ombi", "1").Returns("https://google.com:443/ombi/unsubscribe/1").SetName("RP missing slash");
yield return new TestCaseData("https://google.com:3577", "1").Returns("https://google.com:3577/unsubscribe/1").SetName("Port");
yield return new TestCaseData("https://google.com:3577/", "1").Returns("https://google.com:3577/unsubscribe/1").SetName("Port With Slash");
yield return new TestCaseData("", "1").Returns(string.Empty).SetName("Missing App URL empty");
yield return new TestCaseData(null, "1").Returns(string.Empty).SetName("Missing App URL null");
}
}
}
}

@ -228,32 +228,33 @@ namespace Ombi.Schedule.Jobs.Ombi
var messageContent = ParseTemplate(template, customization);
var email = new NewsletterTemplate();
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
var bodyBuilder = new BodyBuilder
foreach (var user in users)
{
HtmlBody = html,
};
var url = GenerateUnsubscribeLink(customization.ApplicationUrl, user.Id);
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo, url);
var message = new MimeMessage
{
Body = bodyBuilder.ToMessageBody(),
Subject = messageContent.Subject
};
var bodyBuilder = new BodyBuilder
{
HtmlBody = html,
};
var message = new MimeMessage
{
Body = bodyBuilder.ToMessageBody(),
Subject = messageContent.Subject
};
foreach (var user in users)
{
// Get the users to send it to
if (user.Email.IsNullOrEmpty())
{
continue;
}
// BCC the messages
message.Bcc.Add(new MailboxAddress(user.Email.Trim(), user.Email.Trim()));
}
// Send the message to the user
message.To.Add(new MailboxAddress(user.Email.Trim(), user.Email.Trim()));
// Send the email
await _email.Send(message, emailSettings);
// Send the email
await _email.Send(message, emailSettings);
}
// Now add all of this to the Recently Added log
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
@ -346,11 +347,14 @@ namespace Ombi.Schedule.Jobs.Ombi
{
continue;
}
var unsubscribeLink = GenerateUnsubscribeLink(customization.ApplicationUrl, a.Id);
var messageContent = ParseTemplate(template, customization);
var email = new NewsletterTemplate();
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo, unsubscribeLink);
await _email.Send(
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email },
@ -371,6 +375,21 @@ namespace Ombi.Schedule.Jobs.Ombi
.SendAsync(NotificationHub.NotificationEvent, "Newsletter Finished");
}
public static string GenerateUnsubscribeLink(string applicationUrl, string id)
{
if (!applicationUrl.HasValue())
{
return string.Empty;
}
if (!applicationUrl.EndsWith('/'))
{
applicationUrl += '/';
}
var b = new UriBuilder($"{applicationUrl}unsubscribe/{id}");
return b.ToString();
}
private async Task<HashSet<PlexServerContent>> GetMoviesWithoutId(HashSet<int> addedMovieLogIds, HashSet<PlexServerContent> needsMovieDbPlex)
{
foreach (var movie in needsMovieDbPlex)

@ -91,6 +91,7 @@ const routes: Routes = [
{ loadChildren: () => import("./vote/vote.module").then(m => m.VoteModule), path: "vote" },
{ loadChildren: () => import("./media-details/media-details.module").then(m => m.MediaDetailsModule), path: "details" },
{ loadChildren: () => import("./user-preferences/user-preferences.module").then(m => m.UserPreferencesModule), path: "user-preferences" },
{ loadChildren: () => import("./unsubscribe/unsubscribe.module").then(m => m.UnsubscribeModule), path: "unsubscribe" },
];

@ -95,4 +95,8 @@ export class IdentityService extends ServiceHelpers {
public updateStreamingCountry(code: string): Observable<null> {
return this.http.post<any>(`${this.url}streamingcountry`, {code: code}, {headers: this.headers});
}
public unsubscribeNewsletter(userId: string): Observable<any>{
return this.http.get<any>(`${this.url}newsletter/unsubscribe/${userId}`, {headers: this.headers});
}
}

@ -0,0 +1,3 @@
<div class="wizard-background">
<h2 style="color:white">Unsubscribed!</h2>
</div>

@ -0,0 +1,28 @@
import { Component, OnInit } from "@angular/core";
import { IdentityService, SettingsService } from "../../../services";
import { ActivatedRoute } from "@angular/router";
import { AuthService } from "../../../auth/auth.service";
@Component({
templateUrl: "./unsubscribe-confirm.component.html",
})
export class UnsubscribeConfirmComponent implements OnInit {
private userId: string;
constructor(private authService: AuthService,
private readonly identityService: IdentityService,
private readonly settingsService: SettingsService,
private route: ActivatedRoute) {
this.route.params.subscribe(async (params: any) => {
if (typeof params.id === 'string' || params.id instanceof String) {
this.userId = params.id;
}
});
}
public async ngOnInit() {
this.identityService.unsubscribeNewsletter(this.userId).subscribe()
}
}

@ -0,0 +1,27 @@
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { RouterModule, Routes } from "@angular/router";
import { CommonModule } from "@angular/common";
import { NgModule } from "@angular/core";
import { SharedModule } from "../shared/shared.module";
import { UnsubscribeConfirmComponent } from "./components/confirm-component/unsubscribe-confirm.component";
const routes: Routes = [
{ path: ":id", component: UnsubscribeConfirmComponent},
];
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
SharedModule,
RouterModule.forChild(routes),
],
declarations: [
UnsubscribeConfirmComponent,
],
providers: [
],
})
export class UnsubscribeModule { }

@ -1032,6 +1032,22 @@ namespace Ombi.Controllers.V1
return Json(true);
}
[HttpGet("newsletter/unsubscribe/{userId}")]
[AllowAnonymous]
public async Task<IActionResult> UnsubscribeUser(string userId)
{
// lookup user
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
if (user == null)
{
return Ok();
}
await UserManager.RemoveFromRoleAsync(user, OmbiRoles.ReceivesNewsletter);
return Ok();
}
private async Task<List<IdentityResult>> AddRoles(IEnumerable<ClaimCheckboxes> roles, OmbiUser ombiUser)
{
var roleResult = new List<IdentityResult>();

Loading…
Cancel
Save